* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdio.h>
+#include "config.h"
+
#include <errno.h>
-#include <unistd.h>
-#include <signal.h>
#include <fcntl.h>
+#include <malloc.h>
#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>
-#include <sys/param.h>
-#include <malloc.h>
-#include <stdlib.h>
-#include "log.h"
-#include "lxc.h"
-#include "conf.h"
-#include "start.h" /* for struct lxc_handler */
-#include "utils.h"
+#include "af_unix.h"
#include "cgroup.h"
#include "commands.h"
#include "commands_utils.h"
-#include "console.h"
+#include "conf.h"
#include "confile.h"
+#include "console.h"
+#include "log.h"
+#include "lxc.h"
#include "lxclock.h"
#include "mainloop.h"
#include "monitor.h"
-#include "af_unix.h"
-#include "config.h"
+#include "start.h"
+#include "utils.h"
/*
- * This file provides the different functions for clients to
- * query/command the server. The client is typically some lxc
- * tool and the server is typically the container (ie. lxc-start).
+ * This file provides the different functions for clients to query/command the
+ * server. The client is typically some lxc tool and the server is typically the
+ * container (ie. lxc-start).
*
- * Each command is transactional, the clients send a request to
- * the server and the server answers the request with a message
- * giving the request's status (zero or a negative errno value).
- * Both the request and response may contain additional data.
+ * Each command is transactional, the clients send a request to the server and
+ * the server answers the request with a message giving the request's status
+ * (zero or a negative errno value). Both the request and response may contain
+ * additional data.
*
- * Each command is wrapped in a ancillary message in order to pass
- * a credential making possible to the server to check if the client
- * is allowed to ask for this command or not.
+ * Each command is wrapped in a ancillary message in order to pass a credential
+ * making possible to the server to check if the client is allowed to ask for
+ * this command or not.
*
- * IMPORTANTLY: Note that semantics for current commands are fixed. If you
- * wish to make any changes to how, say, LXC_CMD_GET_CONFIG_ITEM works by
- * adding information to the end of cmd.data, then you must introduce a new
- * LXC_CMD_GET_CONFIG_ITEM_V2 define with a new number. You may wish to
- * also mark LXC_CMD_GET_CONFIG_ITEM deprecated in commands.h.
+ * IMPORTANTLY: Note that semantics for current commands are fixed. If you wish
+ * to make any changes to how, say, LXC_CMD_GET_CONFIG_ITEM works by adding
+ * information to the end of cmd.data, then you must introduce a new
+ * LXC_CMD_GET_CONFIG_ITEM_V2 define with a new number. You may wish to also
+ * mark LXC_CMD_GET_CONFIG_ITEM deprecated in commands.h.
*
* This is necessary in order to avoid having a newly compiled lxc command
* communicating with a running (old) monitor from crashing the running
static const char *lxc_cmd_str(lxc_cmd_t cmd)
{
- static const char * const cmdname[LXC_CMD_MAX] = {
+ static const char *const cmdname[LXC_CMD_MAX] = {
[LXC_CMD_CONSOLE] = "console",
[LXC_CMD_CONSOLE_WINCH] = "console_winch",
[LXC_CMD_STOP] = "stop",
[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)
return "Unknown cmd";
+
return cmdname[cmd];
}
ret = lxc_abstract_unix_recv_fds(sock, &rspfd, 1, rsp, sizeof(*rsp));
if (ret < 0) {
- WARN("Command %s failed to receive response: %s.",
- lxc_cmd_str(cmd->req.cmd), strerror(errno));
+ WARN("%s - Failed to receive response for command \"%s\"",
+ strerror(errno), lxc_cmd_str(cmd->req.cmd));
return -1;
}
- TRACE("Command \"%s received response", lxc_cmd_str(cmd->req.cmd));
+ TRACE("Command \"%s\" received response", lxc_cmd_str(cmd->req.cmd));
if (cmd->req.cmd == LXC_CMD_CONSOLE) {
struct lxc_cmd_console_rsp_data *rspdata;
rspdata = malloc(sizeof(*rspdata));
if (!rspdata) {
- ERROR("Command %s couldn't allocate response buffer.",
+ ERROR("Failed to allocate response buffer for command \"%s\"",
lxc_cmd_str(cmd->req.cmd));
- return -1;
+ return -ENOMEM;
}
rspdata->masterfd = rspfd;
rspdata->ttynum = PTR_TO_INT(rsp->data);
}
if (rsp->datalen == 0) {
- DEBUG("command %s response data length is 0",
+ DEBUG("Response data length for command \"%s\" is 0",
lxc_cmd_str(cmd->req.cmd));
return ret;
}
- if (rsp->datalen > LXC_CMD_DATA_MAX) {
- ERROR("Command %s response data %d too long.",
- lxc_cmd_str(cmd->req.cmd), rsp->datalen);
+
+ if ((rsp->datalen > LXC_CMD_DATA_MAX) &&
+ (cmd->req.cmd != LXC_CMD_CONSOLE_LOG)) {
errno = EFBIG;
- return -1;
+ ERROR("%s - Response data for command \"%s\" is too long: %d "
+ "bytes > %d", strerror(errno), lxc_cmd_str(cmd->req.cmd),
+ rsp->datalen, LXC_CMD_DATA_MAX);
+ 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) {
- ERROR("Command %s was unable to allocate response buffer.",
- lxc_cmd_str(cmd->req.cmd));
- return -1;
+ errno = ENOMEM;
+ ERROR("%s - Failed to allocate response buffer for command "
+ "\"%s\"", strerror(errno), lxc_cmd_str(cmd->req.cmd));
+ return -ENOMEM;
}
+
ret = recv(sock, rsp->data, rsp->datalen, 0);
if (ret != rsp->datalen) {
- ERROR("Command %s failed to receive response data: %s.",
+ ERROR("%s - Failed to receive response data for command \"%s\"",
lxc_cmd_str(cmd->req.cmd), strerror(errno));
if (ret >= 0)
ret = -1;
*/
static int lxc_cmd_rsp_send(int fd, struct lxc_cmd_rsp *rsp)
{
- int ret;
+ ssize_t ret;
ret = send(fd, rsp, sizeof(*rsp), 0);
- if (ret != sizeof(*rsp)) {
- ERROR("Failed to send command response %d: %s.", ret,
- strerror(errno));
+ if (ret < 0 || (size_t)ret != sizeof(*rsp)) {
+ ERROR("%s - Failed to send command response %zd",
+ strerror(errno), ret);
return -1;
}
- if (rsp->datalen > 0) {
- ret = send(fd, rsp->data, rsp->datalen, 0);
- if (ret != rsp->datalen) {
- WARN("Failed to send command response data %d: %s.",
- ret, strerror(errno));
- return -1;
- }
+ if (rsp->datalen <= 0)
+ return 0;
+
+ ret = send(fd, rsp->data, rsp->datalen, 0);
+ if (ret < 0 || ret != (ssize_t)rsp->datalen) {
+ WARN("%s - Failed to send command response data %zd",
+ strerror(errno), ret);
+ return -1;
}
+
return 0;
}
static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd,
- const char *lxcpath, const char *hashed_sock_name)
+ const char *lxcpath, const char *hashed_sock_name)
{
int client_fd;
- int ret = -1;
+ ssize_t ret = -1;
client_fd = lxc_cmd_connect(name, lxcpath, hashed_sock_name);
- if (client_fd < 0 && client_fd == -ECONNREFUSED)
- return -ECONNREFUSED;
- else if (client_fd < 0)
- return -1;
+ if (client_fd < 0) {
+ if (client_fd == -ECONNREFUSED)
+ return -ECONNREFUSED;
- TRACE("Command \"%s\" connected to command socket",
- lxc_cmd_str(cmd->req.cmd));
+ return -1;
+ }
- ret = lxc_abstract_unix_send_credential(client_fd, &cmd->req, sizeof(cmd->req));
- if (ret != sizeof(cmd->req)) {
+ ret = lxc_abstract_unix_send_credential(client_fd, &cmd->req,
+ sizeof(cmd->req));
+ if (ret < 0 || (size_t)ret != sizeof(cmd->req)) {
close(client_fd);
if (errno == EPIPE)
return -1;
}
- TRACE("Command \"%s\" requested data of length %d",
- lxc_cmd_str(cmd->req.cmd), cmd->req.datalen);
+ if (cmd->req.datalen <= 0)
+ return client_fd;
- if (cmd->req.datalen > 0) {
- ret = send(client_fd, cmd->req.data, cmd->req.datalen, MSG_NOSIGNAL);
- if (ret != cmd->req.datalen) {
- close(client_fd);
+ ret = send(client_fd, cmd->req.data, cmd->req.datalen, MSG_NOSIGNAL);
+ if (ret < 0 || ret != (ssize_t)cmd->req.datalen) {
+ close(client_fd);
- if (errno == EPIPE)
- return -EPIPE;
+ if (errno == EPIPE)
+ return -EPIPE;
- if (ret >= 0)
- return -EMSGSIZE;
+ if (ret >= 0)
+ return -EMSGSIZE;
- return -1;
- }
+ return -1;
}
return client_fd;
static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
const char *lxcpath, const char *hashed_sock_name)
{
- int client_fd, ret = -1;
+ int client_fd;
+ int ret = -1;
bool stay_connected = false;
if (cmd->req.cmd == LXC_CMD_CONSOLE ||
*stopped = 0;
- TRACE("command %s tries to connect command socket",
- lxc_cmd_str(cmd->req.cmd));
-
client_fd = lxc_cmd_send(name, cmd, lxcpath, hashed_sock_name);
if (client_fd < 0) {
- TRACE("command %s failed to connect command socket: %s",
- lxc_cmd_str(cmd->req.cmd), strerror(errno));
+ TRACE("%s - Command \"%s\" failed to connect command socket",
+ strerror(errno), lxc_cmd_str(cmd->req.cmd));
if (client_fd == -ECONNREFUSED) {
*stopped = 1;
return -1;
out:
if (!stay_connected || ret <= 0)
close(client_fd);
+
if (stay_connected && ret > 0)
cmd->rsp.ret = client_fd;
};
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
-
if (stopped)
return 0;
if (ret > 0 && cmd.rsp.ret < 0) {
if (ret > 0)
return 0;
- /*
- * At this point we weren't denied access, and the
- * container *was* started. There was some inexplicable
- * error in the protocol.
- * I'm not clear on whether we should return -1 here, but
- * we didn't receive a -EACCES, so technically it's not that
- * we're not allowed to control the container - it's just not
- * behaving.
+ /* At this point we weren't denied access, and the container *was*
+ * started. There was some inexplicable error in the protocol. I'm not
+ * clear on whether we should return -1 here, but we didn't receive a
+ * -EACCES, so technically it's not that we're not allowed to control
+ * the container - it's just not behaving.
*/
return 0;
}
* returned path.
*/
char *lxc_cmd_get_cgroup_path(const char *name, const char *lxcpath,
- const char *subsystem)
+ const char *subsystem)
{
int ret, stopped;
struct lxc_cmd_rr cmd = {
.req = {
.cmd = LXC_CMD_GET_CGROUP,
- .datalen = strlen(subsystem)+1,
+ .datalen = strlen(subsystem) + 1,
.data = subsystem,
},
};
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
- if (ret < 0) {
- TRACE("command %s failed for container \"%s\": %s.",
- lxc_cmd_str(cmd.req.cmd), name, strerror(errno));
+ if (ret < 0)
return NULL;
- }
- if (!ret) {
- WARN("container \"%s\" has stopped before sending its state", name);
+ if (ret == 0)
return NULL;
- }
- if (cmd.rsp.ret < 0 || cmd.rsp.datalen < 0) {
- ERROR("command %s failed for container \"%s\": %s",
- lxc_cmd_str(cmd.req.cmd), name, strerror(-cmd.rsp.ret));
+ if (cmd.rsp.ret < 0 || cmd.rsp.datalen < 0)
return NULL;
- }
-
- TRACE("command %s successful for container \"%s\"",
- lxc_cmd_str(cmd.req.cmd), name);
return cmd.rsp.data;
}
static int lxc_cmd_get_cgroup_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler)
{
- struct lxc_cmd_rsp rsp;
const char *path;
+ struct lxc_cmd_rsp rsp;
if (req->datalen < 1)
return -1;
path = cgroup_get_cgroup(handler, req->data);
if (!path)
return -1;
- rsp.datalen = strlen(path) + 1,
- rsp.data = (char *)path;
+
rsp.ret = 0;
+ rsp.datalen = strlen(path) + 1;
+ rsp.data = (char *)path;
return lxc_cmd_rsp_send(fd, &rsp);
}
struct lxc_cmd_rr cmd = {
.req = { .cmd = LXC_CMD_GET_CONFIG_ITEM,
.data = item,
- .datalen = strlen(item)+1,
+ .datalen = strlen(item) + 1,
},
};
if (cmd.rsp.ret == 0)
return cmd.rsp.data;
+
return NULL;
}
struct lxc_handler *handler)
{
int cilen;
- struct lxc_cmd_rsp rsp;
char *cidata;
struct lxc_config_t *item;
+ struct lxc_cmd_rsp rsp;
memset(&rsp, 0, sizeof(rsp));
item = lxc_get_config(req->data);
if (!item)
goto err1;
+
cilen = item->get(req->data, NULL, 0, handler->conf, NULL);
if (cilen <= 0)
goto err1;
cidata = alloca(cilen + 1);
if (item->get(req->data, cidata, cilen + 1, handler->conf, NULL) != cilen)
goto err1;
+
cidata[cilen] = '\0';
rsp.data = cidata;
rsp.datalen = cilen + 1;
memset(&rsp, 0, sizeof(rsp));
rsp.ret = lxc_set_config_item_locked(handler->conf, key, value);
+
return lxc_cmd_rsp_send(fd, &rsp);
}
return -1;
if (!ret) {
- WARN("Container \"%s\" has stopped before sending its state.", name);
+ WARN("Container \"%s\" has stopped before sending its state", name);
return -1;
}
- DEBUG("Container \"%s\" is in \"%s\" state.", name,
+ DEBUG("Container \"%s\" is in \"%s\" state", name,
lxc_state2str(PTR_TO_INT(cmd.rsp.data)));
+
return PTR_TO_INT(cmd.rsp.data);
}
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
if (ret < 0) {
if (stopped) {
- INFO("Container \"%s\" is already stopped.", name);
+ INFO("Container \"%s\" is already stopped", name);
return 0;
}
+
return -1;
}
- /* we do not expect any answer, because we wait for the connection to be
- * closed
+ /* We do not expect any answer, because we wait for the connection to be
+ * closed.
*/
if (ret > 0) {
- ERROR("Failed to stop container \"%s\": %s.", name,
- strerror(-cmd.rsp.ret));
+ ERROR("%s - Failed to stop container \"%s\"",
+ strerror(-cmd.rsp.ret), name);
return -1;
}
- INFO("Container \"%s\" has stopped.", name);
+ INFO("Container \"%s\" has stopped", name);
return 0;
}
memset(&rsp, 0, sizeof(rsp));
rsp.ret = kill(handler->pid, stopsignal);
if (!rsp.ret) {
- /* we can't just use lxc_unfreeze() since we are already in the
+ /* We can't just use lxc_unfreeze() since we are already in the
* context of handling the STOP cmd in lxc-start, and calling
* lxc_unfreeze() would do another cmd (GET_CGROUP) which would
- * deadlock us
+ * deadlock us.
*/
if (cgroup_unfreeze(handler))
return 0;
- ERROR("Failed to unfreeze container \"%s\".", handler->name);
+
+ ERROR("Failed to unfreeze container \"%s\"", handler->name);
rsp.ret = -1;
}
struct lxc_cmd_rsp rsp = { .data = 0 };
lxc_console_sigwinch(SIGWINCH);
+
return lxc_cmd_rsp_send(fd, &rsp);
}
return ret;
if (cmd.rsp.ret < 0) {
- ERROR("Console access denied: %s.", strerror(-cmd.rsp.ret));
+ ERROR("%s - Denied access to tty", strerror(-cmd.rsp.ret));
ret = -1;
goto out;
}
if (ret == 0) {
- ERROR("Console %d invalid, busy or all consoles busy.", *ttynum);
+ ERROR("tty number %d invalid, busy or all ttys busy", *ttynum);
ret = -1;
goto out;
}
rspdata = cmd.rsp.data;
if (rspdata->masterfd < 0) {
- ERROR("Unable to allocate fd for tty %d.", rspdata->ttynum);
+ ERROR("Unable to allocate fd for tty %d", rspdata->ttynum);
goto out;
}
- ret = cmd.rsp.ret; /* sock fd */
+ ret = cmd.rsp.ret; /* socket fd */
*fd = rspdata->masterfd;
*ttynum = rspdata->ttynum;
- INFO("tty %d allocated fd %d sock %d.", rspdata->ttynum, *fd, ret);
+ INFO("Alloced fd %d for tty %d via socket %d", *fd, rspdata->ttynum, ret);
+
out:
free(cmd.rsp.data);
return ret;
static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler)
{
- int ttynum = PTR_TO_INT(req->data);
- int masterfd;
+ int masterfd, ret;
struct lxc_cmd_rsp rsp;
+ int ttynum = PTR_TO_INT(req->data);
masterfd = lxc_console_allocate(handler->conf, fd, &ttynum);
if (masterfd < 0)
memset(&rsp, 0, sizeof(rsp));
rsp.data = INT_TO_PTR(ttynum);
- if (lxc_abstract_unix_send_fds(fd, &masterfd, 1, &rsp, sizeof(rsp)) < 0) {
- ERROR("Failed to send tty to client.");
+ ret = lxc_abstract_unix_send_fds(fd, &masterfd, 1, &rsp, sizeof(rsp));
+ if (ret < 0) {
+ ERROR("Failed to send tty to client");
lxc_console_free(handler->conf, fd);
goto out_close;
}
return 0;
out_close:
- /* special indicator to lxc_cmd_handler() to close the fd and do
- * related cleanup
+ /* Special indicator to lxc_cmd_handler() to close the fd and do
+ * related cleanup.
*/
return 1;
}
};
ret = lxc_cmd(NULL, &cmd, &stopped, NULL, hashed_sock_name);
- if (ret < 0) {
+ if (ret < 0)
return NULL;
- }
if (cmd.rsp.ret == 0)
return cmd.rsp.data;
+
return NULL;
}
static int lxc_cmd_get_name_callback(int fd, struct lxc_cmd_req *req,
- struct lxc_handler *handler)
+ struct lxc_handler *handler)
{
struct lxc_cmd_rsp rsp;
};
ret = lxc_cmd(NULL, &cmd, &stopped, NULL, hashed_sock_name);
- if (ret < 0) {
+ if (ret < 0)
return NULL;
- }
if (cmd.rsp.ret == 0)
return cmd.rsp.data;
+
return NULL;
}
memset(&rsp, 0, sizeof(rsp));
+ rsp.ret = 0;
rsp.data = (char *)handler->lxcpath;
rsp.datalen = strlen(handler->lxcpath) + 1;
- rsp.ret = 0;
return lxc_cmd_rsp_send(fd, &rsp);
}
state = lxc_getstate(name, lxcpath);
if (state < 0) {
process_unlock();
- TRACE("failed to retrieve state of container: %s",
- strerror(errno));
+ TRACE("%s - Failed to retrieve state of container", strerror(errno));
return -1;
} else if (states[state]) {
process_unlock();
- TRACE("container is %s state", lxc_state2str(state));
+ TRACE("Container is %s state", lxc_state2str(state));
return state;
}
if ((state == STARTING) && !states[RUNNING] && !states[STOPPING] && !states[STOPPED]) {
process_unlock();
- TRACE("container is in %s state and caller requested to be "
- "informed about a previous state",
- lxc_state2str(state));
+ TRACE("Container is in %s state and caller requested to be "
+ "informed about a previous state", lxc_state2str(state));
return state;
} else if ((state == RUNNING) && !states[STOPPING] && !states[STOPPED]) {
process_unlock();
- TRACE("container is in %s state and caller requested to be "
- "informed about a previous state",
- lxc_state2str(state));
+ TRACE("Container is in %s state and caller requested to be "
+ "informed about a previous state", lxc_state2str(state));
return state;
} else if ((state == STOPPING) && !states[STOPPED]) {
process_unlock();
- TRACE("container is in %s state and caller requested to be "
- "informed about a previous state",
- lxc_state2str(state));
+ TRACE("Container is in %s state and caller requested to be "
+ "informed about a previous state", lxc_state2str(state));
return state;
} else if ((state == STOPPED) || (state == ABORTING)) {
process_unlock();
- TRACE("container is in %s state and caller requested to be "
- "informed about a previous state",
- lxc_state2str(state));
+ TRACE("Container is in %s state and caller requested to be "
+ "informed about a previous state", lxc_state2str(state));
return state;
}
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
process_unlock();
if (ret < 0) {
- ERROR("failed to execute command: %s", strerror(errno));
+ ERROR("%s - Failed to execute command", strerror(errno));
return -1;
}
+
/* We should now be guaranteed to get an answer from the state sending
* function.
*/
if (cmd.rsp.ret < 0) {
- ERROR("failed to receive socket fd");
+ ERROR("Failed to receive socket fd");
return -1;
}
{
struct lxc_cmd_rsp rsp = {0};
- if (req->datalen < 0) {
- TRACE("Requested datalen was < 0");
+ if (req->datalen < 0)
return -1;
- }
- if (req->datalen > (sizeof(lxc_state_t) * MAX_STATE)) {
- TRACE("Requested datalen was too large");
+ if (req->datalen > (sizeof(lxc_state_t) * MAX_STATE))
return -1;
- }
- if (!req->data) {
- TRACE("No states requested");
+ if (!req->data)
return -1;
- }
rsp.ret = lxc_add_state_client(fd, handler, (lxc_state_t *)req->data);
if (rsp.ret < 0)
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;
+ data.write_logfile = log->write_logfile;
+
+ 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 || cmd.rsp.ret == -ENOENT) {
+ *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_console *console = &handler->conf->console;
+ 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 */
+ rsp.ret = -ENODATA;
+ if (log->read && (buf->r_off == buf->w_off))
+ goto out;
+
+ if (log->write_logfile && rsp.datalen > 0) {
+ rsp.ret = -ENOENT;
+ if (!console->log_path)
+ goto out;
+
+ rsp.ret = lxc_console_write_ringbuffer(console);
+ if (rsp.ret < 0)
+ goto out;
+ }
+
+ 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)
{
[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) {
- ERROR("Undefined command id %d received.", req->cmd);
+ ERROR("Undefined command id %d", req->cmd);
return -1;
}
return cb[req->cmd](fd, req, handler);
{
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));
if (ret == -EACCES) {
- /* we don't care for the peer, just send and close */
- struct lxc_cmd_rsp rsp = { .ret = ret };
+ /* We don't care for the peer, just send and close. */
+ struct lxc_cmd_rsp rsp = {.ret = ret};
lxc_cmd_rsp_send(fd, &rsp);
goto out_close;
}
- TRACE("Processing \"%s\" command", lxc_cmd_str(req.cmd));
if (ret < 0) {
- SYSERROR("Failed to receive data on command socket for \"%s\"",
- lxc_cmd_str(req.cmd));
+ SYSERROR("Failed to receive data on command socket for command "
+ "\"%s\"", lxc_cmd_str(req.cmd));
goto out_close;
}
- if (!ret) {
- DEBUG("Peer has disconnected for \"%s\"", lxc_cmd_str(req.cmd));
+ if (ret == 0)
goto out_close;
- }
if (ret != sizeof(req)) {
WARN("Failed to receive full command request. Ignoring request "
- "for \"%s\"",
- lxc_cmd_str(req.cmd));
+ "for \"%s\"", lxc_cmd_str(req.cmd));
ret = -1;
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));
- ret = -1;
+ "command \"%s\"", req.datalen, lxc_cmd_str(req.cmd));
+ errno = EFBIG;
+ ret = -EFBIG;
goto out_close;
}
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 "
- "request for \"%s\"",
- lxc_cmd_str(req.cmd));
+ "request for \"%s\"", lxc_cmd_str(req.cmd));
ret = -1;
goto out_close;
}
+
req.data = reqdata;
}
ret = lxc_cmd_process(fd, &req, handler);
if (ret) {
- /* this is not an error, but only a request to close fd */
+ /* This is not an error, but only a request to close fd. */
ret = 0;
goto out_close;
}
out:
+ if (req.cmd == LXC_CMD_CONSOLE_LOG && reqdata)
+ free(reqdata);
+
return ret;
+
out_close:
lxc_cmd_fd_cleanup(fd, handler, descr);
goto out;
static int lxc_cmd_accept(int fd, uint32_t events, void *data,
struct lxc_epoll_descr *descr)
{
- int opt = 1, ret = -1, connection;
+ int connection;
+ int opt = 1, ret = -1;
connection = accept(fd, NULL, 0);
if (connection < 0) {
return -1;
}
- if (fcntl(connection, F_SETFD, FD_CLOEXEC)) {
- SYSERROR("Failed to set close-on-exec on incoming command connection.");
+ ret = fcntl(connection, F_SETFD, FD_CLOEXEC);
+ if (ret < 0) {
+ SYSERROR("Failed to set close-on-exec on incoming command connection");
goto out_close;
}
- if (setsockopt(connection, SOL_SOCKET,
- SO_PASSCRED, &opt, sizeof(opt))) {
- SYSERROR("Failed to enable necessary credentials on command socket.");
+ ret = setsockopt(connection, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt));
+ if (ret < 0) {
+ SYSERROR("Failed to enable necessary credentials on command socket");
goto out_close;
}
ret = lxc_mainloop_add_handler(descr, connection, lxc_cmd_handler, data);
if (ret) {
- ERROR("Failed to add command handler.");
+ ERROR("Failed to add command handler");
goto out_close;
}
int lxc_cmd_init(const char *name, struct lxc_handler *handler,
const char *lxcpath)
{
- int fd;
- char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = { 0 };
+ int fd, len, ret;
+ char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = {0};
char *offset = &path[1];
- int len;
/* -2 here because this is an abstract unix socket so it needs a
* leading \0, and we null terminate, so it needs a trailing \0.
* because we print the sockname out sometimes.
*/
len = sizeof(path) - 2;
- if (lxc_make_abstract_socket_name(offset, len, name, lxcpath, NULL,
- "command"))
+ ret = lxc_make_abstract_socket_name(offset, len, name, lxcpath, NULL, "command");
+ if (ret < 0)
return -1;
fd = lxc_abstract_unix_open(path, SOCK_STREAM, 0);
if (fd < 0) {
- ERROR("Failed to create the command service point %s: %s.",
- offset, strerror(errno));
+ ERROR("%s - Failed to create command socket %s",
+ strerror(errno), offset);
if (errno == EADDRINUSE)
- ERROR("Container \"%s\" appears to be already running!", name);
+ ERROR("Container \"%s\" appears to be already running", name);
return -1;
}
- if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
- SYSERROR("Failed to set FD_CLOEXEC on signal file descriptor.");
+ ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
+ if (ret < 0) {
+ SYSERROR("Failed to set FD_CLOEXEC on command socket file descriptor");
close(fd);
return -1;
}
return 0;
}
-int lxc_cmd_mainloop_add(const char *name,
- struct lxc_epoll_descr *descr,
+int lxc_cmd_mainloop_add(const char *name, struct lxc_epoll_descr *descr,
struct lxc_handler *handler)
{
- int ret, fd = handler->conf->maincmd_fd;
+ int ret;
+ int fd = handler->conf->maincmd_fd;
ret = lxc_mainloop_add_handler(descr, fd, lxc_cmd_accept, handler);
- if (ret) {
- ERROR("Failed to add handler for command socket.");
+ if (ret < 0) {
+ ERROR("Failed to add handler for command socket");
close(fd);
}
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;
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)
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"
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;
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;
--- /dev/null
+/* 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);
+}