Commit 3f0af363 by Serge Hallyn Committed by GitHub

Merge pull request #1659 from brauner/2017-06-28/do_not_use_cmd_socket_on_daemonized_start

start: use separate socket on daemonized start
parents 616e0593 ee8377bd
...@@ -96,6 +96,7 @@ src/tests/lxc-test-utils* ...@@ -96,6 +96,7 @@ src/tests/lxc-test-utils*
src/tests/lxc-usernic-test src/tests/lxc-usernic-test
src/tests/lxc-test-config-jump-table src/tests/lxc-test-config-jump-table
src/tests/lxc-test-parse-config-file src/tests/lxc-test-parse-config-file
src/tests/lxc-test-shortlived
config/compile config/compile
config/config.guess config/config.guess
......
...@@ -92,6 +92,7 @@ liblxc_la_SOURCES = \ ...@@ -92,6 +92,7 @@ liblxc_la_SOURCES = \
cgroups/cgfsng.c \ cgroups/cgfsng.c \
cgroups/cgroup.c cgroups/cgroup.h \ cgroups/cgroup.c cgroups/cgroup.h \
commands.c commands.h \ commands.c commands.h \
commands_utils.c commands_utils.h \
start.c start.h \ start.c start.h \
execute.c \ execute.c \
monitor.c monitor.h \ monitor.c monitor.h \
......
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <inttypes.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/param.h> #include <sys/param.h>
#include <malloc.h> #include <malloc.h>
...@@ -41,6 +40,7 @@ ...@@ -41,6 +40,7 @@
#include "utils.h" #include "utils.h"
#include "cgroup.h" #include "cgroup.h"
#include "commands.h" #include "commands.h"
#include "commands_utils.h"
#include "console.h" #include "console.h"
#include "confile.h" #include "confile.h"
#include "lxclock.h" #include "lxclock.h"
...@@ -76,75 +76,20 @@ ...@@ -76,75 +76,20 @@
lxc_log_define(lxc_commands, lxc); lxc_log_define(lxc_commands, lxc);
static int fill_sock_name(char *path, int len, const char *lxcname,
const char *lxcpath, const char *hashed_sock_name)
{
const char *name;
char *tmppath;
size_t tmplen;
uint64_t hash;
int ret;
name = lxcname;
if (!name)
name = "";
if (hashed_sock_name != NULL) {
ret = snprintf(path, len, "lxc/%s/command", hashed_sock_name);
if (ret < 0 || ret >= len) {
ERROR("Error writing to command sock path");
return -1;
}
return 0;
}
if (!lxcpath) {
lxcpath = lxc_global_config_value("lxc.lxcpath");
if (!lxcpath) {
ERROR("Out of memory getting lxcpath");
return -1;
}
}
ret = snprintf(path, len, "%s/%s/command", lxcpath, name);
if (ret < 0) {
ERROR("Error writing to command sock path");
return -1;
}
if (ret < len)
return 0;
/* ret >= len; lxcpath or name is too long. hash both */
tmplen = strlen(name) + strlen(lxcpath) + 2;
tmppath = alloca(tmplen);
ret = snprintf(tmppath, tmplen, "%s/%s", lxcpath, name);
if (ret < 0 || ret >= tmplen) {
ERROR("memory error");
return -1;
}
hash = fnv_64a_buf(tmppath, ret, FNV1A_64_INIT);
ret = snprintf(path, len, "lxc/%016" PRIx64 "/command", hash);
if (ret < 0 || ret >= len) {
ERROR("Command socket name too long");
return -1;
}
return 0;
}
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_STOP] = "stop", [LXC_CMD_CONSOLE_WINCH] = "console_winch",
[LXC_CMD_GET_STATE] = "get_state", [LXC_CMD_STOP] = "stop",
[LXC_CMD_GET_INIT_PID] = "get_init_pid", [LXC_CMD_GET_STATE] = "get_state",
[LXC_CMD_GET_CLONE_FLAGS] = "get_clone_flags", [LXC_CMD_GET_INIT_PID] = "get_init_pid",
[LXC_CMD_GET_CGROUP] = "get_cgroup", [LXC_CMD_GET_CLONE_FLAGS] = "get_clone_flags",
[LXC_CMD_GET_CONFIG_ITEM] = "get_config_item", [LXC_CMD_GET_CGROUP] = "get_cgroup",
[LXC_CMD_GET_NAME] = "get_name", [LXC_CMD_GET_CONFIG_ITEM] = "get_config_item",
[LXC_CMD_GET_LXCPATH] = "get_lxcpath", [LXC_CMD_GET_NAME] = "get_name",
[LXC_CMD_STATE_SERVER] = "state_server", [LXC_CMD_GET_LXCPATH] = "get_lxcpath",
[LXC_CMD_ADD_STATE_CLIENT] = "add_state_client",
}; };
if (cmd >= LXC_CMD_MAX) if (cmd >= LXC_CMD_MAX)
...@@ -180,6 +125,7 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd) ...@@ -180,6 +125,7 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
lxc_cmd_str(cmd->req.cmd), strerror(errno)); lxc_cmd_str(cmd->req.cmd), strerror(errno));
return -1; return -1;
} }
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;
...@@ -260,6 +206,60 @@ static int lxc_cmd_rsp_send(int fd, struct lxc_cmd_rsp *rsp) ...@@ -260,6 +206,60 @@ static int lxc_cmd_rsp_send(int fd, struct lxc_cmd_rsp *rsp)
return 0; return 0;
} }
static int lxc_cmd_send(const char *name, struct lxc_cmd_rr *cmd,
const char *lxcpath, const char *hashed_sock_name)
{
int client_fd;
int ret = -1;
/* -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.
* Although null termination isn't required by the API, we do it anyway
* because we print the sockname out sometimes.
*/
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;
TRACE("Command \"%s\" connected to command socket",
lxc_cmd_str(cmd->req.cmd));
ret = lxc_abstract_unix_send_credential(client_fd, &cmd->req, sizeof(cmd->req));
if (ret != sizeof(cmd->req)) {
close(client_fd);
if (errno == EPIPE)
return -EPIPE;
if (ret >= 0)
return -EMSGSIZE;
return -1;
}
TRACE("Command \"%s\" requested data of length %d",
lxc_cmd_str(cmd->req.cmd), cmd->req.datalen);
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);
if (errno == EPIPE)
return -EPIPE;
if (ret >= 0)
return -EMSGSIZE;
return -1;
}
}
return client_fd;
}
/* /*
* lxc_cmd: Connect to the specified running container, send it a command * lxc_cmd: Connect to the specified running container, send it a command
* request and collect the response * request and collect the response
...@@ -282,14 +282,11 @@ static int lxc_cmd_rsp_send(int fd, struct lxc_cmd_rsp *rsp) ...@@ -282,14 +282,11 @@ static int lxc_cmd_rsp_send(int fd, struct lxc_cmd_rsp *rsp)
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 sock, ret = -1; int client_fd, ret = -1;
char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = { 0 };
char *offset = &path[1];
size_t len;
bool stay_connected = false; bool stay_connected = false;
if (cmd->req.cmd == LXC_CMD_CONSOLE || if (cmd->req.cmd == LXC_CMD_CONSOLE ||
cmd->req.cmd == LXC_CMD_STATE_SERVER) cmd->req.cmd == LXC_CMD_ADD_STATE_CLIENT)
stay_connected = true; stay_connected = true;
*stopped = 0; *stopped = 0;
...@@ -299,63 +296,34 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped, ...@@ -299,63 +296,34 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
* Although null termination isn't required by the API, we do it anyway * Although null termination isn't required by the API, we do it anyway
* because we print the sockname out sometimes. * because we print the sockname out sometimes.
*/ */
len = sizeof(path)-2; TRACE("command %s tries to connect command socket",
if (fill_sock_name(offset, len, name, lxcpath, hashed_sock_name)) lxc_cmd_str(cmd->req.cmd));
return -1;
sock = lxc_abstract_unix_connect(path); client_fd = lxc_cmd_send(name, cmd, lxcpath, hashed_sock_name);
TRACE("command %s tries to connect to \"@%s\"", if (client_fd < 0) {
lxc_cmd_str(cmd->req.cmd), offset); TRACE("command %s failed to connect command socket: %s",
if (sock < 0) { lxc_cmd_str(cmd->req.cmd), strerror(errno));
if (errno == ECONNREFUSED) { if (client_fd == -ECONNREFUSED) {
TRACE("command %s failed to connect to \"@%s\": %s",
lxc_cmd_str(cmd->req.cmd), offset,
strerror(errno));
*stopped = 1; *stopped = 1;
} else { return -1;
SYSERROR("command %s failed to connect to \"@%s\": %s",
lxc_cmd_str(cmd->req.cmd), offset,
strerror(errno));
} }
return -1; if (client_fd == -EPIPE)
}
ret = lxc_abstract_unix_send_credential(sock, &cmd->req, sizeof(cmd->req));
if (ret != sizeof(cmd->req)) {
if (errno == EPIPE)
goto epipe; goto epipe;
SYSERROR("Command %s failed to send req to \"@%s\" %d.",
lxc_cmd_str(cmd->req.cmd), offset, ret);
if (ret >=0)
ret = -1;
goto out;
}
if (cmd->req.datalen > 0) { goto out;
ret = send(sock, cmd->req.data, cmd->req.datalen, MSG_NOSIGNAL);
if (ret != cmd->req.datalen) {
if (errno == EPIPE)
goto epipe;
SYSERROR("Command %s failed to send request data to \"@%s\" %d.",
lxc_cmd_str(cmd->req.cmd), offset, ret);
if (ret >=0)
ret = -1;
goto out;
}
} }
ret = lxc_cmd_rsp_recv(sock, cmd); ret = lxc_cmd_rsp_recv(client_fd, cmd);
out: out:
if (!stay_connected || ret <= 0) if (!stay_connected || ret <= 0)
close(sock); close(client_fd);
if (stay_connected && ret > 0) if (stay_connected && ret > 0)
cmd->rsp.ret = sock; cmd->rsp.ret = client_fd;
return ret; return ret;
epipe: epipe:
close(sock);
*stopped = 1; *stopped = 1;
return 0; return 0;
} }
...@@ -868,31 +836,23 @@ static int lxc_cmd_get_lxcpath_callback(int fd, struct lxc_cmd_req *req, ...@@ -868,31 +836,23 @@ static int lxc_cmd_get_lxcpath_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_add_state_client(const char *name, const char *lxcpath,
* lxc_cmd_state_server: register a client fd in the handler list lxc_state_t states[MAX_STATE],
* int *state_client_fd)
* @name : name of container to connect to
* @lxcpath : the lxcpath in which the container is running
*
* Returns the lxcpath on success, NULL on failure.
*/
int lxc_cmd_state_server(const char *name, const char *lxcpath,
lxc_state_t states[MAX_STATE])
{ {
int stopped; int stopped;
ssize_t ret; ssize_t ret;
int state = -1; int state = -1;
struct lxc_msg msg = {0};
struct lxc_cmd_rr cmd = { struct lxc_cmd_rr cmd = {
.req = { .req = {
.cmd = LXC_CMD_STATE_SERVER, .cmd = LXC_CMD_ADD_STATE_CLIENT,
.data = states, .data = states,
.datalen = (sizeof(lxc_state_t) * MAX_STATE) .datalen = (sizeof(lxc_state_t) * MAX_STATE)
}, },
}; };
/* Lock the whole lxc_cmd_state_server_callback() call to ensure that /* Lock the whole lxc_cmd_add_state_client_callback() call to ensure
* lxc_set_state() doesn't cause us to miss a state. * that lxc_set_state() doesn't cause us to miss a state.
*/ */
process_lock(); process_lock();
/* Check if already in requested state. */ /* Check if already in requested state. */
...@@ -949,60 +909,35 @@ int lxc_cmd_state_server(const char *name, const char *lxcpath, ...@@ -949,60 +909,35 @@ int lxc_cmd_state_server(const char *name, const char *lxcpath,
return -1; return -1;
} }
again: *state_client_fd = cmd.rsp.ret;
ret = recv(cmd.rsp.ret, &msg, sizeof(msg), 0); return MAX_STATE;
if (ret < 0) {
if (errno == EINTR)
goto again;
ERROR("failed to receive message: %s", strerror(errno));
return -1;
}
if (ret == 0) {
ERROR("length of message was 0");
return -1;
}
TRACE("received state %s from state client %d",
lxc_state2str(msg.value), cmd.rsp.ret);
return msg.value;
} }
static int lxc_cmd_state_server_callback(int fd, struct lxc_cmd_req *req, static int lxc_cmd_add_state_client_callback(int fd, struct lxc_cmd_req *req,
struct lxc_handler *handler) struct lxc_handler *handler)
{ {
struct lxc_cmd_rsp rsp = {0}; struct lxc_cmd_rsp rsp = {0};
struct state_client *newclient;
struct lxc_list *tmplist;
if (req->datalen < 0) { if (req->datalen < 0) {
TRACE("requested datalen was < 0"); TRACE("Requested datalen was < 0");
return -1; return -1;
} }
if (!req->data) { if (req->datalen > (sizeof(lxc_state_t) * MAX_STATE)) {
TRACE("no states requested"); TRACE("Requested datalen was too large");
return -1; return -1;
} }
newclient = malloc(sizeof(*newclient)); if (!req->data) {
if (!newclient) TRACE("No states requested");
return -1;
/* copy requested states */
memcpy(newclient->states, req->data, sizeof(newclient->states));
newclient->clientfd = fd;
tmplist = malloc(sizeof(*tmplist));
if (!tmplist) {
free(newclient);
return -1; return -1;
} }
lxc_list_add_elem(tmplist, newclient); rsp.ret = lxc_add_state_client(fd, handler, (lxc_state_t *)req->data);
lxc_list_add_tail(&handler->state_clients, tmplist); if (rsp.ret < 0)
ERROR("Failed to add state client %d to state client list", fd);
TRACE("added state client %d to state client list", fd); else
TRACE("Added state client %d to state client list", fd);
return lxc_cmd_rsp_send(fd, &rsp); return lxc_cmd_rsp_send(fd, &rsp);
} }
...@@ -1013,17 +948,17 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req, ...@@ -1013,17 +948,17 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req,
typedef int (*callback)(int, struct lxc_cmd_req *, struct lxc_handler *); typedef int (*callback)(int, struct lxc_cmd_req *, struct lxc_handler *);
callback cb[LXC_CMD_MAX] = { callback cb[LXC_CMD_MAX] = {
[LXC_CMD_CONSOLE] = lxc_cmd_console_callback, [LXC_CMD_CONSOLE] = lxc_cmd_console_callback,
[LXC_CMD_CONSOLE_WINCH] = lxc_cmd_console_winch_callback, [LXC_CMD_CONSOLE_WINCH] = lxc_cmd_console_winch_callback,
[LXC_CMD_STOP] = lxc_cmd_stop_callback, [LXC_CMD_STOP] = lxc_cmd_stop_callback,
[LXC_CMD_GET_STATE] = lxc_cmd_get_state_callback, [LXC_CMD_GET_STATE] = lxc_cmd_get_state_callback,
[LXC_CMD_GET_INIT_PID] = lxc_cmd_get_init_pid_callback, [LXC_CMD_GET_INIT_PID] = lxc_cmd_get_init_pid_callback,
[LXC_CMD_GET_CLONE_FLAGS] = lxc_cmd_get_clone_flags_callback, [LXC_CMD_GET_CLONE_FLAGS] = lxc_cmd_get_clone_flags_callback,
[LXC_CMD_GET_CGROUP] = lxc_cmd_get_cgroup_callback, [LXC_CMD_GET_CGROUP] = lxc_cmd_get_cgroup_callback,
[LXC_CMD_GET_CONFIG_ITEM] = lxc_cmd_get_config_item_callback, [LXC_CMD_GET_CONFIG_ITEM] = lxc_cmd_get_config_item_callback,
[LXC_CMD_GET_NAME] = lxc_cmd_get_name_callback, [LXC_CMD_GET_NAME] = lxc_cmd_get_name_callback,
[LXC_CMD_GET_LXCPATH] = lxc_cmd_get_lxcpath_callback, [LXC_CMD_GET_LXCPATH] = lxc_cmd_get_lxcpath_callback,
[LXC_CMD_STATE_SERVER] = lxc_cmd_state_server_callback, [LXC_CMD_ADD_STATE_CLIENT] = lxc_cmd_add_state_client_callback,
}; };
if (req->cmd >= LXC_CMD_MAX) { if (req->cmd >= LXC_CMD_MAX) {
...@@ -1057,24 +992,30 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, ...@@ -1057,24 +992,30 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data,
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."); SYSERROR("Failed to receive data on command socket for \"%s\"",
lxc_cmd_str(req.cmd));
goto out_close; goto out_close;
} }
if (!ret) { if (!ret) {
DEBUG("Peer has disconnected."); 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\"",
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) {
ERROR("Received command data length %d is too large.", req.datalen); ERROR("Received command data length %d is too large for "
"command \"%s\"",
req.datalen, lxc_cmd_str(req.cmd));
ret = -1; ret = -1;
goto out_close; goto out_close;
} }
...@@ -1085,7 +1026,9 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, ...@@ -1085,7 +1026,9 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data,
reqdata = alloca(req.datalen); reqdata = alloca(req.datalen);
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 request."); WARN("Failed to receive full command request. Ignoring "
"request for \"%s\"",
lxc_cmd_str(req.cmd));
ret = -1; ret = -1;
goto out_close; goto out_close;
} }
...@@ -1156,7 +1099,8 @@ int lxc_cmd_init(const char *name, struct lxc_handler *handler, ...@@ -1156,7 +1099,8 @@ 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 (fill_sock_name(offset, len, name, lxcpath, NULL)) if (lxc_make_abstract_socket_name(offset, len, name, lxcpath, NULL,
"command"))
return -1; return -1;
fd = lxc_abstract_unix_open(path, SOCK_STREAM, 0); fd = lxc_abstract_unix_open(path, SOCK_STREAM, 0);
......
...@@ -24,6 +24,10 @@ ...@@ -24,6 +24,10 @@
#ifndef __LXC_COMMANDS_H #ifndef __LXC_COMMANDS_H
#define __LXC_COMMANDS_H #define __LXC_COMMANDS_H
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include "state.h" #include "state.h"
#define LXC_CMD_DATA_MAX (MAXPATHLEN * 2) #define LXC_CMD_DATA_MAX (MAXPATHLEN * 2)
...@@ -43,7 +47,7 @@ typedef enum { ...@@ -43,7 +47,7 @@ typedef enum {
LXC_CMD_GET_CONFIG_ITEM, LXC_CMD_GET_CONFIG_ITEM,
LXC_CMD_GET_NAME, LXC_CMD_GET_NAME,
LXC_CMD_GET_LXCPATH, LXC_CMD_GET_LXCPATH,
LXC_CMD_STATE_SERVER, LXC_CMD_ADD_STATE_CLIENT,
LXC_CMD_MAX, LXC_CMD_MAX,
} lxc_cmd_t; } lxc_cmd_t;
...@@ -85,8 +89,23 @@ extern char *lxc_cmd_get_lxcpath(const char *hashed_sock); ...@@ -85,8 +89,23 @@ extern char *lxc_cmd_get_lxcpath(const char *hashed_sock);
extern pid_t lxc_cmd_get_init_pid(const char *name, const char *lxcpath); extern pid_t lxc_cmd_get_init_pid(const char *name, const char *lxcpath);
extern int lxc_cmd_get_state(const char *name, const char *lxcpath); extern int lxc_cmd_get_state(const char *name, const char *lxcpath);
extern int lxc_cmd_stop(const char *name, const char *lxcpath); extern int lxc_cmd_stop(const char *name, const char *lxcpath);
extern int lxc_cmd_state_server(const char *name, const char *lxcpath,
lxc_state_t states[MAX_STATE]); /* lxc_cmd_add_state_client Register a new state client fd in the container's
* in-memory handler.
*
* @param[in] name Name of container to connect to.
* @param[in] lxcpath The lxcpath in which the container is running.
* @param[in] states The states to wait for.
* @param[out] state_client_fd The state client fd from which the state can be
* received.
* @return Return < 0 on error
* == MAX_STATE when state needs to retrieved
* via socket fd
* < MAX_STATE current container state
*/
extern int lxc_cmd_add_state_client(const char *name, const char *lxcpath,
lxc_state_t states[MAX_STATE],
int *state_client_fd);
struct lxc_epoll_descr; struct lxc_epoll_descr;
struct lxc_handler; struct lxc_handler;
......
/* liblxcapi
*
* Copyright © 2017 Christian Brauner <christian.brauner@ubuntu.com>.
* Copyright © 2017 Canonical Ltd.
*
* 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 _GNU_SOURCE
#define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "af_unix.h"
#include "commands.h"
#include "commands_utils.h"
#include "initutils.h"
#include "log.h"
#include "monitor.h"
#include "state.h"
#include "utils.h"
lxc_log_define(lxc_commands_utils, lxc);
int lxc_cmd_sock_rcv_state(int state_client_fd, int timeout)
{
int ret;
struct lxc_msg msg;
struct timeval out;
if (timeout >= 0) {
memset(&out, 0, sizeof(out));
out.tv_sec = timeout;
ret = setsockopt(state_client_fd, SOL_SOCKET, SO_RCVTIMEO,
(const void *)&out, sizeof(out));
if (ret < 0) {
SYSERROR("Failed to set %ds timeout on containter "
"state socket",
timeout);
return -1;
}
}
memset(&msg, 0, sizeof(msg));
again:
ret = recv(state_client_fd, &msg, sizeof(msg), 0);
if (ret < 0) {
if (errno == EINTR) {
TRACE("Caught EINTR; retrying");
goto again;
}
ERROR("failed to receive message: %s", strerror(errno));
return -1;
}
if (ret == 0) {
ERROR("length of message was 0");
return -1;
}
TRACE("received state %s from state client %d",
lxc_state2str(msg.value), state_client_fd);
return msg.value;
}
/* Register a new state client and retrieve state from command socket. */
int lxc_cmd_sock_get_state(const char *name, const char *lxcpath,
lxc_state_t states[MAX_STATE], int timeout)
{
int ret;
int state_client_fd;
ret = lxc_cmd_add_state_client(name, lxcpath, states, &state_client_fd);
if (ret < 0)
return -1;
if (ret < MAX_STATE)
return ret;
ret = lxc_cmd_sock_rcv_state(state_client_fd, timeout);
close(state_client_fd);
return ret;
}
int lxc_make_abstract_socket_name(char *path, int len, const char *lxcname,
const char *lxcpath,
const char *hashed_sock_name,
const char *suffix)
{
const char *name;
char *tmppath;
size_t tmplen;
uint64_t hash;
int ret;
name = lxcname;
if (!name)
name = "";
if (hashed_sock_name != NULL) {
ret =
snprintf(path, len, "lxc/%s/%s", hashed_sock_name, suffix);
if (ret < 0 || ret >= len) {
ERROR("Failed to create abstract socket name");
return -1;
}
return 0;
}
if (!lxcpath) {
lxcpath = lxc_global_config_value("lxc.lxcpath");
if (!lxcpath) {
ERROR("Failed to allocate memory");
return -1;
}
}
ret = snprintf(path, len, "%s/%s/%s", lxcpath, name, suffix);
if (ret < 0) {
ERROR("Failed to create abstract socket name");
return -1;
}
if (ret < len)
return 0;
/* ret >= len; lxcpath or name is too long. hash both */
tmplen = strlen(name) + strlen(lxcpath) + 2;
tmppath = alloca(tmplen);
ret = snprintf(tmppath, tmplen, "%s/%s", lxcpath, name);
if (ret < 0 || (size_t)ret >= tmplen) {
ERROR("Failed to create abstract socket name");
return -1;
}
hash = fnv_64a_buf(tmppath, ret, FNV1A_64_INIT);
ret = snprintf(path, len, "lxc/%016" PRIx64 "/%s", hash, suffix);
if (ret < 0 || ret >= len) {
ERROR("Failed to create abstract socket name");
return -1;
}
return 0;
}
int lxc_cmd_connect(const char *name, const char *lxcpath,
const char *hashed_sock_name)
{
int ret, client_fd;
char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = {0};
char *offset = &path[1];
size_t len = sizeof(path) - 2;
/* -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.
* Although null termination isn't required by the API, we do it anyway
* because we print the sockname out sometimes.
*/
ret = lxc_make_abstract_socket_name(offset, len, name, lxcpath,
hashed_sock_name, "command");
if (ret < 0)
return -1;
/* Get new client fd. */
client_fd = lxc_abstract_unix_connect(path);
if (client_fd < 0) {
if (errno == ECONNREFUSED)
return -ECONNREFUSED;
return -1;
}
return client_fd;
}
int lxc_add_state_client(int state_client_fd, struct lxc_handler *handler,
lxc_state_t states[MAX_STATE])
{
struct state_client *newclient;
struct lxc_list *tmplist;
newclient = malloc(sizeof(*newclient));
if (!newclient)
return -ENOMEM;
/* copy requested states */
memcpy(newclient->states, states, sizeof(newclient->states));
newclient->clientfd = state_client_fd;
tmplist = malloc(sizeof(*tmplist));
if (!tmplist) {
free(newclient);
return -ENOMEM;
}
lxc_list_add_elem(tmplist, newclient);
lxc_list_add_tail(&handler->state_clients, tmplist);
TRACE("added state client %d to state client list", state_client_fd);
return 0;
}
/* liblxcapi
*
* Copyright © 2017 Christian Brauner <christian.brauner@ubuntu.com>.
* Copyright © 2017 Canonical Ltd.
*
* 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.
*/
#ifndef __LXC_COMMANDS_UTILS_H
#define __LXC_COMMANDS_UTILS_H
#include <stdio.h>
#include "state.h"
#include "commands.h"
int lxc_make_abstract_socket_name(char *path, int len, const char *lxcname,
const char *lxcpath,
const char *hashed_sock_name,
const char *suffix);
/* lxc_cmd_sock_get_state Register a new state client fd in the container's
* in-memory handler and retrieve the requested
* states.
*
* @param[in] name Name of container to connect to.
* @param[in] lxcpath The lxcpath in which the container is running.
* @param[in] states The states to wait for.
* @return Return < 0 on error
* < MAX_STATE current container state
*/
extern int lxc_cmd_sock_get_state(const char *name, const char *lxcpath,
lxc_state_t states[MAX_STATE], int timeout);
/* lxc_cmd_sock_rcv_state Retrieve the requested state from a state client
* fd registerd in the container's in-memory
* handler.
*
* @param[int] state_client_fd The state client fd from which the state can be
* received.
* @return Return < 0 on error
* < MAX_STATE current container state
*/
extern int lxc_cmd_sock_rcv_state(int state_client_fd, int timeout);
/* lxc_add_state_client Add a new state client to the container's
* in-memory handler.
*
* @param[int] state_client_fd The state client fd to add.
* @param[int] handler The container's in-memory handler.
* @param[in] states The states to wait for.
*
* @return Return < 0 on error
* 0 on success
*/
extern int lxc_add_state_client(int state_client_fd,
struct lxc_handler *handler,
lxc_state_t states[MAX_STATE]);
/* lxc_cmd_connect Connect to the container's command socket.
*
* @param[in] name Name of container to connect to.
* @param[in] lxcpath The lxcpath in which the container is running.
* @param[in] hashed_sock_name The hashed name of the socket (optional). Can be
* NULL.
*
* @return Return < 0 on error
* >= 0 client fd
*/
extern int lxc_cmd_connect(const char *name, const char *lxcpath,
const char *hashed_sock_name);
#endif /* __LXC_COMMANDS_UTILS_H */
...@@ -797,7 +797,7 @@ static void do_restore(struct lxc_container *c, int status_pipe, struct migrate_ ...@@ -797,7 +797,7 @@ static void do_restore(struct lxc_container *c, int status_pipe, struct migrate_
close(fd); close(fd);
} }
handler = lxc_init_handler(c->name, c->lxc_conf, c->config_path); handler = lxc_init_handler(c->name, c->lxc_conf, c->config_path, false);
if (!handler) if (!handler)
goto out; goto out;
......
...@@ -116,7 +116,7 @@ int lxc_execute(const char *name, char *const argv[], int quiet, ...@@ -116,7 +116,7 @@ int lxc_execute(const char *name, char *const argv[], int quiet,
{ {
struct execute_args args = {.argv = argv, .quiet = quiet}; struct execute_args args = {.argv = argv, .quiet = quiet};
if (lxc_check_inherited(handler->conf, false, handler->conf->maincmd_fd)) if (lxc_check_inherited(handler->conf, false, &handler->conf->maincmd_fd, 1))
return -1; return -1;
handler->conf->is_execute = 1; handler->conf->is_execute = 1;
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include "af_unix.h"
#include "attach.h" #include "attach.h"
#include "bdev.h" #include "bdev.h"
#include "lxcoverlay.h" #include "lxcoverlay.h"
...@@ -46,6 +47,7 @@ ...@@ -46,6 +47,7 @@
#include "conf.h" #include "conf.h"
#include "config.h" #include "config.h"
#include "commands.h" #include "commands.h"
#include "commands_utils.h"
#include "confile.h" #include "confile.h"
#include "confile_legacy.h" #include "confile_legacy.h"
#include "console.h" #include "console.h"
...@@ -611,23 +613,6 @@ static bool do_lxcapi_wait(struct lxc_container *c, const char *state, int timeo ...@@ -611,23 +613,6 @@ static bool do_lxcapi_wait(struct lxc_container *c, const char *state, int timeo
WRAP_API_2(bool, lxcapi_wait, const char *, int) WRAP_API_2(bool, lxcapi_wait, const char *, int)
static bool do_wait_on_daemonized_start(struct lxc_container *c, int pid)
{
/* we'll probably want to make this timeout configurable? */
int timeout = 5, ret, status;
/*
* our child is going to fork again, then exit. reap the
* child
*/
ret = waitpid(pid, &status, 0);
if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
DEBUG("failed waiting for first dual-fork child");
return do_lxcapi_wait(c, "RUNNING", timeout);
}
WRAP_API_1(bool, wait_on_daemonized_start, int)
static bool am_single_threaded(void) static bool am_single_threaded(void)
{ {
struct dirent *direntp; struct dirent *direntp;
...@@ -714,6 +699,79 @@ static void free_init_cmd(char **argv) ...@@ -714,6 +699,79 @@ static void free_init_cmd(char **argv)
free(argv); free(argv);
} }
static int lxc_rcv_status(int state_socket)
{
int ret;
int state = -1;
struct timeval timeout = {0};
/* Set 5 second timeout to prevent hanging forever in case something
* goes wrong. 5 seconds is a long time to get into RUNNING state.
*/
timeout.tv_sec = 5;
ret = setsockopt(state_socket, SOL_SOCKET, SO_RCVTIMEO,
(const void *)&timeout, sizeof(timeout));
if (ret < 0) {
SYSERROR("Failed to set 5s timeout on containter state socket");
return -1;
}
again:
/* Receive container state. */
ret = lxc_abstract_unix_rcv_credential(state_socket, &state,
sizeof(int));
if (ret <= 0) {
if (errno != EINTR)
return -1;
TRACE("Caught EINTR; retrying");
goto again;
}
return state;
}
static bool wait_on_daemonized_start(struct lxc_handler *handler, int pid)
{
int state, status;
/* Close write end of the socket pair. */
close(handler->state_socket_pair[1]);
handler->state_socket_pair[1] = -1;
state = lxc_rcv_status(handler->state_socket_pair[0]);
/* Close read end of the socket pair. */
close(handler->state_socket_pair[0]);
handler->state_socket_pair[0] = -1;
/* The first child is going to fork() again and then exits. So we reap
* the first child here.
*/
if (waitpid(pid, &status, 0) < 0)
DEBUG("Failed waiting on first child");
else if (!WIFEXITED(status))
DEBUG("Failed to retrieve exit status of first child");
else if (WEXITSTATUS(status) != 0)
DEBUG("First child exited with: %d", WEXITSTATUS(status));
if (state < 0) {
SYSERROR("Failed to receive the container state");
return false;
}
/* If we receive anything else then running we know that the container
* failed to start.
*/
if (state != RUNNING) {
ERROR("Received container state \"%s\" instead of \"RUNNING\"",
lxc_state2str(state));
return false;
}
TRACE("Container is in \"RUNNING\" state");
return true;
}
static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const argv[]) static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const argv[])
{ {
int ret; int ret;
...@@ -727,31 +785,32 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -727,31 +785,32 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
}; };
char **init_cmd = NULL; char **init_cmd = NULL;
/* container exists */ /* container does exist */
if (!c) if (!c)
return false; return false;
/* If anything fails before we set error_num, we want an error in there */ /* If anything fails before we set error_num, we want an error in there.
*/
c->error_num = 1; c->error_num = 1;
/* container has not been setup */ /* Container has not been setup. */
if (!c->lxc_conf) if (!c->lxc_conf)
return false; return false;
ret = ongoing_create(c); ret = ongoing_create(c);
if (ret < 0) { if (ret < 0) {
ERROR("Error checking for incomplete creation"); ERROR("Failed checking for incomplete container creation");
return false; return false;
} else if (ret == 1) { } else if (ret == 1) {
ERROR("Error: creation of %s is ongoing", c->name); ERROR("Ongoing container creation detected");
return false; return false;
} else if (ret == 2) { } else if (ret == 2) {
ERROR("Error: %s creation was not completed", c->name); ERROR("Failed to create container");
do_lxcapi_destroy(c); do_lxcapi_destroy(c);
return false; return false;
} }
/* is this app meant to be run through lxcinit, as in lxc-execute? */ /* Is this app meant to be run through lxcinit, as in lxc-execute? */
if (useinit && !argv) if (useinit && !argv)
return false; return false;
...@@ -761,7 +820,7 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -761,7 +820,7 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
daemonize = c->daemonize; daemonize = c->daemonize;
/* initialize handler */ /* initialize handler */
handler = lxc_init_handler(c->name, conf, c->config_path); handler = lxc_init_handler(c->name, conf, c->config_path, daemonize);
container_mem_unlock(c); container_mem_unlock(c);
if (!handler) if (!handler)
return false; return false;
...@@ -771,25 +830,29 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -771,25 +830,29 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
ret = lxc_execute(c->name, argv, 1, handler, c->config_path, ret = lxc_execute(c->name, argv, 1, handler, c->config_path,
daemonize); daemonize);
c->error_num = ret; c->error_num = ret;
return ret == 0 ? true : false;
if (ret != 0)
return false;
return true;
} }
/* if no argv was passed in, use lxc.init_cmd if provided in /* If no argv was passed in, use lxc.init_cmd if provided in the
* configuration */ * configuration
*/
if (!argv) if (!argv)
argv = init_cmd = split_init_cmd(conf->init_cmd); argv = init_cmd = split_init_cmd(conf->init_cmd);
/* ... and otherwise use default_args */ /* ... otherwise use default_args. */
if (!argv) if (!argv)
argv = default_args; argv = default_args;
/* /* I'm not sure what locks we want here.Any? Is liblxc's locking enough
* say, I'm not sure - what locks do we want here? Any? * here to protect the on disk container? We don't want to exclude
* Is liblxc's locking enough here to protect the on disk * things like lxc_info while the container is running.
* container? We don't want to exclude things like lxc_info */
* while container is running...
*/
if (daemonize) { if (daemonize) {
bool started;
char title[2048]; char title[2048];
pid_t pid; pid_t pid;
...@@ -800,45 +863,82 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -800,45 +863,82 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
return false; return false;
} }
/* first parent */
if (pid != 0) { if (pid != 0) {
/* Set to NULL because we don't want father unlink /* Set to NULL because we don't want father unlink
* the PID file, child will do the free and unlink. * the PID file, child will do the free and unlink.
*/ */
c->pidfile = NULL; c->pidfile = NULL;
close(handler->conf->maincmd_fd);
return wait_on_daemonized_start(c, pid); /* Wait for container to tell us whether it started
* successfully.
*/
started = wait_on_daemonized_start(handler, pid);
free_init_cmd(init_cmd);
lxc_free_handler(handler);
return started;
} }
/* first child */
/* We don't really care if this doesn't print all the /* We don't really care if this doesn't print all the
* characters; all that it means is that the proctitle will be * characters. All that it means is that the proctitle will be
* ugly. Similarly, we also don't care if setproctitle() * ugly. Similarly, we also don't care if setproctitle() fails.
* fails. */ * */
snprintf(title, sizeof(title), "[lxc monitor] %s %s", c->config_path, c->name); snprintf(title, sizeof(title), "[lxc monitor] %s %s", c->config_path, c->name);
INFO("Attempting to set proc title to %s", title); INFO("Attempting to set proc title to %s", title);
setproctitle(title); setproctitle(title);
/* second fork to be reparented by init */ /* We fork() a second time to be reparented to init. Like
* POSIX's daemon() function we change to "/" and redirect
* std{in,out,err} to /dev/null.
*/
pid = fork(); pid = fork();
if (pid < 0) { if (pid < 0) {
SYSERROR("Error doing dual-fork"); SYSERROR("Failed to fork first child process");
exit(1); exit(EXIT_FAILURE);
} }
if (pid != 0)
exit(0); /* second parent */
/* like daemon(), chdir to / and redirect 0,1,2 to /dev/null */ if (pid != 0) {
if (chdir("/")) { free_init_cmd(init_cmd);
SYSERROR("Error chdir()ing to /."); lxc_free_handler(handler);
exit(1); exit(EXIT_SUCCESS);
} }
lxc_check_inherited(conf, true, handler->conf->maincmd_fd);
if (null_stdfds() < 0) { /* second child */
ERROR("failed to close fds");
exit(1); /* change to / directory */
ret = chdir("/");
if (ret < 0) {
SYSERROR("Failed to change to \"/\" directory");
exit(EXIT_FAILURE);
}
ret = lxc_check_inherited(conf, true,
(int[]){handler->conf->maincmd_fd,
handler->state_socket_pair[0],
handler->state_socket_pair[1]},
3);
if (ret < 0)
exit(EXIT_FAILURE);
/* redirect std{in,out,err} to /dev/null */
ret = null_stdfds();
if (ret < 0) {
ERROR("Failed to redirect std{in,out,err} to /dev/null");
exit(EXIT_FAILURE);
} }
setsid();
/* become session leader */
ret = setsid();
if (ret < 0)
TRACE("Process %d is already process group leader", getpid());
} else { } else {
if (!am_single_threaded()) { if (!am_single_threaded()) {
ERROR("Cannot start non-daemonized container when threaded"); ERROR("Cannot start non-daemonized container when threaded");
free_init_cmd(init_cmd);
lxc_free_handler(handler); lxc_free_handler(handler);
return false; return false;
} }
...@@ -855,7 +955,7 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -855,7 +955,7 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
free_init_cmd(init_cmd); free_init_cmd(init_cmd);
lxc_free_handler(handler); lxc_free_handler(handler);
if (daemonize) if (daemonize)
exit(1); exit(EXIT_FAILURE);
return false; return false;
} }
...@@ -866,7 +966,7 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -866,7 +966,7 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
free_init_cmd(init_cmd); free_init_cmd(init_cmd);
lxc_free_handler(handler); lxc_free_handler(handler);
if (daemonize) if (daemonize)
exit(1); exit(EXIT_FAILURE);
return false; return false;
} }
...@@ -878,45 +978,54 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a ...@@ -878,45 +978,54 @@ static bool do_lxcapi_start(struct lxc_container *c, int useinit, char * const a
/* Unshare the mount namespace if requested */ /* Unshare the mount namespace if requested */
if (conf->monitor_unshare) { if (conf->monitor_unshare) {
if (unshare(CLONE_NEWNS)) { ret = unshare(CLONE_NEWNS);
if (ret < 0) {
SYSERROR("failed to unshare mount namespace"); SYSERROR("failed to unshare mount namespace");
free_init_cmd(init_cmd);
lxc_free_handler(handler); lxc_free_handler(handler);
return false; ret = 1;
goto on_error;
} }
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL)) {
ret = mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL);
if (ret < 0) {
SYSERROR("Failed to make / rslave at startup"); SYSERROR("Failed to make / rslave at startup");
free_init_cmd(init_cmd);
lxc_free_handler(handler); lxc_free_handler(handler);
return false; ret = 1;
goto on_error;
} }
} }
reboot: reboot:
if (conf->reboot == 2) { if (conf->reboot == 2) {
/* initialize handler */ /* initialize handler */
handler = lxc_init_handler(c->name, conf, c->config_path); handler = lxc_init_handler(c->name, conf, c->config_path, daemonize);
if (!handler) if (!handler) {
goto out; ret = 1;
goto on_error;
}
} }
if (lxc_check_inherited(conf, daemonize, handler->conf->maincmd_fd)) { ret = lxc_check_inherited(conf, daemonize,
ERROR("Inherited fds found"); (int[]){handler->conf->maincmd_fd,
handler->state_socket_pair[0],
handler->state_socket_pair[1]},
3);
if (ret < 0) {
lxc_free_handler(handler); lxc_free_handler(handler);
ret = 1; ret = 1;
goto out; goto on_error;
} }
ret = lxc_start(c->name, argv, handler, c->config_path, daemonize); ret = lxc_start(c->name, argv, handler, c->config_path, daemonize);
c->error_num = ret; c->error_num = ret;
if (conf->reboot == 1) { if (conf->reboot == 1) {
INFO("container requested reboot"); INFO("Container requested reboot");
conf->reboot = 2; conf->reboot = 2;
goto reboot; goto reboot;
} }
out: on_error:
if (c->pidfile) { if (c->pidfile) {
unlink(c->pidfile); unlink(c->pidfile);
free(c->pidfile); free(c->pidfile);
...@@ -924,9 +1033,15 @@ out: ...@@ -924,9 +1033,15 @@ out:
} }
free_init_cmd(init_cmd); free_init_cmd(init_cmd);
if (daemonize) if (daemonize && ret != 0)
exit(ret == 0 ? true : false); exit(EXIT_FAILURE);
return (ret == 0 ? true : false); else if (daemonize)
exit(EXIT_SUCCESS);
if (ret != 0)
return false;
return true;
} }
static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv[]) static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv[])
...@@ -1634,9 +1749,11 @@ WRAP_API(bool, lxcapi_reboot) ...@@ -1634,9 +1749,11 @@ WRAP_API(bool, lxcapi_reboot)
static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout) static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout)
{ {
bool retv; int ret, state_client_fd = -1;
bool retv = false;
pid_t pid; pid_t pid;
int haltsignal = SIGPWR; int haltsignal = SIGPWR;
lxc_state_t states[MAX_STATE] = {0};
if (!c) if (!c)
return false; return false;
...@@ -1656,10 +1773,33 @@ static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout) ...@@ -1656,10 +1773,33 @@ static bool do_lxcapi_shutdown(struct lxc_container *c, int timeout)
INFO("Using signal number '%d' as halt signal.", haltsignal); INFO("Using signal number '%d' as halt signal.", haltsignal);
/* Add a new state client before sending the shutdown signal so that we
* don't miss a state.
*/
states[STOPPED] = 1;
ret = lxc_cmd_add_state_client(c->name, c->config_path, states,
&state_client_fd);
/* Send shutdown signal to container. */
if (kill(pid, haltsignal) < 0) if (kill(pid, haltsignal) < 0)
WARN("Could not send signal %d to pid %d.", haltsignal, pid); WARN("Could not send signal %d to pid %d.", haltsignal, pid);
retv = do_lxcapi_wait(c, "STOPPED", timeout); /* Retrieve the state. */
if (state_client_fd >= 0) {
int state;
state = lxc_cmd_sock_rcv_state(state_client_fd, timeout);
close(state_client_fd);
if (state != STOPPED)
return false;
retv = true;
} else if (ret == STOPPED) {
TRACE("Container is already stopped");
retv = true;
} else {
TRACE("Received state \"%s\" instead of expected \"STOPPED\"",
lxc_state2str(ret));
}
return retv; return retv;
} }
......
...@@ -370,7 +370,7 @@ int lxc_monitord_spawn(const char *lxcpath) ...@@ -370,7 +370,7 @@ int lxc_monitord_spawn(const char *lxcpath)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
lxc_check_inherited(NULL, true, pipefd[1]); lxc_check_inherited(NULL, true, &pipefd[1], 1);
if (null_stdfds() < 0) { if (null_stdfds() < 0) {
SYSERROR("Failed to dup2() standard file descriptors to /dev/null."); SYSERROR("Failed to dup2() standard file descriptors to /dev/null.");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
......
...@@ -1410,7 +1410,7 @@ static bool is_ovs_bridge(const char *bridge) ...@@ -1410,7 +1410,7 @@ static bool is_ovs_bridge(const char *bridge)
*/ */
static void ovs_cleanup_nic(const char *lxcpath, const char *name, const char *bridge, const char *nic) static void ovs_cleanup_nic(const char *lxcpath, const char *name, const char *bridge, const char *nic)
{ {
if (lxc_check_inherited(NULL, true, -1) < 0) if (lxc_check_inherited(NULL, true, &(int){-1}, 1) < 0)
return; return;
if (lxc_wait(name, "STOPPED", -1, lxcpath) < 0) if (lxc_wait(name, "STOPPED", -1, lxcpath) < 0)
return; return;
......
...@@ -67,6 +67,7 @@ ...@@ -67,6 +67,7 @@
#include "caps.h" #include "caps.h"
#include "cgroup.h" #include "cgroup.h"
#include "commands.h" #include "commands.h"
#include "commands_utils.h"
#include "conf.h" #include "conf.h"
#include "console.h" #include "console.h"
#include "error.h" #include "error.h"
...@@ -189,18 +190,12 @@ static int match_fd(int fd) ...@@ -189,18 +190,12 @@ static int match_fd(int fd)
return (fd == 0 || fd == 1 || fd == 2); return (fd == 0 || fd == 1 || fd == 2);
} }
/* Check for any fds we need to close. int lxc_check_inherited(struct lxc_conf *conf, bool closeall,
* - If fd_to_ignore != -1, then if we find that fd open we will ignore it. int *fds_to_ignore, size_t len_fds)
* - By default we warn about open fds we find.
* - If closeall is true, we will close open fds.
* - If lxc-start was passed "-C", then conf->close_all_fds will be true, in
* which case we also close all open fds.
* - A daemonized container will always pass closeall=true.
*/
int lxc_check_inherited(struct lxc_conf *conf, bool closeall, int fd_to_ignore)
{ {
struct dirent *direntp; struct dirent *direntp;
int fd, fddir; int fd, fddir;
size_t i;
DIR *dir; DIR *dir;
if (conf && conf->close_all_fds) if (conf && conf->close_all_fds)
...@@ -230,7 +225,12 @@ restart: ...@@ -230,7 +225,12 @@ restart:
continue; continue;
} }
if (fd == fddir || fd == lxc_log_fd || fd == fd_to_ignore) for (i = 0; i < len_fds; i++)
if (fds_to_ignore[i] == fd)
break;
if (fd == fddir || fd == lxc_log_fd ||
(i < len_fds && fd == fds_to_ignore[i]))
continue; continue;
if (current_config && fd == current_config->logfd) if (current_config && fd == current_config->logfd)
...@@ -343,8 +343,9 @@ static int signal_handler(int fd, uint32_t events, void *data, ...@@ -343,8 +343,9 @@ static int signal_handler(int fd, uint32_t events, void *data,
return 1; return 1;
} }
int lxc_set_state(const char *name, struct lxc_handler *handler, static int lxc_serve_state_clients(const char *name,
lxc_state_t state) struct lxc_handler *handler,
lxc_state_t state)
{ {
ssize_t ret; ssize_t ret;
struct lxc_list *cur, *next; struct lxc_list *cur, *next;
...@@ -352,8 +353,9 @@ int lxc_set_state(const char *name, struct lxc_handler *handler, ...@@ -352,8 +353,9 @@ int lxc_set_state(const char *name, struct lxc_handler *handler,
struct lxc_msg msg = {.type = lxc_msg_state, .value = state}; struct lxc_msg msg = {.type = lxc_msg_state, .value = state};
process_lock(); process_lock();
/* Only set state under process lock held so that we don't cause /* Only set state under process lock held so that we don't cause
* lxc_cmd_state_server() to miss a state. * lxc_cmd_add_state_client() to miss a state.
*/ */
handler->state = state; handler->state = state;
TRACE("set container state to %s", lxc_state2str(state)); TRACE("set container state to %s", lxc_state2str(state));
...@@ -382,9 +384,11 @@ int lxc_set_state(const char *name, struct lxc_handler *handler, ...@@ -382,9 +384,11 @@ int lxc_set_state(const char *name, struct lxc_handler *handler,
again: again:
ret = send(client->clientfd, &msg, sizeof(msg), 0); ret = send(client->clientfd, &msg, sizeof(msg), 0);
if (ret < 0) { if (ret <= 0) {
if (errno == EINTR) if (errno == EINTR) {
TRACE("Caught EINTR; retrying");
goto again; goto again;
}
ERROR("failed to send message to client"); ERROR("failed to send message to client");
} }
...@@ -397,6 +401,60 @@ int lxc_set_state(const char *name, struct lxc_handler *handler, ...@@ -397,6 +401,60 @@ int lxc_set_state(const char *name, struct lxc_handler *handler,
} }
process_unlock(); process_unlock();
return 0;
}
static int lxc_serve_state_socket_pair(const char *name,
struct lxc_handler *handler,
lxc_state_t state)
{
ssize_t ret;
if (!handler->backgrounded ||
handler->state_socket_pair[1] < 0 ||
state == STARTING)
return 0;
/* Close read end of the socket pair. */
close(handler->state_socket_pair[0]);
handler->state_socket_pair[0] = -1;
again:
ret = lxc_abstract_unix_send_credential(handler->state_socket_pair[1],
&(int){state}, sizeof(int));
if (ret != sizeof(int)) {
if (errno == EINTR)
goto again;
SYSERROR("Failed to send state to %d",
handler->state_socket_pair[1]);
return -1;
}
TRACE("Sent container state \"%s\" to %d", lxc_state2str(state),
handler->state_socket_pair[1]);
/* Close write end of the socket pair. */
close(handler->state_socket_pair[1]);
handler->state_socket_pair[1] = -1;
return 0;
}
int lxc_set_state(const char *name, struct lxc_handler *handler,
lxc_state_t state)
{
int ret;
ret = lxc_serve_state_socket_pair(name, handler, state);
if (ret < 0) {
ERROR("Failed to synchronize via anonymous pair of unix sockets");
return -1;
}
ret = lxc_serve_state_clients(name, handler, state);
if (ret < 0)
return -1;
/* This function will try to connect to the legacy lxc-monitord state /* This function will try to connect to the legacy lxc-monitord state
* server and only exists for backwards compatibility. * server and only exists for backwards compatibility.
*/ */
...@@ -459,6 +517,12 @@ void lxc_free_handler(struct lxc_handler *handler) ...@@ -459,6 +517,12 @@ void lxc_free_handler(struct lxc_handler *handler)
if (handler->conf && handler->conf->maincmd_fd) if (handler->conf && handler->conf->maincmd_fd)
close(handler->conf->maincmd_fd); close(handler->conf->maincmd_fd);
if (handler->state_socket_pair[0] >= 0)
close(handler->state_socket_pair[0]);
if (handler->state_socket_pair[1] >= 0)
close(handler->state_socket_pair[1]);
if (handler->name) if (handler->name)
free(handler->name); free(handler->name);
...@@ -467,9 +531,9 @@ void lxc_free_handler(struct lxc_handler *handler) ...@@ -467,9 +531,9 @@ void lxc_free_handler(struct lxc_handler *handler)
} }
struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf, struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf,
const char *lxcpath) const char *lxcpath, bool daemonize)
{ {
int i; int i, ret;
struct lxc_handler *handler; struct lxc_handler *handler;
handler = malloc(sizeof(*handler)); handler = malloc(sizeof(*handler));
...@@ -484,6 +548,7 @@ struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf, ...@@ -484,6 +548,7 @@ struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf,
handler->conf = conf; handler->conf = conf;
handler->lxcpath = lxcpath; handler->lxcpath = lxcpath;
handler->pinfd = -1; handler->pinfd = -1;
handler->state_socket_pair[0] = handler->state_socket_pair[1] = -1;
lxc_list_init(&handler->state_clients); lxc_list_init(&handler->state_clients);
for (i = 0; i < LXC_NS_MAX; i++) for (i = 0; i < LXC_NS_MAX; i++)
...@@ -495,6 +560,22 @@ struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf, ...@@ -495,6 +560,22 @@ struct lxc_handler *lxc_init_handler(const char *name, struct lxc_conf *conf,
goto on_error; goto on_error;
} }
if (daemonize && !handler->conf->reboot) {
/* Create socketpair() to synchronize on daemonized startup.
* When the container reboots we don't need to synchronize again
* currently so don't open another socketpair().
*/
ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
handler->state_socket_pair);
if (ret < 0) {
ERROR("Failed to create anonymous pair of unix sockets");
goto on_error;
}
TRACE("Created anonymous pair {%d,%d} of unix sockets",
handler->state_socket_pair[0],
handler->state_socket_pair[1]);
}
if (lxc_cmd_init(name, handler, lxcpath)) { if (lxc_cmd_init(name, handler, lxcpath)) {
ERROR("failed to set up command socket"); ERROR("failed to set up command socket");
goto on_error; goto on_error;
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
#include <signal.h> #include <signal.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdbool.h> #include <stdbool.h>
#include "conf.h" #include "conf.h"
...@@ -50,6 +52,10 @@ struct lxc_handler { ...@@ -50,6 +52,10 @@ struct lxc_handler {
bool backgrounded; // indicates whether should we close std{in,out,err} on start bool backgrounded; // indicates whether should we close std{in,out,err} on start
int nsfd[LXC_NS_MAX]; int nsfd[LXC_NS_MAX];
int netnsfd; int netnsfd;
/* The socketpair() fds used to wait on successful daemonized
* startup.
*/
int state_socket_pair[2];
struct lxc_list state_clients; struct lxc_list state_clients;
}; };
...@@ -68,12 +74,21 @@ extern int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_stat ...@@ -68,12 +74,21 @@ extern int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_stat
extern void lxc_abort(const char *name, struct lxc_handler *handler); extern void lxc_abort(const char *name, struct lxc_handler *handler);
extern struct lxc_handler *lxc_init_handler(const char *name, extern struct lxc_handler *lxc_init_handler(const char *name,
struct lxc_conf *conf, struct lxc_conf *conf,
const char *lxcpath); const char *lxcpath,
bool daemonize);
extern void lxc_free_handler(struct lxc_handler *handler); extern void lxc_free_handler(struct lxc_handler *handler);
extern int lxc_init(const char *name, struct lxc_handler *handler); extern int lxc_init(const char *name, struct lxc_handler *handler);
extern void lxc_fini(const char *name, struct lxc_handler *handler); extern void lxc_fini(const char *name, struct lxc_handler *handler);
extern int lxc_check_inherited(struct lxc_conf *conf, bool closeall, int fd_to_ignore); /* lxc_check_inherited: Check for any open file descriptors and close them if
* requested.
* @param[in] conf The container's configuration.
* @param[in] closeall Whether we should close all open file descriptors.
* @param[in] fds_to_ignore Array of file descriptors to ignore.
* @param[in] len_fds Length of fds_to_ignore array.
*/
extern int lxc_check_inherited(struct lxc_conf *conf, bool closeall,
int *fds_to_ignore, size_t len_fds);
int __lxc_start(const char *, struct lxc_handler *, struct lxc_operations *, int __lxc_start(const char *, struct lxc_handler *, struct lxc_operations *,
void *, const char *, bool); void *, const char *, bool);
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "cgroup.h" #include "cgroup.h"
#include "commands.h" #include "commands.h"
#include "commands_utils.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include "lxc.h" #include "lxc.h"
...@@ -114,7 +115,7 @@ extern int lxc_wait(const char *lxcname, const char *states, int timeout, ...@@ -114,7 +115,7 @@ extern int lxc_wait(const char *lxcname, const char *states, int timeout,
if (fillwaitedstates(states, s)) if (fillwaitedstates(states, s))
return -1; return -1;
state = lxc_cmd_state_server(lxcname, lxcpath, s); state = lxc_cmd_sock_get_state(lxcname, lxcpath, s, timeout);
if (state < 0) { if (state < 0) {
SYSERROR("failed to receive state from monitor"); SYSERROR("failed to receive state from monitor");
return -1; return -1;
......
...@@ -178,6 +178,8 @@ int main(int argc, char *argv[]) ...@@ -178,6 +178,8 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
if (!pid) { if (!pid) {
int ret;
/* restore default signal handlers */ /* restore default signal handlers */
for (i = 1; i < NSIG; i++) for (i = 1; i < NSIG; i++)
signal(i, SIG_DFL); signal(i, SIG_DFL);
...@@ -189,9 +191,9 @@ int main(int argc, char *argv[]) ...@@ -189,9 +191,9 @@ int main(int argc, char *argv[])
NOTICE("About to exec '%s'", aargv[0]); NOTICE("About to exec '%s'", aargv[0]);
execvp(aargv[0], aargv); ret = execvp(aargv[0], aargv);
ERROR("Failed to exec: '%s' : %s", aargv[0], strerror(errno)); ERROR("Failed to exec: '%s' : %s", aargv[0], strerror(errno));
exit(err); exit(ret);
} }
/* let's process the signals now */ /* let's process the signals now */
......
...@@ -23,11 +23,13 @@ ...@@ -23,11 +23,13 @@
#include "config.h" #include "config.h"
#define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */
#include <ctype.h> #include <ctype.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <grp.h> #include <grp.h>
#include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
......
...@@ -26,6 +26,7 @@ lxc_test_apparmor_SOURCES = aa.c ...@@ -26,6 +26,7 @@ lxc_test_apparmor_SOURCES = aa.c
lxc_test_utils_SOURCES = lxc-test-utils.c lxctest.h lxc_test_utils_SOURCES = lxc-test-utils.c lxctest.h
lxc_test_parse_config_file_SOURCES = parse_config_file.c lxctest.h lxc_test_parse_config_file_SOURCES = parse_config_file.c lxctest.h
lxc_test_config_jump_table_SOURCES = config_jump_table.c lxctest.h lxc_test_config_jump_table_SOURCES = config_jump_table.c lxctest.h
lxc_test_shortlived_SOURCES = shortlived.c
AM_CFLAGS=-DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \ AM_CFLAGS=-DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \
-DLXCPATH=\"$(LXCPATH)\" \ -DLXCPATH=\"$(LXCPATH)\" \
...@@ -54,7 +55,7 @@ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \ ...@@ -54,7 +55,7 @@ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
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 \
lxc-test-config-jump-table lxc-test-config-jump-table lxc-test-shortlived
bin_SCRIPTS = lxc-test-automount \ bin_SCRIPTS = lxc-test-automount \
lxc-test-autostart \ lxc-test-autostart \
...@@ -107,6 +108,7 @@ EXTRA_DIST = \ ...@@ -107,6 +108,7 @@ EXTRA_DIST = \
may_control.c \ may_control.c \
parse_config_file.c \ parse_config_file.c \
saveconfig.c \ saveconfig.c \
shortlived.c \
shutdowntest.c \ shutdowntest.c \
snapshot.c \ snapshot.c \
startone.c startone.c
......
/* liblxcapi
*
* Copyright © 2017 Christian Brauner <christian.brauner@ubuntu.com>.
* Copyright © 2017 Canonical Ltd.
*
* 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.
*/
#include <lxc/lxccontainer.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#define MYNAME "shortlived"
static int destroy_container(void)
{
int status, ret;
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return -1;
}
if (pid == 0) {
ret = execlp("lxc-destroy", "lxc-destroy", "-f", "-n", MYNAME, NULL);
// Should not return
perror("execl");
exit(1);
}
again:
ret = waitpid(pid, &status, 0);
if (ret == -1) {
if (errno == EINTR)
goto again;
perror("waitpid");
return -1;
}
if (ret != pid)
goto again;
if (!WIFEXITED(status)) { // did not exit normally
fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__);
return -1;
}
return WEXITSTATUS(status);
}
static int create_container(void)
{
int status, ret;
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return -1;
}
if (pid == 0) {
ret = execlp("lxc-create", "lxc-create", "-t", "busybox", "-n", MYNAME, NULL);
// Should not return
perror("execl");
exit(1);
}
again:
ret = waitpid(pid, &status, 0);
if (ret == -1) {
if (errno == EINTR)
goto again;
perror("waitpid");
return -1;
}
if (ret != pid)
goto again;
if (!WIFEXITED(status)) { // did not exit normally
fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__);
return -1;
}
return WEXITSTATUS(status);
}
int main(int argc, char *argv[])
{
struct lxc_container *c;
const char *s;
bool b;
int i;
int ret = 0;
ret = 1;
/* test a real container */
c = lxc_container_new(MYNAME, NULL);
if (!c) {
fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, MYNAME);
ret = 1;
goto out;
}
if (c->is_defined(c)) {
fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME);
goto out;
}
ret = create_container();
if (ret) {
fprintf(stderr, "%d: failed to create a container\n", __LINE__);
goto out;
}
b = c->is_defined(c);
if (!b) {
fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME);
goto out;
}
s = c->state(c);
if (!s || strcmp(s, "STOPPED")) {
fprintf(stderr, "%d: %s is in state %s, not in STOPPED.\n", __LINE__, c->name, s ? s : "undefined");
goto out;
}
b = c->load_config(c, NULL);
if (!b) {
fprintf(stderr, "%d: %s failed to read its config\n", __LINE__, c->name);
goto out;
}
if (!c->set_config_item(c, "lxc.init.cmd", "echo hello")) {
fprintf(stderr, "%d: failed setting lxc.init.cmd\n", __LINE__);
goto out;
}
c->want_daemonize(c, true);
/* Test whether we can start a really short-lived daemonized container.
*/
for (i = 0; i < 10; i++) {
if (!c->startl(c, 0, NULL)) {
fprintf(stderr, "%d: %s failed to start\n", __LINE__, c->name);
goto out;
}
sleep(1);
}
if (!c->set_config_item(c, "lxc.init.cmd", "you-shall-fail")) {
fprintf(stderr, "%d: failed setting lxc.init.cmd\n", __LINE__);
goto out;
}
/* Test whether we catch the start failure of a really short-lived
* daemonized container.
*/
for (i = 0; i < 10; i++) {
if (c->startl(c, 0, NULL)) {
fprintf(stderr, "%d: %s failed to start\n", __LINE__, c->name);
goto out;
}
sleep(1);
}
c->stop(c);
fprintf(stderr, "all lxc_container tests passed for %s\n", c->name);
ret = 0;
out:
if (c) {
c->stop(c);
destroy_container();
}
lxc_container_put(c);
exit(ret);
}
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