Unverified Commit 5cc2545c by Serge Hallyn Committed by GitHub

Merge pull request #1871 from brauner/2017-10-21/api_extension_console_ringbuffer

API: add console ringbuffer extension
parents 29e4eb31 a52c1c68
......@@ -78,6 +78,7 @@ src/tests/lxc-test-cgpath
src/tests/lxc-test-clonetest
src/tests/lxc-test-concurrent
src/tests/lxc-test-console
src/tests/lxc-test-console-log
src/tests/lxc-test-containertests
src/tests/lxc-test-createtest
src/tests/lxc-test-destroytest
......
......@@ -29,6 +29,7 @@
#include <sys/types.h>
#include "state.h"
#include "lxccontainer.h"
#define LXC_CMD_DATA_MAX (MAXPATHLEN * 2)
......@@ -49,6 +50,7 @@ typedef enum {
LXC_CMD_GET_LXCPATH,
LXC_CMD_ADD_STATE_CLIENT,
LXC_CMD_SET_CONFIG_ITEM,
LXC_CMD_CONSOLE_LOG,
LXC_CMD_MAX,
} lxc_cmd_t;
......@@ -79,6 +81,14 @@ struct lxc_cmd_set_config_item_req_data {
void *value;
};
struct lxc_cmd_console_log {
bool clear;
bool read;
uint64_t read_max;
bool write_logfile;
};
extern int lxc_cmd_console_winch(const char *name, const char *lxcpath);
extern int lxc_cmd_console(const char *name, int *ttynum, int *fd,
const char *lxcpath);
......@@ -124,5 +134,7 @@ extern int lxc_try_cmd(const char *name, const char *lxcpath);
extern int lxc_cmd_set_config_item(const char *name, const char *item,
const char *value, const char *lxcpath);
extern int lxc_cmd_console_log(const char *name, const char *lxcpath,
struct lxc_console_log *log);
#endif /* __commands_h */
......@@ -3083,65 +3083,7 @@ static bool verify_start_hooks(struct lxc_conf *conf)
return true;
}
/**
* Note that this function needs to run before the mainloop starts. Since we
* register a handler for the console's masterfd when we create the mainloop
* the console handler needs to see an allocated ringbuffer.
*/
static int lxc_setup_console_ringbuf(struct lxc_console *console)
{
int ret;
struct lxc_ringbuf *buf = &console->ringbuf;
uint64_t size = console->log_size;
/* no ringbuffer previously allocated and no ringbuffer requested */
if (!buf->addr && size <= 0)
return 0;
/* ringbuffer allocated but no new ringbuffer requested */
if (buf->addr && size <= 0) {
lxc_ringbuf_release(buf);
buf->addr = NULL;
buf->r_off = 0;
buf->w_off = 0;
buf->size = 0;
TRACE("Deallocated console ringbuffer");
return 0;
}
if (size <= 0)
return 0;
/* check wether the requested size for the ringbuffer has changed */
if (buf->addr && buf->size != size) {
TRACE("Console ringbuffer size changed from %" PRIu64
" to %" PRIu64 " bytes. Deallocating console ringbuffer",
buf->size, size);
lxc_ringbuf_release(buf);
}
ret = lxc_ringbuf_create(buf, size);
if (ret < 0) {
ERROR("Failed to setup %" PRIu64 " byte console ringbuffer", size);
return -1;
}
TRACE("Allocated %" PRIu64 " byte console ringbuffer", size);
return 0;
}
int lxc_setup_parent(struct lxc_handler *handler)
{
int ret;
ret = lxc_setup_console_ringbuf(&handler->conf->console);
if (ret < 0)
return -1;
return 0;
}
int lxc_setup_child(struct lxc_handler *handler)
int lxc_setup(struct lxc_handler *handler)
{
int ret;
const char *name = handler->name;
......
......@@ -379,7 +379,7 @@ extern int lxc_delete_autodev(struct lxc_handler *handler);
extern void lxc_clear_includes(struct lxc_conf *conf);
extern int do_rootfs_setup(struct lxc_conf *conf, const char *name,
const char *lxcpath);
extern int lxc_setup_child(struct lxc_handler *handler);
extern int lxc_setup(struct lxc_handler *handler);
extern int lxc_setup_parent(struct lxc_handler *handler);
extern int setup_resource_limits(struct lxc_list *limits, pid_t pid);
extern int find_unmapped_nsid(struct lxc_conf *conf, enum idtype idtype);
......
......@@ -512,11 +512,50 @@ out:
return ret;
}
int lxc_console_write_ringbuffer(struct lxc_console *console)
{
int fd;
char *r_addr;
ssize_t ret;
uint64_t used;
struct lxc_ringbuf *buf = &console->ringbuf;
if (!console->log_path)
return 0;
used = lxc_ringbuf_used(buf);
if (used == 0)
return 0;
fd = lxc_unpriv(open(console->log_path, O_CLOEXEC | O_RDWR | O_CREAT | O_TRUNC, 0600));
if (fd < 0) {
SYSERROR("Failed to open console log file \"%s\"", console->log_path);
return -EIO;
}
DEBUG("Using \"%s\" as console log file", console->log_path);
r_addr = lxc_ringbuf_get_read_addr(buf);
ret = lxc_write_nointr(fd, r_addr, used);
close(fd);
if (ret < 0)
return -EIO;
return 0;
}
void lxc_console_delete(struct lxc_console *console)
{
if (console->tios && console->peer >= 0 &&
tcsetattr(console->peer, TCSAFLUSH, console->tios))
WARN("failed to set old terminal settings");
int ret;
ret = lxc_console_write_ringbuffer(console);
if (ret < 0)
WARN("Failed to write console log to disk");
if (console->tios && console->peer >= 0) {
ret = tcsetattr(console->peer, TCSAFLUSH, console->tios);
if (ret < 0)
WARN("%s - Failed to set old terminal settings", strerror(errno));
}
free(console->tios);
console->tios = NULL;
......@@ -525,66 +564,120 @@ void lxc_console_delete(struct lxc_console *console)
close(console->slave);
if (console->log_fd >= 0)
close(console->log_fd);
console->peer = -1;
console->master = -1;
console->slave = -1;
console->log_fd = -1;
}
/**
* Note that this function needs to run before the mainloop starts. Since we
* register a handler for the console's masterfd when we create the mainloop
* the console handler needs to see an allocated ringbuffer.
*/
static int lxc_setup_console_ringbuf(struct lxc_console *console)
{
int ret;
struct lxc_ringbuf *buf = &console->ringbuf;
uint64_t size = console->log_size;
/* no ringbuffer previously allocated and no ringbuffer requested */
if (!buf->addr && size <= 0)
return 0;
/* ringbuffer allocated but no new ringbuffer requested */
if (buf->addr && size <= 0) {
lxc_ringbuf_release(buf);
buf->addr = NULL;
buf->r_off = 0;
buf->w_off = 0;
buf->size = 0;
TRACE("Deallocated console ringbuffer");
return 0;
}
if (size <= 0)
return 0;
/* check wether the requested size for the ringbuffer has changed */
if (buf->addr && buf->size != size) {
TRACE("Console ringbuffer size changed from %" PRIu64
" to %" PRIu64 " bytes. Deallocating console ringbuffer",
buf->size, size);
lxc_ringbuf_release(buf);
}
ret = lxc_ringbuf_create(buf, size);
if (ret < 0) {
ERROR("Failed to setup %" PRIu64 " byte console ringbuffer", size);
return -1;
}
TRACE("Allocated %" PRIu64 " byte console ringbuffer", size);
return 0;
}
int lxc_console_create(struct lxc_conf *conf)
{
int ret, saved_errno;
struct lxc_console *console = &conf->console;
int ret;
if (!conf->rootfs.path) {
INFO("container does not have a rootfs, console device will be shared with the host");
INFO("Container does not have a rootfs. The console will be "
"shared with the host");
return 0;
}
if (console->path && !strcmp(console->path, "none")) {
INFO("no console requested");
INFO("No console was requested");
return 0;
}
process_lock();
ret = openpty(&console->master, &console->slave, console->name, NULL, NULL);
saved_errno = errno;
process_unlock();
if (ret < 0) {
SYSERROR("failed to allocate a pty");
ERROR("%s - Failed to allocate a pty", strerror(saved_errno));
return -1;
}
if (fcntl(console->master, F_SETFD, FD_CLOEXEC)) {
SYSERROR("failed to set console master to close-on-exec");
ret = fcntl(console->master, F_SETFD, FD_CLOEXEC);
if (ret < 0) {
SYSERROR("Failed to set FD_CLOEXEC flag on console master");
goto err;
}
if (fcntl(console->slave, F_SETFD, FD_CLOEXEC)) {
SYSERROR("failed to set console slave to close-on-exec");
ret = fcntl(console->slave, F_SETFD, FD_CLOEXEC);
if (ret < 0) {
SYSERROR("Failed to set FD_CLOEXEC flag on console slave");
goto err;
}
ret = lxc_console_peer_default(console);
if (ret < 0) {
ERROR("failed to allocate peer tty device");
ERROR("Failed to allocate a peer pty device");
goto err;
}
if (console->log_path) {
if (console->log_path && console->log_size <= 0) {
console->log_fd = lxc_unpriv(open(console->log_path, O_CLOEXEC | O_RDWR | O_CREAT | O_APPEND, 0600));
if (console->log_fd < 0) {
SYSERROR("failed to open console log file \"%s\"", console->log_path);
SYSERROR("Failed to open console log file \"%s\"", console->log_path);
goto err;
}
DEBUG("using \"%s\" as console log file", console->log_path);
DEBUG("Using \"%s\" as console log file", console->log_path);
}
ret = lxc_setup_console_ringbuf(console);
if (ret < 0)
goto err;
return 0;
err:
lxc_console_delete(console);
return -1;
return -ENODEV;
}
int lxc_console_set_stdfds(int fd)
......@@ -687,18 +780,16 @@ int lxc_console(struct lxc_container *c, int ttynum,
istty = isatty(stdinfd);
if (istty) {
ret = lxc_setup_tios(stdinfd, &oldtios);
if (ret) {
ERROR("failed to setup terminal properties");
if (ret < 0)
return -1;
}
} else {
INFO("fd %d does not refer to a tty device", stdinfd);
INFO("File descriptor %d does not refer to a tty device", stdinfd);
}
ttyfd = lxc_cmd_console(c->name, &ttynum, &masterfd, c->config_path);
if (ttyfd < 0) {
ret = ttyfd;
goto err1;
goto restore_tios;
}
fprintf(stderr, "\n"
......@@ -708,13 +799,13 @@ int lxc_console(struct lxc_container *c, int ttynum,
ttynum, 'a' + escape - 1);
ret = setsid();
if (ret)
INFO("already group leader");
if (ret < 0)
TRACE("Process is already group leader");
ts = lxc_console_sigwinch_init(stdinfd, masterfd);
if (!ts) {
ret = -1;
goto err2;
goto close_fds;
}
ts->escape = escape;
ts->winch_proxy = c->name;
......@@ -728,52 +819,57 @@ int lxc_console(struct lxc_container *c, int ttynum,
ret = lxc_mainloop_open(&descr);
if (ret) {
ERROR("failed to create mainloop");
goto err3;
ERROR("Failed to create mainloop");
goto sigwinch_fini;
}
if (ts->sigfd != -1) {
ret = lxc_mainloop_add_handler(&descr, ts->sigfd,
lxc_console_cb_sigwinch_fd, ts);
if (ret) {
ERROR("failed to add handler for SIGWINCH fd");
goto err4;
lxc_console_cb_sigwinch_fd, ts);
if (ret < 0) {
ERROR("Failed to add SIGWINCH handler");
goto close_mainloop;
}
}
ret = lxc_mainloop_add_handler(&descr, ts->stdinfd,
lxc_console_cb_tty_stdin, ts);
if (ret) {
ERROR("failed to add handler for stdinfd");
goto err4;
if (ret < 0) {
ERROR("Failed to add stdin handler");
goto close_mainloop;
}
ret = lxc_mainloop_add_handler(&descr, ts->masterfd,
lxc_console_cb_tty_master, ts);
if (ret) {
ERROR("failed to add handler for masterfd");
goto err4;
if (ret < 0) {
ERROR("Failed to add master handler");
goto close_mainloop;
}
ret = lxc_mainloop(&descr, -1);
if (ret) {
ERROR("mainloop returned an error");
goto err4;
if (ret < 0) {
ERROR("The mainloop returned an error");
goto close_mainloop;
}
ret = 0;
err4:
close_mainloop:
lxc_mainloop_close(&descr);
err3:
sigwinch_fini:
lxc_console_sigwinch_fini(ts);
err2:
close_fds:
close(masterfd);
close(ttyfd);
err1:
restore_tios:
if (istty) {
if (tcsetattr(stdinfd, TCSAFLUSH, &oldtios) < 0)
WARN("failed to reset terminal properties: %s.", strerror(errno));
istty = tcsetattr(stdinfd, TCSAFLUSH, &oldtios);
if (istty < 0)
WARN("%s - Failed to restore terminal properties",
strerror(errno));
}
return ret;
......
......@@ -215,4 +215,6 @@ extern int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
*/
extern void lxc_console_sigwinch_fini(struct lxc_tty_state *ts);
extern int lxc_console_write_ringbuffer(struct lxc_console *console);
#endif
......@@ -516,6 +516,29 @@ static int lxcapi_console(struct lxc_container *c, int ttynum, int stdinfd,
return ret;
}
static int do_lxcapi_console_log(struct lxc_container *c, struct lxc_console_log *log)
{
int ret;
ret = lxc_cmd_console_log(c->name, do_lxcapi_get_config_path(c), log);
if (ret < 0) {
if (ret == -ENODATA)
NOTICE("The console log is empty");
else if (ret == -EFAULT)
NOTICE("The container does not keep a console log");
else if (ret == -ENOENT)
NOTICE("The container does not keep a console log file");
else if (ret == -EIO)
NOTICE("Failed to write console log to log file");
else
ERROR("Failed to retrieve console log");
}
return ret;
}
WRAP_API_1(int, lxcapi_console_log, struct lxc_console_log *)
static pid_t do_lxcapi_init_pid(struct lxc_container *c)
{
if (!c)
......@@ -4607,6 +4630,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
c->checkpoint = lxcapi_checkpoint;
c->restore = lxcapi_restore;
c->migrate = lxcapi_migrate;
c->console_log = lxcapi_console_log;
return c;
......
......@@ -51,6 +51,8 @@ struct lxc_lock;
struct migrate_opts;
struct lxc_console_log;
/*!
* An LXC container.
*
......@@ -834,6 +836,16 @@ struct lxc_container {
* \return \c true on success, else \c false.
*/
bool (*set_running_config_item)(struct lxc_container *c, const char *key, const char *value);
/*!
* \brief Query the console log of a container.
*
* \param c Container.
* \param opts A lxc_console_log struct filled with relevant options.
*
* \return \c 0 on success, nonzero on failure.
*/
int (*console_log)(struct lxc_container *c, struct lxc_console_log *log);
};
/*!
......@@ -921,6 +933,34 @@ struct migrate_opts {
uint64_t ghost_limit;
};
struct lxc_console_log {
/* Clear the console log. */
bool clear;
/* Retrieve the console log. */
bool read;
/* This specifies the maximum size to read from the ringbuffer. Setting
* it to 0 means that the a read can be as big as the whole ringbuffer.
* On return callers can check how many bytes were actually read.
* If "read" and "clear" are set to false and a non-zero value is
* specified then up to "read_max" bytes of data will be discarded from
* the ringbuffer.
*/
uint64_t *read_max;
/* Data that was read from the ringbuffer. If "read_max" is 0 on return
* "data" is invalid.
*/
char *data;
/* If a console log file was specified this flag indicates whether the
* contents of the ringbuffer should be written to the logfile when a
* request is sent to the ringbuffer.
*/
bool write_logfile;
};
/*!
* \brief Create a new container.
*
......
......@@ -904,7 +904,7 @@ static int do_start(void *data)
}
/* Setup the container, ip, names, utsname, ... */
ret = lxc_setup_child(handler);
ret = lxc_setup(handler);
close(handler->data_sock[0]);
close(handler->data_sock[1]);
if (ret < 0) {
......@@ -1266,10 +1266,6 @@ static int lxc_spawn(struct lxc_handler *handler)
flags &= ~CLONE_NEWNET;
}
ret = lxc_setup_parent(handler);
if (ret < 0)
goto out_delete_net;
if (fork_before_clone)
handler->pid = lxc_fork_attach_clone(do_start, handler, flags | CLONE_PARENT);
else
......
......@@ -15,6 +15,7 @@ lxc_test_lxcpath_SOURCES = lxcpath.c
lxc_test_cgpath_SOURCES = cgpath.c
lxc_test_clonetest_SOURCES = clonetest.c
lxc_test_console_SOURCES = console.c
lxc_test_console_log_SOURCES = console_log.c lxctest.h
lxc_test_snapshot_SOURCES = snapshot.c
lxc_test_concurrent_SOURCES = concurrent.c
lxc_test_may_control_SOURCES = may_control.c
......@@ -52,7 +53,7 @@ endif
bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
lxc-test-destroytest lxc-test-saveconfig lxc-test-createtest \
lxc-test-shutdowntest lxc-test-get_item lxc-test-getkeys lxc-test-lxcpath \
lxc-test-cgpath lxc-test-clonetest lxc-test-console \
lxc-test-cgpath lxc-test-clonetest lxc-test-console lxc-test-console-log \
lxc-test-snapshot lxc-test-concurrent lxc-test-may-control \
lxc-test-reboot lxc-test-list lxc-test-attach lxc-test-device-add-remove \
lxc-test-apparmor lxc-test-utils lxc-test-parse-config-file \
......@@ -85,6 +86,7 @@ EXTRA_DIST = \
concurrent.c \
config_jump_table.c \
console.c \
console_log.c \
containertests.c \
createtest.c \
destroytest.c \
......
/* liblxcapi
*
* Copyright © 2017 Christian Brauner <christian.brauner@ubuntu.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define __STDC_FORMAT_MACROS
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <lxc/lxccontainer.h>
#include "lxctest.h"
#include "utils.h"
int main(int argc, char *argv[])
{
int logfd, ret;
char buf[4096 + 1];
ssize_t bytes;
struct stat st;
struct lxc_container *c;
struct lxc_console_log log;
bool do_unlink = false;
int fret = EXIT_FAILURE;
c = lxc_container_new("console-log", NULL);
if (!c) {
lxc_error("%s", "Failed to create container \"console-log\"");
exit(fret);
}
if (c->is_defined(c)) {
lxc_error("%s\n", "Container \"console-log\" is defined");
goto on_error_put;
}
/* Set console ringbuffer size. */
if (!c->set_config_item(c, "lxc.console.logsize", "4096")) {
lxc_error("%s\n", "Failed to set config item \"lxc.console.logsize\"");
goto on_error_put;
}
/* Set on-disk logfile. */
if (!c->set_config_item(c, "lxc.console.logfile", "/tmp/console-log.log")) {
lxc_error("%s\n", "Failed to set config item \"lxc.console.logfile\"");
goto on_error_put;
}
if (!c->createl(c, "busybox", NULL, NULL, 0, NULL)) {
lxc_error("%s\n", "Failed to create busybox container \"console-log\"");
goto on_error_put;
}
if (!c->is_defined(c)) {
lxc_error("%s\n", "Container \"console-log\" is not defined");
goto on_error_put;
}
c->clear_config(c);
if (!c->load_config(c, NULL)) {
lxc_error("%s\n", "Failed to load config for container \"console-log\"");
goto on_error_stop;
}
if (!c->want_daemonize(c, true)) {
lxc_error("%s\n", "Failed to mark container \"console-log\" daemonized");
goto on_error_stop;
}
if (!c->startl(c, 0, NULL)) {
lxc_error("%s\n", "Failed to start container \"console-log\" daemonized");
goto on_error_stop;
}
/* Leave some time for the container to write something to the log. */
sleep(2);
/* Retrieve the contents of the ringbuffer. */
log.clear = false;
log.read_max = &(uint64_t){0};
log.read = true;
log.write_logfile = false;
ret = c->console_log(c, &log);
if (ret < 0) {
lxc_error("%s - Failed to retrieve console log \n", strerror(-ret));
goto on_error_stop;
} else {
lxc_debug("Retrieved %" PRIu64
" bytes from console log. Contents are \"%s\"\n",
*log.read_max, log.data);
}
/* Leave another two seconds to ensure boot is finished. */
sleep(2);
/* Clear the console ringbuffer. */
log.read_max = &(uint64_t){0};
log.read = false;
log.write_logfile = false;
log.clear = true;
ret = c->console_log(c, &log);
if (ret < 0) {
if (ret != -ENODATA) {
lxc_error("%s - Failed to retrieve console log\n", strerror(-ret));
goto on_error_stop;
}
}
if (!c->stop(c)) {
lxc_error("%s\n", "Failed to stop container \"console-log\"");
goto on_error_stop;
}
if (!c->startl(c, 0, NULL)) {
lxc_error("%s\n", "Failed to start container \"console-log\" daemonized");
goto on_error_destroy;
}
do_unlink = true;
/* Leave some time for the container to write something to the log. */
sleep(2);
log.read_max = &(uint64_t){0};
log.read = true;
log.write_logfile = true;
log.clear = false;
ret = c->console_log(c, &log);
if (ret < 0) {
lxc_error("%s - Failed to retrieve console log \n", strerror(-ret));
goto on_error_stop;
} else {
lxc_debug("Retrieved %" PRIu64
" bytes from console log. Contents are \"%s\"\n",
*log.read_max, log.data);
}
logfd = open("/tmp/console-log.log", O_RDONLY);
if (logfd < 0) {
lxc_error("%s - Failed to open console log file "
"\"/tmp/console-log.log\"\n", strerror(errno));
goto on_error_stop;
}
bytes = lxc_read_nointr(logfd, buf, 4096 + 1);
close(logfd);
if (bytes < 0 || ((uint64_t)bytes != *log.read_max)) {
lxc_error("%s - Failed to read console log file "
"\"/tmp/console-log.log\"\n", strerror(errno));
goto on_error_stop;
}
ret = stat("/tmp/console-log.log", &st);
if (ret < 0) {
lxc_error("%s - Failed to stat on-disk logfile\n", strerror(errno));
goto on_error_stop;
}
if ((uint64_t)st.st_size != *log.read_max) {
lxc_error("On-disk logfile size and used ringbuffer size do "
"not match: %" PRIu64 " != %" PRIu64 "\n",
(uint64_t)st.st_size, *log.read_max);
goto on_error_stop;
}
if (memcmp(log.data, buf, *log.read_max)) {
lxc_error("%s - Contents of in-memory ringbuffer and on-disk "
"logfile do not match\n", strerror(errno));
goto on_error_stop;
} else {
lxc_debug("Retrieved %" PRIu64 " bytes from console log and "
"console log file. Contents are: \"%s\" - \"%s\"\n",
*log.read_max, log.data, buf);
}
fret = 0;
on_error_stop:
if (c->is_running(c) && !c->stop(c))
lxc_error("%s\n", "Failed to stop container \"console-log\"");
on_error_destroy:
if (!c->destroy(c))
lxc_error("%s\n", "Failed to destroy container \"console-log\"");
on_error_put:
lxc_container_put(c);
if (do_unlink) {
ret = unlink("/tmp/console-log.log");
if (ret < 0)
lxc_error("%s - Failed to remove container log file\n", strerror(errno));
}
exit(fret);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment