attach: invert child/parent handling

This makes it more consistent with th rest of the shared library. Cc: stable-4.0 Signed-off-by: 's avatarChristian Brauner <christian.brauner@ubuntu.com>
parent f4bea7cc
...@@ -962,6 +962,9 @@ int lxc_attach(struct lxc_container *container, lxc_attach_exec_t exec_function, ...@@ -962,6 +962,9 @@ int lxc_attach(struct lxc_container *container, lxc_attach_exec_t exec_function,
struct lxc_conf *conf; struct lxc_conf *conf;
char *name, *lxcpath; char *name, *lxcpath;
struct attach_clone_payload payload = {0}; struct attach_clone_payload payload = {0};
int ret_parent = -1;
pid_t to_cleanup_pid;
struct lxc_epoll_descr descr = {0};
ret = access("/proc/self/ns", X_OK); ret = access("/proc/self/ns", X_OK);
if (ret) if (ret)
...@@ -1155,275 +1158,272 @@ int lxc_attach(struct lxc_container *container, lxc_attach_exec_t exec_function, ...@@ -1155,275 +1158,272 @@ int lxc_attach(struct lxc_container *container, lxc_attach_exec_t exec_function,
return -1; return -1;
} }
if (pid) { if (pid == 0) {
int ret_parent = -1;
pid_t to_cleanup_pid = pid;
struct lxc_epoll_descr descr = {0};
/* close unneeded file descriptors */ /* close unneeded file descriptors */
close(ipc_sockets[1]); close_prot_errno_disarm(ipc_sockets[0]);
free(cwd);
lxc_proc_close_ns_fd(init_ctx);
if (options->attach_flags & LXC_ATTACH_TERMINAL)
lxc_attach_terminal_close_pts(&terminal);
/* Attach to cgroup, if requested. */
if (options->attach_flags & LXC_ATTACH_MOVE_TO_CGROUP) {
/*
* If this is the unified hierarchy cgroup_attach() is
* enough.
*/
ret = cgroup_attach(conf, name, lxcpath, pid);
if (ret) {
call_cleaner(cgroup_exit) struct cgroup_ops *cgroup_ops = NULL;
cgroup_ops = cgroup_init(conf);
if (!cgroup_ops)
goto on_error;
if (!cgroup_ops->attach(cgroup_ops, conf, name, lxcpath, pid)) if (options->attach_flags & LXC_ATTACH_TERMINAL) {
goto on_error; lxc_attach_terminal_close_ptx(&terminal);
} lxc_attach_terminal_close_peer(&terminal);
TRACE("Moved intermediate process %d into container's cgroups", pid); lxc_attach_terminal_close_log(&terminal);
} }
/* Setup /proc limits */ /* Wait for the parent to have setup cgroups. */
if (!lxc_list_empty(&conf->procs)) { ret = lxc_read_nointr(ipc_sockets[1], &status, sizeof(status));
ret = setup_proc_filesystem(&conf->procs, pid); if (ret != sizeof(status)) {
if (ret < 0) shutdown(ipc_sockets[1], SHUT_RDWR);
goto on_error; lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
} }
/* Setup resource limits */ TRACE("Intermediate process starting to initialize");
if (!lxc_list_empty(&conf->limits)) {
ret = setup_resource_limits(&conf->limits, pid); /* Attach now, create another subprocess later, since pid namespaces
if (ret < 0) * only really affect the children of the current process.
goto on_error; */
ret = lxc_attach_to_ns(init_pid, init_ctx);
if (ret < 0) {
ERROR("Failed to enter namespaces");
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
} }
if (options->attach_flags & LXC_ATTACH_TERMINAL) { /* close namespace file descriptors */
ret = lxc_attach_terminal_mainloop_init(&terminal, &descr); lxc_proc_close_ns_fd(init_ctx);
if (ret < 0)
goto on_error;
TRACE("Initialized terminal mainloop"); /* Attach succeeded, try to cwd. */
if (options->initial_cwd)
new_cwd = options->initial_cwd;
else
new_cwd = cwd;
if (new_cwd) {
ret = chdir(new_cwd);
if (ret < 0)
WARN("Could not change directory to \"%s\"", new_cwd);
} }
free(cwd);
/* Let the child process know to go ahead. */ /* Create attached process. */
status = 0; payload.ipc_socket = ipc_sockets[1];
ret = lxc_write_nointr(ipc_sockets[0], &status, sizeof(status)); payload.options = options;
if (ret != sizeof(status)) payload.init_ctx = init_ctx;
goto close_mainloop; payload.terminal_pts_fd = terminal.pty;
payload.exec_function = exec_function;
TRACE("Told intermediate process to start initializing"); payload.exec_payload = exec_payload;
pid = lxc_raw_clone(CLONE_PARENT, NULL);
if (pid < 0) {
SYSERROR("Failed to clone attached process");
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
}
/* Get pid of attached process from intermediate process. */ if (pid == 0) {
ret = lxc_read_nointr(ipc_sockets[0], &attached_pid, sizeof(attached_pid)); if (options->attach_flags & LXC_ATTACH_TERMINAL) {
if (ret != sizeof(attached_pid)) ret = lxc_terminal_signal_sigmask_safe_blocked(&terminal);
goto close_mainloop; if (ret < 0) {
SYSERROR("Failed to reset signal mask");
_exit(EXIT_FAILURE);
}
}
TRACE("Received pid %d of attached process in parent pid namespace", attached_pid); ret = attach_child_main(&payload);
if (ret < 0)
ERROR("Failed to exec");
/* Ignore SIGKILL (CTRL-C) and SIGQUIT (CTRL-\) - issue #313. */ _exit(EXIT_FAILURE);
if (options->stdin_fd == 0) {
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
} }
/* Reap intermediate process. */ if (options->attach_flags & LXC_ATTACH_TERMINAL)
ret = wait_for_pid(pid); lxc_attach_terminal_close_pts(&terminal);
if (ret < 0)
goto close_mainloop;
TRACE("Intermediate process %d exited", pid); /* Tell grandparent the pid of the pid of the newly created child. */
ret = lxc_write_nointr(ipc_sockets[1], &pid, sizeof(pid));
if (ret != sizeof(pid)) {
/* If this really happens here, this is very unfortunate, since
* the parent will not know the pid of the attached process and
* will not be able to wait for it (and we won't either due to
* CLONE_PARENT) so the parent won't be able to reap it and the
* attached process will remain a zombie.
*/
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
}
/* We will always have to reap the attached process now. */ TRACE("Sending pid %d of attached process", pid);
to_cleanup_pid = attached_pid;
/* Open LSM fd and send it to child. */ /* The rest is in the hands of the initial and the attached process. */
if ((options->namespaces & CLONE_NEWNS) && lxc_proc_put_context_info(init_ctx);
(options->attach_flags & LXC_ATTACH_LSM) && _exit(EXIT_SUCCESS);
init_ctx->lsm_label) { }
int labelfd;
bool on_exec;
ret = -1; to_cleanup_pid = pid;
on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? true : false;
labelfd = init_ctx->lsm_ops->process_label_fd_get(init_ctx->lsm_ops,
attached_pid, on_exec);
if (labelfd < 0)
goto close_mainloop;
TRACE("Opened LSM label file descriptor %d", labelfd); /* close unneeded file descriptors */
close(ipc_sockets[1]);
free(cwd);
lxc_proc_close_ns_fd(init_ctx);
if (options->attach_flags & LXC_ATTACH_TERMINAL)
lxc_attach_terminal_close_pts(&terminal);
/* Send child fd of the LSM security module to write to. */ /* Attach to cgroup, if requested. */
ret = lxc_abstract_unix_send_fds(ipc_sockets[0], &labelfd, 1, NULL, 0); if (options->attach_flags & LXC_ATTACH_MOVE_TO_CGROUP) {
if (ret <= 0) { /*
if (ret < 0) * If this is the unified hierarchy cgroup_attach() is
SYSERROR("Failed to send lsm label fd"); * enough.
*/
ret = cgroup_attach(conf, name, lxcpath, pid);
if (ret) {
call_cleaner(cgroup_exit) struct cgroup_ops *cgroup_ops = NULL;
close(labelfd); cgroup_ops = cgroup_init(conf);
goto close_mainloop; if (!cgroup_ops)
} goto on_error;
close(labelfd); if (!cgroup_ops->attach(cgroup_ops, conf, name, lxcpath, pid))
TRACE("Sent LSM label file descriptor %d to child", labelfd); goto on_error;
} }
TRACE("Moved intermediate process %d into container's cgroups", pid);
}
if (conf->seccomp.seccomp) { /* Setup /proc limits */
ret = lxc_seccomp_recv_notifier_fd(&conf->seccomp, ipc_sockets[0]); if (!lxc_list_empty(&conf->procs)) {
if (ret < 0) ret = setup_proc_filesystem(&conf->procs, pid);
goto close_mainloop; if (ret < 0)
goto on_error;
}
ret = lxc_seccomp_add_notifier(name, lxcpath, &conf->seccomp); /* Setup resource limits */
if (ret < 0) if (!lxc_list_empty(&conf->limits)) {
goto close_mainloop; ret = setup_resource_limits(&conf->limits, pid);
} if (ret < 0)
goto on_error;
}
/* We're done, the child process should now execute whatever it if (options->attach_flags & LXC_ATTACH_TERMINAL) {
* is that the user requested. The parent can now track it with ret = lxc_attach_terminal_mainloop_init(&terminal, &descr);
* waitpid() or similar. if (ret < 0)
*/ goto on_error;
*attached_process = attached_pid; TRACE("Initialized terminal mainloop");
}
/* Now shut down communication with child, we're done. */ /* Let the child process know to go ahead. */
shutdown(ipc_sockets[0], SHUT_RDWR); status = 0;
close(ipc_sockets[0]); ret = lxc_write_nointr(ipc_sockets[0], &status, sizeof(status));
ipc_sockets[0] = -1; if (ret != sizeof(status))
goto close_mainloop;
ret_parent = 0; TRACE("Told intermediate process to start initializing");
to_cleanup_pid = -1;
if (options->attach_flags & LXC_ATTACH_TERMINAL) { /* Get pid of attached process from intermediate process. */
ret = lxc_mainloop(&descr, -1); ret = lxc_read_nointr(ipc_sockets[0], &attached_pid, sizeof(attached_pid));
if (ret < 0) { if (ret != sizeof(attached_pid))
ret_parent = -1; goto close_mainloop;
to_cleanup_pid = attached_pid;
}
}
close_mainloop: TRACE("Received pid %d of attached process in parent pid namespace", attached_pid);
if (options->attach_flags & LXC_ATTACH_TERMINAL)
lxc_mainloop_close(&descr);
on_error: /* Ignore SIGKILL (CTRL-C) and SIGQUIT (CTRL-\) - issue #313. */
if (ipc_sockets[0] >= 0) { if (options->stdin_fd == 0) {
shutdown(ipc_sockets[0], SHUT_RDWR); signal(SIGINT, SIG_IGN);
close(ipc_sockets[0]); signal(SIGQUIT, SIG_IGN);
} }
if (to_cleanup_pid > 0) /* Reap intermediate process. */
(void)wait_for_pid(to_cleanup_pid); ret = wait_for_pid(pid);
if (ret < 0)
goto close_mainloop;
if (options->attach_flags & LXC_ATTACH_TERMINAL) { TRACE("Intermediate process %d exited", pid);
lxc_terminal_delete(&terminal);
lxc_terminal_conf_free(&terminal);
}
lxc_proc_put_context_info(init_ctx); /* We will always have to reap the attached process now. */
return ret_parent; to_cleanup_pid = attached_pid;
}
/* close unneeded file descriptors */ /* Open LSM fd and send it to child. */
close_prot_errno_disarm(ipc_sockets[0]); if ((options->namespaces & CLONE_NEWNS) &&
(options->attach_flags & LXC_ATTACH_LSM) && init_ctx->lsm_label) {
int labelfd;
bool on_exec;
if (options->attach_flags & LXC_ATTACH_TERMINAL) { ret = -1;
lxc_attach_terminal_close_ptx(&terminal); on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? true : false;
lxc_attach_terminal_close_peer(&terminal); labelfd = init_ctx->lsm_ops->process_label_fd_get(init_ctx->lsm_ops,
lxc_attach_terminal_close_log(&terminal); attached_pid, on_exec);
} if (labelfd < 0)
goto close_mainloop;
/* Wait for the parent to have setup cgroups. */ TRACE("Opened LSM label file descriptor %d", labelfd);
ret = lxc_read_nointr(ipc_sockets[1], &status, sizeof(status));
if (ret != sizeof(status)) {
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
}
TRACE("Intermediate process starting to initialize"); /* Send child fd of the LSM security module to write to. */
ret = lxc_abstract_unix_send_fds(ipc_sockets[0], &labelfd, 1, NULL, 0);
if (ret <= 0) {
if (ret < 0)
SYSERROR("Failed to send lsm label fd");
/* Attach now, create another subprocess later, since pid namespaces close(labelfd);
* only really affect the children of the current process. goto close_mainloop;
*/ }
ret = lxc_attach_to_ns(init_pid, init_ctx);
if (ret < 0) { close(labelfd);
ERROR("Failed to enter namespaces"); TRACE("Sent LSM label file descriptor %d to child", labelfd);
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
} }
/* close namespace file descriptors */ if (conf->seccomp.seccomp) {
lxc_proc_close_ns_fd(init_ctx); ret = lxc_seccomp_recv_notifier_fd(&conf->seccomp, ipc_sockets[0]);
if (ret < 0)
goto close_mainloop;
/* Attach succeeded, try to cwd. */ ret = lxc_seccomp_add_notifier(name, lxcpath, &conf->seccomp);
if (options->initial_cwd)
new_cwd = options->initial_cwd;
else
new_cwd = cwd;
if (new_cwd) {
ret = chdir(new_cwd);
if (ret < 0) if (ret < 0)
WARN("Could not change directory to \"%s\"", new_cwd); goto close_mainloop;
} }
free(cwd);
/* Create attached process. */ /* We're done, the child process should now execute whatever it
payload.ipc_socket = ipc_sockets[1]; * is that the user requested. The parent can now track it with
payload.options = options; * waitpid() or similar.
payload.init_ctx = init_ctx; */
payload.terminal_pts_fd = terminal.pty;
payload.exec_function = exec_function;
payload.exec_payload = exec_payload;
pid = lxc_raw_clone(CLONE_PARENT, NULL); *attached_process = attached_pid;
if (pid < 0) {
SYSERROR("Failed to clone attached process");
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
}
if (pid == 0) { /* Now shut down communication with child, we're done. */
if (options->attach_flags & LXC_ATTACH_TERMINAL) { shutdown(ipc_sockets[0], SHUT_RDWR);
ret = lxc_terminal_signal_sigmask_safe_blocked(&terminal); close(ipc_sockets[0]);
if (ret < 0) { ipc_sockets[0] = -1;
SYSERROR("Failed to reset signal mask");
_exit(EXIT_FAILURE);
}
}
ret = attach_child_main(&payload); ret_parent = 0;
if (ret < 0) to_cleanup_pid = -1;
ERROR("Failed to exec");
_exit(EXIT_FAILURE); if (options->attach_flags & LXC_ATTACH_TERMINAL) {
ret = lxc_mainloop(&descr, -1);
if (ret < 0) {
ret_parent = -1;
to_cleanup_pid = attached_pid;
}
} }
close_mainloop:
if (options->attach_flags & LXC_ATTACH_TERMINAL) if (options->attach_flags & LXC_ATTACH_TERMINAL)
lxc_attach_terminal_close_pts(&terminal); lxc_mainloop_close(&descr);
/* Tell grandparent the pid of the pid of the newly created child. */ on_error:
ret = lxc_write_nointr(ipc_sockets[1], &pid, sizeof(pid)); if (ipc_sockets[0] >= 0) {
if (ret != sizeof(pid)) { shutdown(ipc_sockets[0], SHUT_RDWR);
/* If this really happens here, this is very unfortunate, since close(ipc_sockets[0]);
* the parent will not know the pid of the attached process and
* will not be able to wait for it (and we won't either due to
* CLONE_PARENT) so the parent won't be able to reap it and the
* attached process will remain a zombie.
*/
shutdown(ipc_sockets[1], SHUT_RDWR);
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_FAILURE);
} }
TRACE("Sending pid %d of attached process", pid); if (to_cleanup_pid > 0)
(void)wait_for_pid(to_cleanup_pid);
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
lxc_terminal_delete(&terminal);
lxc_terminal_conf_free(&terminal);
}
/* The rest is in the hands of the initial and the attached process. */
lxc_proc_put_context_info(init_ctx); lxc_proc_put_context_info(init_ctx);
_exit(EXIT_SUCCESS); return ret_parent;
} }
int lxc_attach_run_command(void *payload) int lxc_attach_run_command(void *payload)
......
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