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 ...@@ -78,6 +78,7 @@ src/tests/lxc-test-cgpath
src/tests/lxc-test-clonetest src/tests/lxc-test-clonetest
src/tests/lxc-test-concurrent src/tests/lxc-test-concurrent
src/tests/lxc-test-console src/tests/lxc-test-console
src/tests/lxc-test-console-log
src/tests/lxc-test-containertests src/tests/lxc-test-containertests
src/tests/lxc-test-createtest src/tests/lxc-test-createtest
src/tests/lxc-test-destroytest src/tests/lxc-test-destroytest
......
...@@ -21,53 +21,54 @@ ...@@ -21,53 +21,54 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include <stdio.h> #include "config.h"
#include <errno.h> #include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h> #include <fcntl.h>
#include <malloc.h>
#include <poll.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/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/param.h>
#include <malloc.h>
#include <stdlib.h>
#include "log.h" #include "af_unix.h"
#include "lxc.h"
#include "conf.h"
#include "start.h" /* for struct lxc_handler */
#include "utils.h"
#include "cgroup.h" #include "cgroup.h"
#include "commands.h" #include "commands.h"
#include "commands_utils.h" #include "commands_utils.h"
#include "console.h" #include "conf.h"
#include "confile.h" #include "confile.h"
#include "console.h"
#include "log.h"
#include "lxc.h"
#include "lxclock.h" #include "lxclock.h"
#include "mainloop.h" #include "mainloop.h"
#include "monitor.h" #include "monitor.h"
#include "af_unix.h" #include "start.h"
#include "config.h" #include "utils.h"
/* /*
* This file provides the different functions for clients to * This file provides the different functions for clients to query/command the
* query/command the server. The client is typically some lxc * server. The client is typically some lxc tool and the server is typically the
* tool and the server is typically the container (ie. lxc-start). * container (ie. lxc-start).
* *
* Each command is transactional, the clients send a request to * Each command is transactional, the clients send a request to the server and
* the server and the server answers the request with a message * the server answers the request with a message giving the request's status
* giving the request's status (zero or a negative errno value). * (zero or a negative errno value). Both the request and response may contain
* Both the request and response may contain additional data. * additional data.
* *
* Each command is wrapped in a ancillary message in order to pass * Each command is wrapped in a ancillary message in order to pass a credential
* a credential making possible to the server to check if the client * making possible to the server to check if the client is allowed to ask for
* is allowed to ask for this command or not. * this command or not.
* *
* IMPORTANTLY: Note that semantics for current commands are fixed. If you * IMPORTANTLY: Note that semantics for current commands are fixed. If you wish
* wish to make any changes to how, say, LXC_CMD_GET_CONFIG_ITEM works by * to make any changes to how, say, LXC_CMD_GET_CONFIG_ITEM works by adding
* adding information to the end of cmd.data, then you must introduce a new * 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 * LXC_CMD_GET_CONFIG_ITEM_V2 define with a new number. You may wish to also
* also mark LXC_CMD_GET_CONFIG_ITEM deprecated in commands.h. * mark LXC_CMD_GET_CONFIG_ITEM deprecated in commands.h.
* *
* This is necessary in order to avoid having a newly compiled lxc command * This is necessary in order to avoid having a newly compiled lxc command
* communicating with a running (old) monitor from crashing the running * communicating with a running (old) monitor from crashing the running
...@@ -78,7 +79,7 @@ lxc_log_define(lxc_commands, lxc); ...@@ -78,7 +79,7 @@ lxc_log_define(lxc_commands, lxc);
static const char *lxc_cmd_str(lxc_cmd_t cmd) 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] = "console",
[LXC_CMD_CONSOLE_WINCH] = "console_winch", [LXC_CMD_CONSOLE_WINCH] = "console_winch",
[LXC_CMD_STOP] = "stop", [LXC_CMD_STOP] = "stop",
...@@ -91,10 +92,12 @@ static const char *lxc_cmd_str(lxc_cmd_t cmd) ...@@ -91,10 +92,12 @@ static const char *lxc_cmd_str(lxc_cmd_t cmd)
[LXC_CMD_GET_LXCPATH] = "get_lxcpath", [LXC_CMD_GET_LXCPATH] = "get_lxcpath",
[LXC_CMD_ADD_STATE_CLIENT] = "add_state_client", [LXC_CMD_ADD_STATE_CLIENT] = "add_state_client",
[LXC_CMD_SET_CONFIG_ITEM] = "set_config_item", [LXC_CMD_SET_CONFIG_ITEM] = "set_config_item",
[LXC_CMD_CONSOLE_LOG] = "console_log",
}; };
if (cmd >= LXC_CMD_MAX) if (cmd >= LXC_CMD_MAX)
return "Unknown cmd"; return "Unknown cmd";
return cmdname[cmd]; return cmdname[cmd];
} }
...@@ -122,11 +125,11 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) ...@@ -122,11 +125,11 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
ret = lxc_abstract_unix_recv_fds(sock, &rspfd, 1, rsp, sizeof(*rsp)); ret = lxc_abstract_unix_recv_fds(sock, &rspfd, 1, rsp, sizeof(*rsp));
if (ret < 0) { if (ret < 0) {
WARN("Command %s failed to receive response: %s.", WARN("%s - Failed to receive response for command \"%s\"",
lxc_cmd_str(cmd->req.cmd), strerror(errno)); strerror(errno), lxc_cmd_str(cmd->req.cmd));
return -1; 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) { if (cmd->req.cmd == LXC_CMD_CONSOLE) {
struct lxc_cmd_console_rsp_data *rspdata; struct lxc_cmd_console_rsp_data *rspdata;
...@@ -139,9 +142,9 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) ...@@ -139,9 +142,9 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
rspdata = malloc(sizeof(*rspdata)); rspdata = malloc(sizeof(*rspdata));
if (!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)); lxc_cmd_str(cmd->req.cmd));
return -1; return -ENOMEM;
} }
rspdata->masterfd = rspfd; rspdata->masterfd = rspfd;
rspdata->ttynum = PTR_TO_INT(rsp->data); rspdata->ttynum = PTR_TO_INT(rsp->data);
...@@ -149,26 +152,36 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) ...@@ -149,26 +152,36 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
} }
if (rsp->datalen == 0) { 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)); lxc_cmd_str(cmd->req.cmd));
return ret; return ret;
} }
if (rsp->datalen > LXC_CMD_DATA_MAX) {
ERROR("Command %s response data %d too long.", if ((rsp->datalen > LXC_CMD_DATA_MAX) &&
lxc_cmd_str(cmd->req.cmd), rsp->datalen); (cmd->req.cmd != LXC_CMD_CONSOLE_LOG)) {
errno = EFBIG; 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;
} }
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); rsp->data = malloc(rsp->datalen);
}
if (!rsp->data) { if (!rsp->data) {
ERROR("Command %s was unable to allocate response buffer.", errno = ENOMEM;
lxc_cmd_str(cmd->req.cmd)); ERROR("%s - Failed to allocate response buffer for command "
return -1; "\"%s\"", strerror(errno), lxc_cmd_str(cmd->req.cmd));
return -ENOMEM;
} }
ret = recv(sock, rsp->data, rsp->datalen, 0); ret = recv(sock, rsp->data, rsp->datalen, 0);
if (ret != rsp->datalen) { 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)); lxc_cmd_str(cmd->req.cmd), strerror(errno));
if (ret >= 0) if (ret >= 0)
ret = -1; ret = -1;
...@@ -187,23 +200,25 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) ...@@ -187,23 +200,25 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
*/ */
static int lxc_cmd_rsp_send(int fd, struct lxc_cmd_rsp *rsp) 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); ret = send(fd, rsp, sizeof(*rsp), 0);
if (ret != sizeof(*rsp)) { if (ret < 0 || (size_t)ret != sizeof(*rsp)) {
ERROR("Failed to send command response %d: %s.", ret, ERROR("%s - Failed to send command response %zd",
strerror(errno)); strerror(errno), ret);
return -1; return -1;
} }
if (rsp->datalen > 0) { if (rsp->datalen <= 0)
return 0;
ret = send(fd, rsp->data, rsp->datalen, 0); ret = send(fd, rsp->data, rsp->datalen, 0);
if (ret != rsp->datalen) { if (ret < 0 || ret != (ssize_t)rsp->datalen) {
WARN("Failed to send command response data %d: %s.", WARN("%s - Failed to send command response data %zd",
ret, strerror(errno)); strerror(errno), ret);
return -1; return -1;
} }
}
return 0; return 0;
} }
...@@ -211,19 +226,19 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd, ...@@ -211,19 +226,19 @@ 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 client_fd;
int ret = -1; ssize_t ret = -1;
client_fd = lxc_cmd_connect(name, lxcpath, hashed_sock_name); client_fd = lxc_cmd_connect(name, lxcpath, hashed_sock_name);
if (client_fd < 0 && client_fd == -ECONNREFUSED) if (client_fd < 0) {
if (client_fd == -ECONNREFUSED)
return -ECONNREFUSED; return -ECONNREFUSED;
else if (client_fd < 0)
return -1;
TRACE("Command \"%s\" connected to command socket", return -1;
lxc_cmd_str(cmd->req.cmd)); }
ret = lxc_abstract_unix_send_credential(client_fd, &cmd->req, sizeof(cmd->req)); ret = lxc_abstract_unix_send_credential(client_fd, &cmd->req,
if (ret != sizeof(cmd->req)) { sizeof(cmd->req));
if (ret < 0 || (size_t)ret != sizeof(cmd->req)) {
close(client_fd); close(client_fd);
if (errno == EPIPE) if (errno == EPIPE)
...@@ -235,12 +250,11 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd, ...@@ -235,12 +250,11 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd,
return -1; return -1;
} }
TRACE("Command \"%s\" requested data of length %d", if (cmd->req.datalen <= 0)
lxc_cmd_str(cmd->req.cmd), cmd->req.datalen); return client_fd;
if (cmd->req.datalen > 0) {
ret = send(client_fd, cmd->req.data, cmd->req.datalen, MSG_NOSIGNAL); ret = send(client_fd, cmd->req.data, cmd->req.datalen, MSG_NOSIGNAL);
if (ret != cmd->req.datalen) { if (ret < 0 || ret != (ssize_t)cmd->req.datalen) {
close(client_fd); close(client_fd);
if (errno == EPIPE) if (errno == EPIPE)
...@@ -251,7 +265,6 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd, ...@@ -251,7 +265,6 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd,
return -1; return -1;
} }
}
return client_fd; return client_fd;
} }
...@@ -278,7 +291,8 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd, ...@@ -278,7 +291,8 @@ static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd,
static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped, static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
const char *lxcpath, const char *hashed_sock_name) const char *lxcpath, const char *hashed_sock_name)
{ {
int client_fd, ret = -1; int client_fd;
int ret = -1;
bool stay_connected = false; bool stay_connected = false;
if (cmd->req.cmd == LXC_CMD_CONSOLE || if (cmd->req.cmd == LXC_CMD_CONSOLE ||
...@@ -287,13 +301,10 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped, ...@@ -287,13 +301,10 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
*stopped = 0; *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); client_fd = lxc_cmd_send(name, cmd, lxcpath, hashed_sock_name);
if (client_fd < 0) { if (client_fd < 0) {
TRACE("command %s failed to connect command socket: %s", TRACE("%s - Command \"%s\" failed to connect command socket",
lxc_cmd_str(cmd->req.cmd), strerror(errno)); strerror(errno), lxc_cmd_str(cmd->req.cmd));
if (client_fd == -ECONNREFUSED) { if (client_fd == -ECONNREFUSED) {
*stopped = 1; *stopped = 1;
return -1; return -1;
...@@ -309,6 +320,7 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped, ...@@ -309,6 +320,7 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
out: out:
if (!stay_connected || ret <= 0) if (!stay_connected || ret <= 0)
close(client_fd); close(client_fd);
if (stay_connected && ret > 0) if (stay_connected && ret > 0)
cmd->rsp.ret = client_fd; cmd->rsp.ret = client_fd;
...@@ -327,7 +339,6 @@ int lxc_try_cmd(const char *name, const char *lxcpath) ...@@ -327,7 +339,6 @@ int lxc_try_cmd(const char *name, const char *lxcpath)
}; };
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL); ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
if (stopped) if (stopped)
return 0; return 0;
if (ret > 0 && cmd.rsp.ret < 0) { if (ret > 0 && cmd.rsp.ret < 0) {
...@@ -337,14 +348,11 @@ int lxc_try_cmd(const char *name, const char *lxcpath) ...@@ -337,14 +348,11 @@ int lxc_try_cmd(const char *name, const char *lxcpath)
if (ret > 0) if (ret > 0)
return 0; return 0;
/* /* At this point we weren't denied access, and the container *was*
* At this point we weren't denied access, and the * started. There was some inexplicable error in the protocol. I'm not
* container *was* started. There was some inexplicable * clear on whether we should return -1 here, but we didn't receive a
* error in the protocol. * -EACCES, so technically it's not that we're not allowed to control
* I'm not clear on whether we should return -1 here, but * the container - it's just not behaving.
* 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; return 0;
} }
...@@ -430,31 +438,20 @@ char *lxc_cmd_get_cgroup_path(const char *name, const char *lxcpath, ...@@ -430,31 +438,20 @@ char *lxc_cmd_get_cgroup_path(const char *name, const char *lxcpath,
struct lxc_cmd_rr cmd = { struct lxc_cmd_rr cmd = {
.req = { .req = {
.cmd = LXC_CMD_GET_CGROUP, .cmd = LXC_CMD_GET_CGROUP,
.datalen = strlen(subsystem)+1, .datalen = strlen(subsystem) + 1,
.data = subsystem, .data = subsystem,
}, },
}; };
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL); ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
if (ret < 0) { if (ret < 0)
TRACE("command %s failed for container \"%s\": %s.",
lxc_cmd_str(cmd.req.cmd), name, strerror(errno));
return NULL; return NULL;
}
if (!ret) { if (ret == 0)
WARN("container \"%s\" has stopped before sending its state", name);
return NULL; return NULL;
}
if (cmd.rsp.ret < 0 || cmd.rsp.datalen < 0) { 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));
return NULL; return NULL;
}
TRACE("command %s successful for container \"%s\"",
lxc_cmd_str(cmd.req.cmd), name);
return cmd.rsp.data; return cmd.rsp.data;
} }
...@@ -462,8 +459,8 @@ char *lxc_cmd_get_cgroup_path(const char *name, const char *lxcpath, ...@@ -462,8 +459,8 @@ char *lxc_cmd_get_cgroup_path(const char *name, const char *lxcpath,
static int lxc_cmd_get_cgroup_callback(int fd, struct lxc_cmd_req *req, static int lxc_cmd_get_cgroup_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler) struct lxc_handler *handler)
{ {
struct lxc_cmd_rsp rsp;
const char *path; const char *path;
struct lxc_cmd_rsp rsp;
if (req->datalen < 1) if (req->datalen < 1)
return -1; return -1;
...@@ -471,9 +468,10 @@ static int lxc_cmd_get_cgroup_callback(int fd, struct lxc_cmd_req *req, ...@@ -471,9 +468,10 @@ static int lxc_cmd_get_cgroup_callback(int fd, struct lxc_cmd_req *req,
path = cgroup_get_cgroup(handler, req->data); path = cgroup_get_cgroup(handler, req->data);
if (!path) if (!path)
return -1; return -1;
rsp.datalen = strlen(path) + 1,
rsp.data = (char *)path;
rsp.ret = 0; rsp.ret = 0;
rsp.datalen = strlen(path) + 1;
rsp.data = (char *)path;
return lxc_cmd_rsp_send(fd, &rsp); return lxc_cmd_rsp_send(fd, &rsp);
} }
...@@ -495,7 +493,7 @@ char *lxc_cmd_get_config_item(const char *name, const char *item, ...@@ -495,7 +493,7 @@ char *lxc_cmd_get_config_item(const char *name, const char *item,
struct lxc_cmd_rr cmd = { struct lxc_cmd_rr cmd = {
.req = { .cmd = LXC_CMD_GET_CONFIG_ITEM, .req = { .cmd = LXC_CMD_GET_CONFIG_ITEM,
.data = item, .data = item,
.datalen = strlen(item)+1, .datalen = strlen(item) + 1,
}, },
}; };
...@@ -505,6 +503,7 @@ char *lxc_cmd_get_config_item(const char *name, const char *item, ...@@ -505,6 +503,7 @@ char *lxc_cmd_get_config_item(const char *name, const char *item,
if (cmd.rsp.ret == 0) if (cmd.rsp.ret == 0)
return cmd.rsp.data; return cmd.rsp.data;
return NULL; return NULL;
} }
...@@ -512,14 +511,15 @@ static int lxc_cmd_get_config_item_callback(int fd, struct lxc_cmd_req *req, ...@@ -512,14 +511,15 @@ static int lxc_cmd_get_config_item_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler) struct lxc_handler *handler)
{ {
int cilen; int cilen;
struct lxc_cmd_rsp rsp;
char *cidata; char *cidata;
struct lxc_config_t *item; struct lxc_config_t *item;
struct lxc_cmd_rsp rsp;
memset(&rsp, 0, sizeof(rsp)); memset(&rsp, 0, sizeof(rsp));
item = lxc_get_config(req->data); item = lxc_get_config(req->data);
if (!item) if (!item)
goto err1; goto err1;
cilen = item->get(req->data, NULL, 0, handler->conf, NULL); cilen = item->get(req->data, NULL, 0, handler->conf, NULL);
if (cilen <= 0) if (cilen <= 0)
goto err1; goto err1;
...@@ -527,6 +527,7 @@ static int lxc_cmd_get_config_item_callback(int fd, struct lxc_cmd_req *req, ...@@ -527,6 +527,7 @@ static int lxc_cmd_get_config_item_callback(int fd, struct lxc_cmd_req *req,
cidata = alloca(cilen + 1); cidata = alloca(cilen + 1);
if (item->get(req->data, cidata, cilen + 1, handler->conf, NULL) != cilen) if (item->get(req->data, cidata, cilen + 1, handler->conf, NULL) != cilen)
goto err1; goto err1;
cidata[cilen] = '\0'; cidata[cilen] = '\0';
rsp.data = cidata; rsp.data = cidata;
rsp.datalen = cilen + 1; rsp.datalen = cilen + 1;
...@@ -589,6 +590,7 @@ static int lxc_cmd_set_config_item_callback(int fd, struct lxc_cmd_req *req, ...@@ -589,6 +590,7 @@ static int lxc_cmd_set_config_item_callback(int fd, struct lxc_cmd_req *req,
memset(&rsp, 0, sizeof(rsp)); memset(&rsp, 0, sizeof(rsp));
rsp.ret = lxc_set_config_item_locked(handler->conf, key, value); rsp.ret = lxc_set_config_item_locked(handler->conf, key, value);
return lxc_cmd_rsp_send(fd, &rsp); return lxc_cmd_rsp_send(fd, &rsp);
} }
...@@ -615,12 +617,13 @@ int lxc_cmd_get_state(const char *name, const char *lxcpath) ...@@ -615,12 +617,13 @@ int lxc_cmd_get_state(const char *name, const char *lxcpath)
return -1; return -1;
if (!ret) { if (!ret) {
WARN("Container \"%s\" has stopped before sending its state.", name); WARN("Container \"%s\" has stopped before sending its state", name);
return -1; 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))); lxc_state2str(PTR_TO_INT(cmd.rsp.data)));
return PTR_TO_INT(cmd.rsp.data); return PTR_TO_INT(cmd.rsp.data);
} }
...@@ -651,22 +654,23 @@ int lxc_cmd_stop(const char *name, const char *lxcpath) ...@@ -651,22 +654,23 @@ int lxc_cmd_stop(const char *name, const char *lxcpath)
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL); ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
if (ret < 0) { if (ret < 0) {
if (stopped) { if (stopped) {
INFO("Container \"%s\" is already stopped.", name); INFO("Container \"%s\" is already stopped", name);
return 0; return 0;
} }
return -1; return -1;
} }
/* we do not expect any answer, because we wait for the connection to be /* We do not expect any answer, because we wait for the connection to be
* closed * closed.
*/ */
if (ret > 0) { if (ret > 0) {
ERROR("Failed to stop container \"%s\": %s.", name, ERROR("%s - Failed to stop container \"%s\"",
strerror(-cmd.rsp.ret)); strerror(-cmd.rsp.ret), name);
return -1; return -1;
} }
INFO("Container \"%s\" has stopped.", name); INFO("Container \"%s\" has stopped", name);
return 0; return 0;
} }
...@@ -681,14 +685,15 @@ static int lxc_cmd_stop_callback(int fd, struct lxc_cmd_req *req, ...@@ -681,14 +685,15 @@ static int lxc_cmd_stop_callback(int fd, struct lxc_cmd_req *req,
memset(&rsp, 0, sizeof(rsp)); memset(&rsp, 0, sizeof(rsp));
rsp.ret = kill(handler->pid, stopsignal); rsp.ret = kill(handler->pid, stopsignal);
if (!rsp.ret) { 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 * context of handling the STOP cmd in lxc-start, and calling
* lxc_unfreeze() would do another cmd (GET_CGROUP) which would * lxc_unfreeze() would do another cmd (GET_CGROUP) which would
* deadlock us * deadlock us.
*/ */
if (cgroup_unfreeze(handler)) if (cgroup_unfreeze(handler))
return 0; return 0;
ERROR("Failed to unfreeze container \"%s\".", handler->name);
ERROR("Failed to unfreeze container \"%s\"", handler->name);
rsp.ret = -1; rsp.ret = -1;
} }
...@@ -723,6 +728,7 @@ static int lxc_cmd_console_winch_callback(int fd, struct lxc_cmd_req *req, ...@@ -723,6 +728,7 @@ static int lxc_cmd_console_winch_callback(int fd, struct lxc_cmd_req *req,
struct lxc_cmd_rsp rsp = { .data = 0 }; struct lxc_cmd_rsp rsp = { .data = 0 };
lxc_console_sigwinch(SIGWINCH); lxc_console_sigwinch(SIGWINCH);
return lxc_cmd_rsp_send(fd, &rsp); return lxc_cmd_rsp_send(fd, &rsp);
} }
...@@ -750,27 +756,28 @@ int lxc_cmd_console(const char *name, int *ttynum, int *fd, const char *lxcpath) ...@@ -750,27 +756,28 @@ int lxc_cmd_console(const char *name, int *ttynum, int *fd, const char *lxcpath)
return ret; return ret;
if (cmd.rsp.ret < 0) { 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; ret = -1;
goto out; goto out;
} }
if (ret == 0) { 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; ret = -1;
goto out; goto out;
} }
rspdata = cmd.rsp.data; rspdata = cmd.rsp.data;
if (rspdata->masterfd < 0) { 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; goto out;
} }
ret = cmd.rsp.ret; /* sock fd */ ret = cmd.rsp.ret; /* socket fd */
*fd = rspdata->masterfd; *fd = rspdata->masterfd;
*ttynum = rspdata->ttynum; *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: out:
free(cmd.rsp.data); free(cmd.rsp.data);
return ret; return ret;
...@@ -779,9 +786,9 @@ out: ...@@ -779,9 +786,9 @@ out:
static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req, static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler) struct lxc_handler *handler)
{ {
int ttynum = PTR_TO_INT(req->data); int masterfd, ret;
int masterfd;
struct lxc_cmd_rsp rsp; struct lxc_cmd_rsp rsp;
int ttynum = PTR_TO_INT(req->data);
masterfd = lxc_console_allocate(handler->conf, fd, &ttynum); masterfd = lxc_console_allocate(handler->conf, fd, &ttynum);
if (masterfd < 0) if (masterfd < 0)
...@@ -789,8 +796,9 @@ static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req, ...@@ -789,8 +796,9 @@ static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req,
memset(&rsp, 0, sizeof(rsp)); memset(&rsp, 0, sizeof(rsp));
rsp.data = INT_TO_PTR(ttynum); rsp.data = INT_TO_PTR(ttynum);
if (lxc_abstract_unix_send_fds(fd, &masterfd, 1, &rsp, sizeof(rsp)) < 0) { ret = lxc_abstract_unix_send_fds(fd, &masterfd, 1, &rsp, sizeof(rsp));
ERROR("Failed to send tty to client."); if (ret < 0) {
ERROR("Failed to send tty to client");
lxc_console_free(handler->conf, fd); lxc_console_free(handler->conf, fd);
goto out_close; goto out_close;
} }
...@@ -798,8 +806,8 @@ static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req, ...@@ -798,8 +806,8 @@ static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req,
return 0; return 0;
out_close: out_close:
/* special indicator to lxc_cmd_handler() to close the fd and do /* Special indicator to lxc_cmd_handler() to close the fd and do
* related cleanup * related cleanup.
*/ */
return 1; return 1;
} }
...@@ -819,12 +827,12 @@ char *lxc_cmd_get_name(const char *hashed_sock_name) ...@@ -819,12 +827,12 @@ char *lxc_cmd_get_name(const char *hashed_sock_name)
}; };
ret = lxc_cmd(NULL, &cmd, &stopped, NULL, hashed_sock_name); ret = lxc_cmd(NULL, &cmd, &stopped, NULL, hashed_sock_name);
if (ret < 0) { if (ret < 0)
return NULL; return NULL;
}
if (cmd.rsp.ret == 0) if (cmd.rsp.ret == 0)
return cmd.rsp.data; return cmd.rsp.data;
return NULL; return NULL;
} }
...@@ -857,12 +865,12 @@ char *lxc_cmd_get_lxcpath(const char *hashed_sock_name) ...@@ -857,12 +865,12 @@ char *lxc_cmd_get_lxcpath(const char *hashed_sock_name)
}; };
ret = lxc_cmd(NULL, &cmd, &stopped, NULL, hashed_sock_name); ret = lxc_cmd(NULL, &cmd, &stopped, NULL, hashed_sock_name);
if (ret < 0) { if (ret < 0)
return NULL; return NULL;
}
if (cmd.rsp.ret == 0) if (cmd.rsp.ret == 0)
return cmd.rsp.data; return cmd.rsp.data;
return NULL; return NULL;
} }
...@@ -873,9 +881,9 @@ static int lxc_cmd_get_lxcpath_callback(int fd, struct lxc_cmd_req *req, ...@@ -873,9 +881,9 @@ static int lxc_cmd_get_lxcpath_callback(int fd, struct lxc_cmd_req *req,
memset(&rsp, 0, sizeof(rsp)); memset(&rsp, 0, sizeof(rsp));
rsp.ret = 0;
rsp.data = (char *)handler->lxcpath; rsp.data = (char *)handler->lxcpath;
rsp.datalen = strlen(handler->lxcpath) + 1; rsp.datalen = strlen(handler->lxcpath) + 1;
rsp.ret = 0;
return lxc_cmd_rsp_send(fd, &rsp); return lxc_cmd_rsp_send(fd, &rsp);
} }
...@@ -903,53 +911,49 @@ int lxc_cmd_add_state_client(const char *name, const char *lxcpath, ...@@ -903,53 +911,49 @@ int lxc_cmd_add_state_client(const char *name, const char *lxcpath,
state = lxc_getstate(name, lxcpath); state = lxc_getstate(name, lxcpath);
if (state < 0) { if (state < 0) {
process_unlock(); process_unlock();
TRACE("failed to retrieve state of container: %s", TRACE("%s - Failed to retrieve state of container", strerror(errno));
strerror(errno));
return -1; return -1;
} else if (states[state]) { } else if (states[state]) {
process_unlock(); process_unlock();
TRACE("container is %s state", lxc_state2str(state)); TRACE("Container is %s state", lxc_state2str(state));
return state; return state;
} }
if ((state == STARTING) && !states[RUNNING] && !states[STOPPING] && !states[STOPPED]) { if ((state == STARTING) && !states[RUNNING] && !states[STOPPING] && !states[STOPPED]) {
process_unlock(); process_unlock();
TRACE("container is in %s state and caller requested to be " TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", "informed about a previous state", lxc_state2str(state));
lxc_state2str(state));
return state; return state;
} else if ((state == RUNNING) && !states[STOPPING] && !states[STOPPED]) { } else if ((state == RUNNING) && !states[STOPPING] && !states[STOPPED]) {
process_unlock(); process_unlock();
TRACE("container is in %s state and caller requested to be " TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", "informed about a previous state", lxc_state2str(state));
lxc_state2str(state));
return state; return state;
} else if ((state == STOPPING) && !states[STOPPED]) { } else if ((state == STOPPING) && !states[STOPPED]) {
process_unlock(); process_unlock();
TRACE("container is in %s state and caller requested to be " TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", "informed about a previous state", lxc_state2str(state));
lxc_state2str(state));
return state; return state;
} else if ((state == STOPPED) || (state == ABORTING)) { } else if ((state == STOPPED) || (state == ABORTING)) {
process_unlock(); process_unlock();
TRACE("container is in %s state and caller requested to be " TRACE("Container is in %s state and caller requested to be "
"informed about a previous state", "informed about a previous state", lxc_state2str(state));
lxc_state2str(state));
return state; return state;
} }
ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL); ret = lxc_cmd(name, &cmd, &stopped, lxcpath, NULL);
process_unlock(); process_unlock();
if (ret < 0) { if (ret < 0) {
ERROR("failed to execute command: %s", strerror(errno)); ERROR("%s - Failed to execute command", strerror(errno));
return -1; return -1;
} }
/* We should now be guaranteed to get an answer from the state sending /* We should now be guaranteed to get an answer from the state sending
* function. * function.
*/ */
if (cmd.rsp.ret < 0) { if (cmd.rsp.ret < 0) {
ERROR("failed to receive socket fd"); ERROR("Failed to receive socket fd");
return -1; return -1;
} }
...@@ -962,20 +966,14 @@ static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req, ...@@ -962,20 +966,14 @@ static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req,
{ {
struct lxc_cmd_rsp rsp = {0}; struct lxc_cmd_rsp rsp = {0};
if (req->datalen < 0) { if (req->datalen < 0)
TRACE("Requested datalen was < 0");
return -1; return -1;
}
if (req->datalen > (sizeof(lxc_state_t) * MAX_STATE)) { if (req->datalen > (sizeof(lxc_state_t) * MAX_STATE))
TRACE("Requested datalen was too large");
return -1; return -1;
}
if (!req->data) { if (!req->data)
TRACE("No states requested");
return -1; return -1;
}
rsp.ret = lxc_add_state_client(fd, handler, (lxc_state_t *)req->data); rsp.ret = lxc_add_state_client(fd, handler, (lxc_state_t *)req->data);
if (rsp.ret < 0) if (rsp.ret < 0)
...@@ -986,6 +984,92 @@ static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req, ...@@ -986,6 +984,92 @@ static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req,
return lxc_cmd_rsp_send(fd, &rsp); 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, static int lxc_cmd_process(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler) struct lxc_handler *handler)
{ {
...@@ -1004,10 +1088,11 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req, ...@@ -1004,10 +1088,11 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req,
[LXC_CMD_GET_LXCPATH] = lxc_cmd_get_lxcpath_callback, [LXC_CMD_GET_LXCPATH] = lxc_cmd_get_lxcpath_callback,
[LXC_CMD_ADD_STATE_CLIENT] = lxc_cmd_add_state_client_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_SET_CONFIG_ITEM] = lxc_cmd_set_config_item_callback,
[LXC_CMD_CONSOLE_LOG] = lxc_cmd_console_log_callback,
}; };
if (req->cmd >= LXC_CMD_MAX) { if (req->cmd >= LXC_CMD_MAX) {
ERROR("Undefined command id %d received.", req->cmd); ERROR("Undefined command id %d", req->cmd);
return -1; return -1;
} }
return cb[req->cmd](fd, req, handler); return cb[req->cmd](fd, req, handler);
...@@ -1026,69 +1111,83 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, ...@@ -1026,69 +1111,83 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data,
{ {
int ret; int ret;
struct lxc_cmd_req req; struct lxc_cmd_req req;
void *reqdata = NULL;
struct lxc_handler *handler = data; struct lxc_handler *handler = data;
ret = lxc_abstract_unix_rcv_credential(fd, &req, sizeof(req)); ret = lxc_abstract_unix_rcv_credential(fd, &req, sizeof(req));
if (ret == -EACCES) { if (ret == -EACCES) {
/* we don't care for the peer, just send and close */ /* We don't care for the peer, just send and close. */
struct lxc_cmd_rsp rsp = { .ret = ret }; struct lxc_cmd_rsp rsp = {.ret = ret};
lxc_cmd_rsp_send(fd, &rsp); lxc_cmd_rsp_send(fd, &rsp);
goto out_close; goto out_close;
} }
TRACE("Processing \"%s\" command", lxc_cmd_str(req.cmd));
if (ret < 0) { if (ret < 0) {
SYSERROR("Failed to receive data on command socket for \"%s\"", SYSERROR("Failed to receive data on command socket for command "
lxc_cmd_str(req.cmd)); "\"%s\"", lxc_cmd_str(req.cmd));
goto out_close; goto out_close;
} }
if (!ret) { if (ret == 0)
DEBUG("Peer has disconnected for \"%s\"", lxc_cmd_str(req.cmd));
goto out_close; goto out_close;
}
if (ret != sizeof(req)) { if (ret != sizeof(req)) {
WARN("Failed to receive full command request. Ignoring request " WARN("Failed to receive full command request. Ignoring request "
"for \"%s\"", "for \"%s\"", lxc_cmd_str(req.cmd));
lxc_cmd_str(req.cmd));
ret = -1; ret = -1;
goto out_close; 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 " ERROR("Received command data length %d is too large for "
"command \"%s\"", "command \"%s\"", req.datalen, lxc_cmd_str(req.cmd));
req.datalen, lxc_cmd_str(req.cmd)); errno = EFBIG;
ret = -1; ret = -EFBIG;
goto out_close; goto out_close;
} }
if (req.datalen > 0) { 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); 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;
}
ret = recv(fd, reqdata, req.datalen, 0); ret = recv(fd, reqdata, req.datalen, 0);
if (ret != req.datalen) { if (ret != req.datalen) {
WARN("Failed to receive full command request. Ignoring " WARN("Failed to receive full command request. Ignoring "
"request for \"%s\"", "request for \"%s\"", lxc_cmd_str(req.cmd));
lxc_cmd_str(req.cmd));
ret = -1; ret = -1;
goto out_close; goto out_close;
} }
req.data = reqdata; req.data = reqdata;
} }
ret = lxc_cmd_process(fd, &req, handler); ret = lxc_cmd_process(fd, &req, handler);
if (ret) { 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; ret = 0;
goto out_close; goto out_close;
} }
out: out:
if (req.cmd == LXC_CMD_CONSOLE_LOG && reqdata)
free(reqdata);
return ret; return ret;
out_close: out_close:
lxc_cmd_fd_cleanup(fd, handler, descr); lxc_cmd_fd_cleanup(fd, handler, descr);
goto out; goto out;
...@@ -1097,7 +1196,8 @@ out_close: ...@@ -1097,7 +1196,8 @@ out_close:
static int lxc_cmd_accept(int fd, uint32_t events, void *data, static int lxc_cmd_accept(int fd, uint32_t events, void *data,
struct lxc_epoll_descr *descr) struct lxc_epoll_descr *descr)
{ {
int opt = 1, ret = -1, connection; int connection;
int opt = 1, ret = -1;
connection = accept(fd, NULL, 0); connection = accept(fd, NULL, 0);
if (connection < 0) { if (connection < 0) {
...@@ -1105,20 +1205,21 @@ static int lxc_cmd_accept(int fd, uint32_t events, void *data, ...@@ -1105,20 +1205,21 @@ static int lxc_cmd_accept(int fd, uint32_t events, void *data,
return -1; return -1;
} }
if (fcntl(connection, F_SETFD, FD_CLOEXEC)) { ret = fcntl(connection, F_SETFD, FD_CLOEXEC);
SYSERROR("Failed to set close-on-exec on incoming command connection."); if (ret < 0) {
SYSERROR("Failed to set close-on-exec on incoming command connection");
goto out_close; goto out_close;
} }
if (setsockopt(connection, SOL_SOCKET, ret = setsockopt(connection, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt));
SO_PASSCRED, &opt, sizeof(opt))) { if (ret < 0) {
SYSERROR("Failed to enable necessary credentials on command socket."); SYSERROR("Failed to enable necessary credentials on command socket");
goto out_close; goto out_close;
} }
ret = lxc_mainloop_add_handler(descr, connection, lxc_cmd_handler, data); ret = lxc_mainloop_add_handler(descr, connection, lxc_cmd_handler, data);
if (ret) { if (ret) {
ERROR("Failed to add command handler."); ERROR("Failed to add command handler");
goto out_close; goto out_close;
} }
...@@ -1133,10 +1234,9 @@ out_close: ...@@ -1133,10 +1234,9 @@ out_close:
int lxc_cmd_init(const char *name, struct lxc_handler *handler, int lxc_cmd_init(const char *name, struct lxc_handler *handler,
const char *lxcpath) const char *lxcpath)
{ {
int fd; int fd, len, ret;
char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = { 0 }; char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = {0};
char *offset = &path[1]; char *offset = &path[1];
int len;
/* -2 here because this is an abstract unix socket so it needs a /* -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. * leading \0, and we null terminate, so it needs a trailing \0.
...@@ -1144,21 +1244,22 @@ int lxc_cmd_init(const char *name, struct lxc_handler *handler, ...@@ -1144,21 +1244,22 @@ int lxc_cmd_init(const char *name, struct lxc_handler *handler,
* because we print the sockname out sometimes. * because we print the sockname out sometimes.
*/ */
len = sizeof(path) - 2; len = sizeof(path) - 2;
if (lxc_make_abstract_socket_name(offset, len, name, lxcpath, NULL, ret = lxc_make_abstract_socket_name(offset, len, name, lxcpath, NULL, "command");
"command")) if (ret < 0)
return -1; return -1;
fd = lxc_abstract_unix_open(path, SOCK_STREAM, 0); fd = lxc_abstract_unix_open(path, SOCK_STREAM, 0);
if (fd < 0) { if (fd < 0) {
ERROR("Failed to create the command service point %s: %s.", ERROR("%s - Failed to create command socket %s",
offset, strerror(errno)); strerror(errno), offset);
if (errno == EADDRINUSE) if (errno == EADDRINUSE)
ERROR("Container \"%s\" appears to be already running!", name); ERROR("Container \"%s\" appears to be already running", name);
return -1; return -1;
} }
if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
SYSERROR("Failed to set FD_CLOEXEC on signal file descriptor."); if (ret < 0) {
SYSERROR("Failed to set FD_CLOEXEC on command socket file descriptor");
close(fd); close(fd);
return -1; return -1;
} }
...@@ -1167,15 +1268,15 @@ int lxc_cmd_init(const char *name, struct lxc_handler *handler, ...@@ -1167,15 +1268,15 @@ int lxc_cmd_init(const char *name, struct lxc_handler *handler,
return 0; return 0;
} }
int lxc_cmd_mainloop_add(const char *name, int lxc_cmd_mainloop_add(const char *name, struct lxc_epoll_descr *descr,
struct lxc_epoll_descr *descr,
struct lxc_handler *handler) 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); ret = lxc_mainloop_add_handler(descr, fd, lxc_cmd_accept, handler);
if (ret) { if (ret < 0) {
ERROR("Failed to add handler for command socket."); ERROR("Failed to add handler for command socket");
close(fd); close(fd);
} }
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <sys/types.h> #include <sys/types.h>
#include "state.h" #include "state.h"
#include "lxccontainer.h"
#define LXC_CMD_DATA_MAX (MAXPATHLEN * 2) #define LXC_CMD_DATA_MAX (MAXPATHLEN * 2)
...@@ -49,6 +50,7 @@ typedef enum { ...@@ -49,6 +50,7 @@ typedef enum {
LXC_CMD_GET_LXCPATH, LXC_CMD_GET_LXCPATH,
LXC_CMD_ADD_STATE_CLIENT, LXC_CMD_ADD_STATE_CLIENT,
LXC_CMD_SET_CONFIG_ITEM, LXC_CMD_SET_CONFIG_ITEM,
LXC_CMD_CONSOLE_LOG,
LXC_CMD_MAX, LXC_CMD_MAX,
} lxc_cmd_t; } lxc_cmd_t;
...@@ -79,6 +81,14 @@ struct lxc_cmd_set_config_item_req_data { ...@@ -79,6 +81,14 @@ struct lxc_cmd_set_config_item_req_data {
void *value; 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_winch(const char *name, const char *lxcpath);
extern int lxc_cmd_console(const char *name, int *ttynum, int *fd, extern int lxc_cmd_console(const char *name, int *ttynum, int *fd,
const char *lxcpath); const char *lxcpath);
...@@ -124,5 +134,7 @@ extern int lxc_try_cmd(const char *name, 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, extern int lxc_cmd_set_config_item(const char *name, const char *item,
const char *value, const char *lxcpath); 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 */ #endif /* __commands_h */
...@@ -3083,65 +3083,7 @@ static bool verify_start_hooks(struct lxc_conf *conf) ...@@ -3083,65 +3083,7 @@ static bool verify_start_hooks(struct lxc_conf *conf)
return true; return true;
} }
/** int lxc_setup(struct lxc_handler *handler)
* 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 ret; int ret;
const char *name = handler->name; const char *name = handler->name;
......
...@@ -379,7 +379,7 @@ extern int lxc_delete_autodev(struct lxc_handler *handler); ...@@ -379,7 +379,7 @@ extern int lxc_delete_autodev(struct lxc_handler *handler);
extern void lxc_clear_includes(struct lxc_conf *conf); extern void lxc_clear_includes(struct lxc_conf *conf);
extern int do_rootfs_setup(struct lxc_conf *conf, const char *name, extern int do_rootfs_setup(struct lxc_conf *conf, const char *name,
const char *lxcpath); 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 lxc_setup_parent(struct lxc_handler *handler);
extern int setup_resource_limits(struct lxc_list *limits, pid_t pid); extern int setup_resource_limits(struct lxc_list *limits, pid_t pid);
extern int find_unmapped_nsid(struct lxc_conf *conf, enum idtype idtype); extern int find_unmapped_nsid(struct lxc_conf *conf, enum idtype idtype);
......
...@@ -512,11 +512,50 @@ out: ...@@ -512,11 +512,50 @@ out:
return ret; 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) void lxc_console_delete(struct lxc_console *console)
{ {
if (console->tios && console->peer >= 0 && int ret;
tcsetattr(console->peer, TCSAFLUSH, console->tios))
WARN("failed to set old terminal settings"); 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); free(console->tios);
console->tios = NULL; console->tios = NULL;
...@@ -525,66 +564,120 @@ void lxc_console_delete(struct lxc_console *console) ...@@ -525,66 +564,120 @@ void lxc_console_delete(struct lxc_console *console)
close(console->slave); close(console->slave);
if (console->log_fd >= 0) if (console->log_fd >= 0)
close(console->log_fd); close(console->log_fd);
console->peer = -1; console->peer = -1;
console->master = -1; console->master = -1;
console->slave = -1; console->slave = -1;
console->log_fd = -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 lxc_console_create(struct lxc_conf *conf)
{ {
int ret, saved_errno;
struct lxc_console *console = &conf->console; struct lxc_console *console = &conf->console;
int ret;
if (!conf->rootfs.path) { 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; return 0;
} }
if (console->path && !strcmp(console->path, "none")) { if (console->path && !strcmp(console->path, "none")) {
INFO("no console requested"); INFO("No console was requested");
return 0; return 0;
} }
process_lock(); process_lock();
ret = openpty(&console->master, &console->slave, console->name, NULL, NULL); ret = openpty(&console->master, &console->slave, console->name, NULL, NULL);
saved_errno = errno;
process_unlock(); process_unlock();
if (ret < 0) { if (ret < 0) {
SYSERROR("failed to allocate a pty"); ERROR("%s - Failed to allocate a pty", strerror(saved_errno));
return -1; return -1;
} }
if (fcntl(console->master, F_SETFD, FD_CLOEXEC)) { ret = fcntl(console->master, F_SETFD, FD_CLOEXEC);
SYSERROR("failed to set console master to close-on-exec"); if (ret < 0) {
SYSERROR("Failed to set FD_CLOEXEC flag on console master");
goto err; goto err;
} }
if (fcntl(console->slave, F_SETFD, FD_CLOEXEC)) { ret = fcntl(console->slave, F_SETFD, FD_CLOEXEC);
SYSERROR("failed to set console slave to close-on-exec"); if (ret < 0) {
SYSERROR("Failed to set FD_CLOEXEC flag on console slave");
goto err; goto err;
} }
ret = lxc_console_peer_default(console); ret = lxc_console_peer_default(console);
if (ret < 0) { if (ret < 0) {
ERROR("failed to allocate peer tty device"); ERROR("Failed to allocate a peer pty device");
goto err; 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)); console->log_fd = lxc_unpriv(open(console->log_path, O_CLOEXEC | O_RDWR | O_CREAT | O_APPEND, 0600));
if (console->log_fd < 0) { 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; 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; return 0;
err: err:
lxc_console_delete(console); lxc_console_delete(console);
return -1; return -ENODEV;
} }
int lxc_console_set_stdfds(int fd) int lxc_console_set_stdfds(int fd)
...@@ -687,18 +780,16 @@ int lxc_console(struct lxc_container *c, int ttynum, ...@@ -687,18 +780,16 @@ int lxc_console(struct lxc_container *c, int ttynum,
istty = isatty(stdinfd); istty = isatty(stdinfd);
if (istty) { if (istty) {
ret = lxc_setup_tios(stdinfd, &oldtios); ret = lxc_setup_tios(stdinfd, &oldtios);
if (ret) { if (ret < 0)
ERROR("failed to setup terminal properties");
return -1; return -1;
}
} else { } 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); ttyfd = lxc_cmd_console(c->name, &ttynum, &masterfd, c->config_path);
if (ttyfd < 0) { if (ttyfd < 0) {
ret = ttyfd; ret = ttyfd;
goto err1; goto restore_tios;
} }
fprintf(stderr, "\n" fprintf(stderr, "\n"
...@@ -708,13 +799,13 @@ int lxc_console(struct lxc_container *c, int ttynum, ...@@ -708,13 +799,13 @@ int lxc_console(struct lxc_container *c, int ttynum,
ttynum, 'a' + escape - 1); ttynum, 'a' + escape - 1);
ret = setsid(); ret = setsid();
if (ret) if (ret < 0)
INFO("already group leader"); TRACE("Process is already group leader");
ts = lxc_console_sigwinch_init(stdinfd, masterfd); ts = lxc_console_sigwinch_init(stdinfd, masterfd);
if (!ts) { if (!ts) {
ret = -1; ret = -1;
goto err2; goto close_fds;
} }
ts->escape = escape; ts->escape = escape;
ts->winch_proxy = c->name; ts->winch_proxy = c->name;
...@@ -728,52 +819,57 @@ int lxc_console(struct lxc_container *c, int ttynum, ...@@ -728,52 +819,57 @@ int lxc_console(struct lxc_container *c, int ttynum,
ret = lxc_mainloop_open(&descr); ret = lxc_mainloop_open(&descr);
if (ret) { if (ret) {
ERROR("failed to create mainloop"); ERROR("Failed to create mainloop");
goto err3; goto sigwinch_fini;
} }
if (ts->sigfd != -1) { if (ts->sigfd != -1) {
ret = lxc_mainloop_add_handler(&descr, ts->sigfd, ret = lxc_mainloop_add_handler(&descr, ts->sigfd,
lxc_console_cb_sigwinch_fd, ts); lxc_console_cb_sigwinch_fd, ts);
if (ret) { if (ret < 0) {
ERROR("failed to add handler for SIGWINCH fd"); ERROR("Failed to add SIGWINCH handler");
goto err4; goto close_mainloop;
} }
} }
ret = lxc_mainloop_add_handler(&descr, ts->stdinfd, ret = lxc_mainloop_add_handler(&descr, ts->stdinfd,
lxc_console_cb_tty_stdin, ts); lxc_console_cb_tty_stdin, ts);
if (ret) { if (ret < 0) {
ERROR("failed to add handler for stdinfd"); ERROR("Failed to add stdin handler");
goto err4; goto close_mainloop;
} }
ret = lxc_mainloop_add_handler(&descr, ts->masterfd, ret = lxc_mainloop_add_handler(&descr, ts->masterfd,
lxc_console_cb_tty_master, ts); lxc_console_cb_tty_master, ts);
if (ret) { if (ret < 0) {
ERROR("failed to add handler for masterfd"); ERROR("Failed to add master handler");
goto err4; goto close_mainloop;
} }
ret = lxc_mainloop(&descr, -1); ret = lxc_mainloop(&descr, -1);
if (ret) { if (ret < 0) {
ERROR("mainloop returned an error"); ERROR("The mainloop returned an error");
goto err4; goto close_mainloop;
} }
ret = 0; ret = 0;
err4: close_mainloop:
lxc_mainloop_close(&descr); lxc_mainloop_close(&descr);
err3:
sigwinch_fini:
lxc_console_sigwinch_fini(ts); lxc_console_sigwinch_fini(ts);
err2:
close_fds:
close(masterfd); close(masterfd);
close(ttyfd); close(ttyfd);
err1:
restore_tios:
if (istty) { if (istty) {
if (tcsetattr(stdinfd, TCSAFLUSH, &oldtios) < 0) istty = tcsetattr(stdinfd, TCSAFLUSH, &oldtios);
WARN("failed to reset terminal properties: %s.", strerror(errno)); if (istty < 0)
WARN("%s - Failed to restore terminal properties",
strerror(errno));
} }
return ret; return ret;
......
...@@ -215,4 +215,6 @@ extern int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata, ...@@ -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 void lxc_console_sigwinch_fini(struct lxc_tty_state *ts);
extern int lxc_console_write_ringbuffer(struct lxc_console *console);
#endif #endif
...@@ -516,6 +516,29 @@ static int lxcapi_console(struct lxc_container *c, int ttynum, int stdinfd, ...@@ -516,6 +516,29 @@ static int lxcapi_console(struct lxc_container *c, int ttynum, int stdinfd,
return ret; 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) static pid_t do_lxcapi_init_pid(struct lxc_container *c)
{ {
if (!c) if (!c)
...@@ -4607,6 +4630,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath ...@@ -4607,6 +4630,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
c->checkpoint = lxcapi_checkpoint; c->checkpoint = lxcapi_checkpoint;
c->restore = lxcapi_restore; c->restore = lxcapi_restore;
c->migrate = lxcapi_migrate; c->migrate = lxcapi_migrate;
c->console_log = lxcapi_console_log;
return c; return c;
......
...@@ -51,6 +51,8 @@ struct lxc_lock; ...@@ -51,6 +51,8 @@ struct lxc_lock;
struct migrate_opts; struct migrate_opts;
struct lxc_console_log;
/*! /*!
* An LXC container. * An LXC container.
* *
...@@ -834,6 +836,16 @@ struct lxc_container { ...@@ -834,6 +836,16 @@ struct lxc_container {
* \return \c true on success, else \c false. * \return \c true on success, else \c false.
*/ */
bool (*set_running_config_item)(struct lxc_container *c, const char *key, const char *value); 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 { ...@@ -921,6 +933,34 @@ struct migrate_opts {
uint64_t ghost_limit; 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. * \brief Create a new container.
* *
......
...@@ -904,7 +904,7 @@ static int do_start(void *data) ...@@ -904,7 +904,7 @@ static int do_start(void *data)
} }
/* Setup the container, ip, names, utsname, ... */ /* Setup the container, ip, names, utsname, ... */
ret = lxc_setup_child(handler); ret = lxc_setup(handler);
close(handler->data_sock[0]); close(handler->data_sock[0]);
close(handler->data_sock[1]); close(handler->data_sock[1]);
if (ret < 0) { if (ret < 0) {
...@@ -1266,10 +1266,6 @@ static int lxc_spawn(struct lxc_handler *handler) ...@@ -1266,10 +1266,6 @@ static int lxc_spawn(struct lxc_handler *handler)
flags &= ~CLONE_NEWNET; flags &= ~CLONE_NEWNET;
} }
ret = lxc_setup_parent(handler);
if (ret < 0)
goto out_delete_net;
if (fork_before_clone) if (fork_before_clone)
handler->pid = lxc_fork_attach_clone(do_start, handler, flags | CLONE_PARENT); handler->pid = lxc_fork_attach_clone(do_start, handler, flags | CLONE_PARENT);
else else
......
...@@ -15,6 +15,7 @@ lxc_test_lxcpath_SOURCES = lxcpath.c ...@@ -15,6 +15,7 @@ lxc_test_lxcpath_SOURCES = lxcpath.c
lxc_test_cgpath_SOURCES = cgpath.c lxc_test_cgpath_SOURCES = cgpath.c
lxc_test_clonetest_SOURCES = clonetest.c lxc_test_clonetest_SOURCES = clonetest.c
lxc_test_console_SOURCES = console.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_snapshot_SOURCES = snapshot.c
lxc_test_concurrent_SOURCES = concurrent.c lxc_test_concurrent_SOURCES = concurrent.c
lxc_test_may_control_SOURCES = may_control.c lxc_test_may_control_SOURCES = may_control.c
...@@ -52,7 +53,7 @@ endif ...@@ -52,7 +53,7 @@ endif
bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
lxc-test-destroytest lxc-test-saveconfig lxc-test-createtest \ lxc-test-destroytest lxc-test-saveconfig lxc-test-createtest \
lxc-test-shutdowntest lxc-test-get_item lxc-test-getkeys lxc-test-lxcpath \ 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-snapshot lxc-test-concurrent lxc-test-may-control \
lxc-test-reboot lxc-test-list lxc-test-attach lxc-test-device-add-remove \ 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 \ lxc-test-apparmor lxc-test-utils lxc-test-parse-config-file \
...@@ -85,6 +86,7 @@ EXTRA_DIST = \ ...@@ -85,6 +86,7 @@ EXTRA_DIST = \
concurrent.c \ concurrent.c \
config_jump_table.c \ config_jump_table.c \
console.c \ console.c \
console_log.c \
containertests.c \ containertests.c \
createtest.c \ createtest.c \
destroytest.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