From: Christian Brauner Date: Sun, 22 Oct 2017 20:14:49 +0000 (+0200) Subject: lxccontainer: add console_log() API extension X-Git-Tag: lxc-3.0.0.beta1~195^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=191d43cca94ec7141c0f160715c1103dda969ec2;p=thirdparty%2Flxc.git lxccontainer: add console_log() API extension commands: add LXC_CMD_CONSOLE_LOG Closes #1870. Signed-off-by: Christian Brauner --- diff --git a/src/lxc/commands.c b/src/lxc/commands.c index 8bc8d696f..36570714f 100644 --- a/src/lxc/commands.c +++ b/src/lxc/commands.c @@ -92,6 +92,7 @@ static const char *lxc_cmd_str(lxc_cmd_t cmd) [LXC_CMD_GET_LXCPATH] = "get_lxcpath", [LXC_CMD_ADD_STATE_CLIENT] = "add_state_client", [LXC_CMD_SET_CONFIG_ITEM] = "set_config_item", + [LXC_CMD_CONSOLE_LOG] = "console_log", }; if (cmd >= LXC_CMD_MAX) @@ -156,7 +157,8 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) return ret; } - if (rsp->datalen > LXC_CMD_DATA_MAX) { + if ((rsp->datalen > LXC_CMD_DATA_MAX) && + (cmd->req.cmd != LXC_CMD_CONSOLE_LOG)) { errno = EFBIG; ERROR("%s - Response data for command \"%s\" is too long: %d " "bytes > %d", strerror(errno), lxc_cmd_str(cmd->req.cmd), @@ -164,7 +166,12 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) return -EFBIG; } - rsp->data = malloc(rsp->datalen); + if (cmd->req.cmd == LXC_CMD_CONSOLE_LOG) { + rsp->data = malloc(rsp->datalen + 1); + ((char *)rsp->data)[rsp->datalen] = '\0'; + } else { + rsp->data = malloc(rsp->datalen); + } if (!rsp->data) { errno = ENOMEM; ERROR("%s - Failed to allocate response buffer for command " @@ -977,6 +984,79 @@ static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req, return lxc_cmd_rsp_send(fd, &rsp); } +int lxc_cmd_console_log(const char *name, const char *lxcpath, + struct lxc_console_log *log) +{ + int ret, stopped; + struct lxc_cmd_console_log data; + struct lxc_cmd_rr cmd; + + data.clear = log->clear; + data.read = log->read; + data.read_max = *log->read_max; + + cmd.req.cmd = LXC_CMD_CONSOLE_LOG; + cmd.req.data = &data; + cmd.req.datalen = sizeof(struct lxc_cmd_console_log); + + ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL); + if (ret < 0) + return ret; + + /* There is nothing to be read from the buffer. So clear any values we + * where passed to clearly indicate to the user that nothing went wrong. + */ + if (cmd.rsp.ret == -ENODATA || cmd.rsp.ret == -EFAULT) { + *log->read_max = 0; + log->data = NULL; + } + + /* This is a proper error so don't touch any values we were passed. */ + if (cmd.rsp.ret < 0) + return cmd.rsp.ret; + + *log->read_max = cmd.rsp.datalen; + log->data = cmd.rsp.data; + + return 0; +} + +static int lxc_cmd_console_log_callback(int fd, struct lxc_cmd_req *req, + struct lxc_handler *handler) +{ + struct lxc_cmd_rsp rsp; + uint64_t logsize = handler->conf->console.log_size; + const struct lxc_cmd_console_log *log = req->data; + struct lxc_ringbuf *buf = &handler->conf->console.ringbuf; + + rsp.ret = -EFAULT; + rsp.datalen = 0; + rsp.data = NULL; + if (logsize <= 0) + goto out; + + rsp.datalen = lxc_ringbuf_used(buf); + if (log->read) + rsp.data = lxc_ringbuf_get_read_addr(buf); + + if (log->read_max > 0 && (log->read_max <= rsp.datalen)) + rsp.datalen = log->read_max; + + /* there's nothing to read */ + if (log->read && (buf->r_off == buf->w_off)) + rsp.ret = -ENODATA; + else + rsp.ret = 0; + + if (log->clear) + lxc_ringbuf_clear(buf); + else if (rsp.datalen > 0) + lxc_ringbuf_move_read_addr(buf, rsp.datalen); + +out: + return lxc_cmd_rsp_send(fd, &rsp); +} + static int lxc_cmd_process(int fd, struct lxc_cmd_req *req, struct lxc_handler *handler) { @@ -995,6 +1075,7 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req, [LXC_CMD_GET_LXCPATH] = lxc_cmd_get_lxcpath_callback, [LXC_CMD_ADD_STATE_CLIENT] = lxc_cmd_add_state_client_callback, [LXC_CMD_SET_CONFIG_ITEM] = lxc_cmd_set_config_item_callback, + [LXC_CMD_CONSOLE_LOG] = lxc_cmd_console_log_callback, }; if (req->cmd >= LXC_CMD_MAX) { @@ -1017,6 +1098,7 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, { int ret; struct lxc_cmd_req req; + void *reqdata = NULL; struct lxc_handler *handler = data; ret = lxc_abstract_unix_rcv_credential(fd, &req, sizeof(req)); @@ -1044,7 +1126,8 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, goto out_close; } - if (req.datalen > LXC_CMD_DATA_MAX) { + if ((req.datalen > LXC_CMD_DATA_MAX) && + (req.cmd != LXC_CMD_CONSOLE_LOG)) { ERROR("Received command data length %d is too large for " "command \"%s\"", req.datalen, lxc_cmd_str(req.cmd)); errno = EFBIG; @@ -1053,9 +1136,21 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, } if (req.datalen > 0) { - void *reqdata; + /* LXC_CMD_CONSOLE_LOG needs to be able to allocate data + * that exceeds LXC_CMD_DATA_MAX: use malloc() for that. + */ + if (req.cmd == LXC_CMD_CONSOLE_LOG) + reqdata = malloc(req.datalen); + else + reqdata = alloca(req.datalen); + if (!reqdata) { + ERROR("Failed to allocate memory for \"%s\" command", + lxc_cmd_str(req.cmd)); + errno = ENOMEM; + ret = -ENOMEM; + goto out_close; + } - reqdata = alloca(req.datalen); ret = recv(fd, reqdata, req.datalen, 0); if (ret != req.datalen) { WARN("Failed to receive full command request. Ignoring " @@ -1075,6 +1170,9 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, } out: + if (req.cmd == LXC_CMD_CONSOLE_LOG && reqdata) + free(reqdata); + return ret; out_close: diff --git a/src/lxc/commands.h b/src/lxc/commands.h index cc5eec3c5..416cfded6 100644 --- a/src/lxc/commands.h +++ b/src/lxc/commands.h @@ -29,6 +29,7 @@ #include #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,13 @@ struct lxc_cmd_set_config_item_req_data { void *value; }; +struct lxc_cmd_console_log { + bool clear; + bool read; + uint64_t read_max; + +}; + 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 +133,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 */ diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c index efb1bf7ae..8c0487853 100644 --- a/src/lxc/lxccontainer.c +++ b/src/lxc/lxccontainer.c @@ -516,6 +516,25 @@ 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("%s - The console log is empty", strerror(-ret)); + else if (ret == -EFAULT) + NOTICE("%s - The container does not have a console log configured", strerror(-ret)); + else + ERROR("%s - Failed to retrieve console log", strerror(-ret)); + } + + 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 +4626,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; diff --git a/src/lxc/lxccontainer.h b/src/lxc/lxccontainer.h index 84bdab81c..22ab85278 100644 --- a/src/lxc/lxccontainer.h +++ b/src/lxc/lxccontainer.h @@ -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,28 @@ 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; +}; + /*! * \brief Create a new container. *