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,
struct lxc_conf *conf;
char *name, *lxcpath;
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);
if (ret)
......@@ -1155,275 +1158,272 @@ int lxc_attach(struct lxc_container *container, lxc_attach_exec_t exec_function,
return -1;
}
if (pid) {
int ret_parent = -1;
pid_t to_cleanup_pid = pid;
struct lxc_epoll_descr descr = {0};
if (pid == 0) {
/* 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);
/* 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;
close_prot_errno_disarm(ipc_sockets[0]);
if (!cgroup_ops->attach(cgroup_ops, conf, name, lxcpath, pid))
goto on_error;
}
TRACE("Moved intermediate process %d into container's cgroups", pid);
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
lxc_attach_terminal_close_ptx(&terminal);
lxc_attach_terminal_close_peer(&terminal);
lxc_attach_terminal_close_log(&terminal);
}
/* Setup /proc limits */
if (!lxc_list_empty(&conf->procs)) {
ret = setup_proc_filesystem(&conf->procs, pid);
if (ret < 0)
goto on_error;
/* Wait for the parent to have setup cgroups. */
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);
}
/* Setup resource limits */
if (!lxc_list_empty(&conf->limits)) {
ret = setup_resource_limits(&conf->limits, pid);
if (ret < 0)
goto on_error;
TRACE("Intermediate process starting to initialize");
/* Attach now, create another subprocess later, since pid namespaces
* only really affect the children of the current process.
*/
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) {
ret = lxc_attach_terminal_mainloop_init(&terminal, &descr);
if (ret < 0)
goto on_error;
/* close namespace file descriptors */
lxc_proc_close_ns_fd(init_ctx);
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. */
status = 0;
ret = lxc_write_nointr(ipc_sockets[0], &status, sizeof(status));
if (ret != sizeof(status))
goto close_mainloop;
TRACE("Told intermediate process to start initializing");
/* Create attached process. */
payload.ipc_socket = ipc_sockets[1];
payload.options = options;
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);
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. */
ret = lxc_read_nointr(ipc_sockets[0], &attached_pid, sizeof(attached_pid));
if (ret != sizeof(attached_pid))
goto close_mainloop;
if (pid == 0) {
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
ret = lxc_terminal_signal_sigmask_safe_blocked(&terminal);
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. */
if (options->stdin_fd == 0) {
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
_exit(EXIT_FAILURE);
}
/* Reap intermediate process. */
ret = wait_for_pid(pid);
if (ret < 0)
goto close_mainloop;
if (options->attach_flags & LXC_ATTACH_TERMINAL)
lxc_attach_terminal_close_pts(&terminal);
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. */
to_cleanup_pid = attached_pid;
TRACE("Sending pid %d of attached process", pid);
/* Open LSM fd and send it to child. */
if ((options->namespaces & CLONE_NEWNS) &&
(options->attach_flags & LXC_ATTACH_LSM) &&
init_ctx->lsm_label) {
int labelfd;
bool on_exec;
/* The rest is in the hands of the initial and the attached process. */
lxc_proc_put_context_info(init_ctx);
_exit(EXIT_SUCCESS);
}
ret = -1;
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;
to_cleanup_pid = pid;
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. */
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 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;
close(labelfd);
goto close_mainloop;
}
cgroup_ops = cgroup_init(conf);
if (!cgroup_ops)
goto on_error;
close(labelfd);
TRACE("Sent LSM label file descriptor %d to child", labelfd);
if (!cgroup_ops->attach(cgroup_ops, conf, name, lxcpath, pid))
goto on_error;
}
TRACE("Moved intermediate process %d into container's cgroups", pid);
}
if (conf->seccomp.seccomp) {
ret = lxc_seccomp_recv_notifier_fd(&conf->seccomp, ipc_sockets[0]);
if (ret < 0)
goto close_mainloop;
/* Setup /proc limits */
if (!lxc_list_empty(&conf->procs)) {
ret = setup_proc_filesystem(&conf->procs, pid);
if (ret < 0)
goto on_error;
}
ret = lxc_seccomp_add_notifier(name, lxcpath, &conf->seccomp);
if (ret < 0)
goto close_mainloop;
}
/* Setup resource limits */
if (!lxc_list_empty(&conf->limits)) {
ret = setup_resource_limits(&conf->limits, pid);
if (ret < 0)
goto on_error;
}
/* We're done, the child process should now execute whatever it
* is that the user requested. The parent can now track it with
* waitpid() or similar.
*/
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
ret = lxc_attach_terminal_mainloop_init(&terminal, &descr);
if (ret < 0)
goto on_error;
*attached_process = attached_pid;
TRACE("Initialized terminal mainloop");
}
/* Now shut down communication with child, we're done. */
shutdown(ipc_sockets[0], SHUT_RDWR);
close(ipc_sockets[0]);
ipc_sockets[0] = -1;
/* Let the child process know to go ahead. */
status = 0;
ret = lxc_write_nointr(ipc_sockets[0], &status, sizeof(status));
if (ret != sizeof(status))
goto close_mainloop;
ret_parent = 0;
to_cleanup_pid = -1;
TRACE("Told intermediate process to start initializing");
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
ret = lxc_mainloop(&descr, -1);
if (ret < 0) {
ret_parent = -1;
to_cleanup_pid = attached_pid;
}
}
/* Get pid of attached process from intermediate process. */
ret = lxc_read_nointr(ipc_sockets[0], &attached_pid, sizeof(attached_pid));
if (ret != sizeof(attached_pid))
goto close_mainloop;
close_mainloop:
if (options->attach_flags & LXC_ATTACH_TERMINAL)
lxc_mainloop_close(&descr);
TRACE("Received pid %d of attached process in parent pid namespace", attached_pid);
on_error:
if (ipc_sockets[0] >= 0) {
shutdown(ipc_sockets[0], SHUT_RDWR);
close(ipc_sockets[0]);
}
/* Ignore SIGKILL (CTRL-C) and SIGQUIT (CTRL-\) - issue #313. */
if (options->stdin_fd == 0) {
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
}
if (to_cleanup_pid > 0)
(void)wait_for_pid(to_cleanup_pid);
/* Reap intermediate process. */
ret = wait_for_pid(pid);
if (ret < 0)
goto close_mainloop;
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
lxc_terminal_delete(&terminal);
lxc_terminal_conf_free(&terminal);
}
TRACE("Intermediate process %d exited", pid);
lxc_proc_put_context_info(init_ctx);
return ret_parent;
}
/* We will always have to reap the attached process now. */
to_cleanup_pid = attached_pid;
/* close unneeded file descriptors */
close_prot_errno_disarm(ipc_sockets[0]);
/* Open LSM fd and send it to child. */
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) {
lxc_attach_terminal_close_ptx(&terminal);
lxc_attach_terminal_close_peer(&terminal);
lxc_attach_terminal_close_log(&terminal);
}
ret = -1;
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;
/* Wait for the parent to have setup cgroups. */
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("Opened LSM label file descriptor %d", labelfd);
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
* only really affect the children of the current process.
*/
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);
close(labelfd);
goto close_mainloop;
}
close(labelfd);
TRACE("Sent LSM label file descriptor %d to child", labelfd);
}
/* close namespace file descriptors */
lxc_proc_close_ns_fd(init_ctx);
if (conf->seccomp.seccomp) {
ret = lxc_seccomp_recv_notifier_fd(&conf->seccomp, ipc_sockets[0]);
if (ret < 0)
goto close_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);
ret = lxc_seccomp_add_notifier(name, lxcpath, &conf->seccomp);
if (ret < 0)
WARN("Could not change directory to \"%s\"", new_cwd);
goto close_mainloop;
}
free(cwd);
/* Create attached process. */
payload.ipc_socket = ipc_sockets[1];
payload.options = options;
payload.init_ctx = init_ctx;
payload.terminal_pts_fd = terminal.pty;
payload.exec_function = exec_function;
payload.exec_payload = exec_payload;
/* We're done, the child process should now execute whatever it
* is that the user requested. The parent can now track it with
* waitpid() or similar.
*/
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);
}
*attached_process = attached_pid;
if (pid == 0) {
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
ret = lxc_terminal_signal_sigmask_safe_blocked(&terminal);
if (ret < 0) {
SYSERROR("Failed to reset signal mask");
_exit(EXIT_FAILURE);
}
}
/* Now shut down communication with child, we're done. */
shutdown(ipc_sockets[0], SHUT_RDWR);
close(ipc_sockets[0]);
ipc_sockets[0] = -1;
ret = attach_child_main(&payload);
if (ret < 0)
ERROR("Failed to exec");
ret_parent = 0;
to_cleanup_pid = -1;
_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)
lxc_attach_terminal_close_pts(&terminal);
lxc_mainloop_close(&descr);
/* 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);
on_error:
if (ipc_sockets[0] >= 0) {
shutdown(ipc_sockets[0], SHUT_RDWR);
close(ipc_sockets[0]);
}
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);
_exit(EXIT_SUCCESS);
return ret_parent;
}
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