From ba2be1a8a619f90ebcddc44fc030a262461b7cfa Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sun, 24 Dec 2017 19:25:34 +0100 Subject: [PATCH] attach: move pty allocation into api Signed-off-by: Christian Brauner --- doc/lxc-attach.sgml.in | 17 --- src/lxc/attach.c | 227 ++++++++++++++++++++++++++++------ src/lxc/attach_options.h | 1 + src/lxc/tools/lxc_attach.c | 205 +++++------------------------- src/tests/attach.c | 65 ++++++++-- src/tests/lxc-test-lxc-attach | 11 -- 6 files changed, 279 insertions(+), 247 deletions(-) diff --git a/doc/lxc-attach.sgml.in b/doc/lxc-attach.sgml.in index 7535bb114..713a30e7f 100644 --- a/doc/lxc-attach.sgml.in +++ b/doc/lxc-attach.sgml.in @@ -58,7 +58,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -R, --remount-sys-proc --keep-env --clear-env - -L, --pty-log file -v, --set-var variable --keep-var variable -- command @@ -256,22 +255,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - - - - - - Specify a file where the output of lxc-attach will be - logged. - - - Important: When a standard file descriptor - does not refer to a pty output produced on it will not be logged. - - - - diff --git a/src/lxc/attach.c b/src/lxc/attach.c index b6f290ad3..34bdf9145 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -66,6 +66,7 @@ #include "lsm/lsm.h" #include "lxclock.h" #include "lxcseccomp.h" +#include "mainloop.h" #include "namespace.h" #include "utils.h" @@ -819,12 +820,30 @@ static signed long get_personality(const char *name, const char *lxcpath) struct attach_clone_payload { int ipc_socket; + int pty_fd; lxc_attach_options_t *options; struct lxc_proc_context_info *init_ctx; lxc_attach_exec_t exec_function; void *exec_payload; }; +static void lxc_put_attach_clone_payload(struct attach_clone_payload *p) +{ + if (p->ipc_socket >= 0) { + shutdown(p->ipc_socket, SHUT_RDWR); + close(p->ipc_socket); + p->ipc_socket = -EBADF; + } + + if (p->pty_fd >= 0) { + close(p->pty_fd); + p->pty_fd = -EBADF; + } + + if (p->init_ctx) + lxc_proc_put_context_info(p->init_ctx); +} + static int attach_child_main(struct attach_clone_payload *payload) { int fd, lsm_fd, ret; @@ -961,7 +980,8 @@ static int attach_child_main(struct attach_clone_payload *payload) } shutdown(payload->ipc_socket, SHUT_RDWR); close(payload->ipc_socket); - payload->ipc_socket = -1; + payload->ipc_socket = -EBADF; + lxc_proc_put_context_info(init_ctx); /* The following is done after the communication socket is shut down. * That way, all errors that might (though unlikely) occur up until this @@ -1011,19 +1031,107 @@ static int attach_child_main(struct attach_clone_payload *payload) } } + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) { + ret = lxc_login_pty(payload->pty_fd); + if (ret < 0) { + SYSERROR("Failed to prepare pty file descriptor %d", payload->pty_fd); + goto on_error; + } + TRACE("Prepared pty file descriptor %d", payload->pty_fd); + } + /* We're done, so we can now do whatever the user intended us to do. */ rexit(payload->exec_function(payload->exec_payload)); on_error: - if (payload->ipc_socket >= 0) { - shutdown(payload->ipc_socket, SHUT_RDWR); - close(payload->ipc_socket); - payload->ipc_socket = -1; - } - lxc_proc_put_context_info(init_ctx); + lxc_put_attach_clone_payload(payload); rexit(EXIT_FAILURE); } +static int lxc_attach_pty(struct lxc_conf *conf, struct lxc_console *pty) +{ + int ret; + + lxc_pty_init(pty); + + ret = lxc_pty_create(pty); + if (ret < 0) { + SYSERROR("Failed to create pty"); + return -1; + } + + /* Shift ttys to container. */ + ret = lxc_pty_map_ids(conf, pty); + if (ret < 0) { + ERROR("Failed to shift pty"); + goto on_error; + } + + return 0; + +on_error: + lxc_console_delete(pty); + lxc_pty_conf_free(pty); + return -1; +} + +static int lxc_attach_pty_mainloop_init(struct lxc_console *pty, + struct lxc_epoll_descr *descr) +{ + int ret; + + ret = lxc_mainloop_open(descr); + if (ret < 0) { + ERROR("Failed to create mainloop"); + return -1; + } + + ret = lxc_console_mainloop_add(descr, pty); + if (ret < 0) { + ERROR("Failed to add handlers to mainloop"); + lxc_mainloop_close(descr); + return -1; + } + + return 0; +} + +static inline void lxc_attach_pty_close_master(struct lxc_console *pty) +{ + if (pty->master < 0) + return; + + close(pty->master); + pty->master = -EBADF; +} + +static inline void lxc_attach_pty_close_slave(struct lxc_console *pty) +{ + if (pty->slave < 0) + return; + + close(pty->slave); + pty->slave = -EBADF; +} + +static inline void lxc_attach_pty_close_peer(struct lxc_console *pty) +{ + if (pty->peer < 0) + return; + + close(pty->peer); + pty->peer = -EBADF; +} + +static inline void lxc_attach_pty_close_log(struct lxc_console *pty) +{ + if (pty->log_fd < 0) + return; + + close(pty->log_fd); + pty->log_fd = -EBADF; +} + int lxc_attach(const char *name, const char *lxcpath, lxc_attach_exec_t exec_function, void *exec_payload, lxc_attach_options_t *options, pid_t *attached_process) @@ -1032,8 +1140,9 @@ int lxc_attach(const char *name, const char *lxcpath, int ipc_sockets[2]; char *cwd, *new_cwd; signed long personality; - pid_t attached_pid, expected, init_pid, pid; + pid_t attached_pid, init_pid, pid; struct lxc_proc_context_info *init_ctx; + struct lxc_console pty; struct attach_clone_payload payload = {0}; ret = access("/proc/self/ns", X_OK); @@ -1150,6 +1259,18 @@ int lxc_attach(const char *name, const char *lxcpath, return -1; } + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) { + ret = lxc_attach_pty(init_ctx->container->lxc_conf, &pty); + if (ret < 0) { + ERROR("Failed to allocate pty"); + free(cwd); + lxc_proc_put_context_info(init_ctx); + return -1; + } + + pty.log_fd = options->log_fd; + } + /* Create a socket pair for IPC communication; set SOCK_CLOEXEC in order * to make sure we don't irritate other threads that want to fork+exec * away @@ -1210,13 +1331,16 @@ int lxc_attach(const char *name, const char *lxcpath, } if (pid) { + int ret_parent = -1; pid_t to_cleanup_pid = pid; + struct lxc_epoll_descr descr = {0}; - /* Initial thread, we close the socket that is for the - * subprocesses. - */ + /* close unneeded file descriptors */ close(ipc_sockets[1]); free(cwd); + lxc_proc_close_ns_fd(init_ctx); + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) + lxc_attach_pty_close_slave(&pty); /* Attach to cgroup, if requested. */ if (options->attach_flags & LXC_ATTACH_MOVE_TO_CGROUP) { @@ -1227,21 +1351,30 @@ int lxc_attach(const char *name, const char *lxcpath, } /* Setup resource limits */ - if (!lxc_list_empty(&init_ctx->container->lxc_conf->limits)) - if (setup_resource_limits(&init_ctx->container->lxc_conf->limits, pid) < 0) + if (!lxc_list_empty(&init_ctx->container->lxc_conf->limits)) { + ret = setup_resource_limits(&init_ctx->container->lxc_conf->limits, pid); + if (ret < 0) goto on_error; + } + + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) { + ret = lxc_attach_pty_mainloop_init(&pty, &descr); + if (ret < 0) + goto on_error; + TRACE("Initalized pty mainloop"); + } /* 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 on_error; + goto close_mainloop; TRACE("Told intermediate process to start initializing"); /* 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 on_error; + goto close_mainloop; TRACE("Received pid %d of attached process in parent pid namespace", attached_pid); /* Ignore SIGKILL (CTRL-C) and SIGQUIT (CTRL-\) - issue #313. */ @@ -1253,7 +1386,7 @@ int lxc_attach(const char *name, const char *lxcpath, /* Reap intermediate process. */ ret = wait_for_pid(pid); if (ret < 0) - goto on_error; + goto close_mainloop; TRACE("Intermediate process %d exited", pid); /* We will always have to reap the attached process now. */ @@ -1269,7 +1402,7 @@ int lxc_attach(const char *name, const char *lxcpath, on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? 1 : 0; labelfd = lsm_open(attached_pid, on_exec); if (labelfd < 0) - goto on_error; + goto close_mainloop; TRACE("Opened LSM label file descriptor %d", labelfd); /* Send child fd of the LSM security module to write to. */ @@ -1277,45 +1410,66 @@ int lxc_attach(const char *name, const char *lxcpath, close(labelfd); if (ret <= 0) { SYSERROR("%d", (int)ret); - goto on_error; + goto close_mainloop; } TRACE("Sent LSM label file descriptor %d to child", labelfd); } - /* Now shut down communication with child, we're done. */ - shutdown(ipc_sockets[0], SHUT_RDWR); - close(ipc_sockets[0]); - lxc_proc_put_context_info(init_ctx); - /* 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. */ *attached_process = attached_pid; - return 0; - on_error: - /* First shut down the socket, then wait for the pid, otherwise - * the pid we're waiting for may never exit. - */ + /* Now shut down communication with child, we're done. */ shutdown(ipc_sockets[0], SHUT_RDWR); close(ipc_sockets[0]); - if (to_cleanup_pid) + ipc_sockets[0] = -1; + + ret_parent = 0; + to_cleanup_pid = -1; + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) { + ret = lxc_mainloop(&descr, -1); + if (ret < 0) { + ret_parent = -1; + to_cleanup_pid = attached_pid; + } + } + + close_mainloop: + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) + lxc_mainloop_close(&descr); + + on_error: + if (ipc_sockets[0] >= 0) { + shutdown(ipc_sockets[0], SHUT_RDWR); + close(ipc_sockets[0]); + } + + if (to_cleanup_pid > 0) (void)wait_for_pid(to_cleanup_pid); + + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) { + lxc_console_delete(&pty); + lxc_pty_conf_free(&pty); + } lxc_proc_put_context_info(init_ctx); - return -1; + return ret_parent; } - /* First subprocess begins here, we close the socket that is for the - * initial thread. - */ + /* close unneeded file descriptors */ close(ipc_sockets[0]); + ipc_sockets[0] = -EBADF; + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) { + lxc_attach_pty_close_master(&pty); + lxc_attach_pty_close_peer(&pty); + lxc_attach_pty_close_log(&pty); + } /* Wait for the parent to have setup cgroups. */ - expected = 0; ret = lxc_read_nointr(ipc_sockets[1], &status, sizeof(status)); - if (ret != sizeof(status) || status != expected) { + if (ret != sizeof(status)) { shutdown(ipc_sockets[1], SHUT_RDWR); lxc_proc_put_context_info(init_ctx); rexit(-1); @@ -1351,6 +1505,7 @@ int lxc_attach(const char *name, const char *lxcpath, payload.ipc_socket = ipc_sockets[1]; payload.options = options; payload.init_ctx = init_ctx; + payload.pty_fd = pty.slave; payload.exec_function = exec_function; payload.exec_payload = exec_payload; @@ -1368,6 +1523,8 @@ int lxc_attach(const char *name, const char *lxcpath, ERROR("Failed to exec"); _exit(EXIT_FAILURE); } + if (options->attach_flags & LXC_ATTACH_ALLOCATE_PTY) + lxc_attach_pty_close_slave(&pty); /* Tell grandparent the pid of the pid of the newly created child. */ ret = lxc_write_nointr(ipc_sockets[1], &pid, sizeof(pid)); diff --git a/src/lxc/attach_options.h b/src/lxc/attach_options.h index fad6456cc..1e64e4abf 100644 --- a/src/lxc/attach_options.h +++ b/src/lxc/attach_options.h @@ -51,6 +51,7 @@ enum { LXC_ATTACH_LSM_NOW = 0x00020000, /*!< FIXME: unknown */ /* Set PR_SET_NO_NEW_PRIVS to block execve() gainable privileges. */ LXC_ATTACH_NO_NEW_PRIVS = 0x00040000, /*!< PR_SET_NO_NEW_PRIVS */ + LXC_ATTACH_ALLOCATE_PTY = 0x00080000, /*!< Allocate new pty for attached process. */ /* We have 16 bits for things that are on by default and 16 bits that * are off by default, that should be sufficient to keep binary diff --git a/src/lxc/tools/lxc_attach.c b/src/lxc/tools/lxc_attach.c index 0615cc719..926bf079e 100644 --- a/src/lxc/tools/lxc_attach.c +++ b/src/lxc/tools/lxc_attach.c @@ -27,11 +27,12 @@ #include #include #include -#include -#include -#include #include #include +#include +#include +#include +#include #include @@ -46,12 +47,6 @@ #include "mainloop.h" #include "utils.h" -#if HAVE_PTY_H -#include -#else -#include <../include/openpty.h> -#endif - static const struct option my_longopts[] = { {"elevated-privileges", optional_argument, 0, 'e'}, {"arch", required_argument, 0, 'a'}, @@ -241,155 +236,29 @@ Options :\n\ .checker = NULL, }; -struct wrapargs { - lxc_attach_options_t *options; - lxc_attach_command_t *command; - struct lxc_console *console; - int ptyfd; -}; - -/* Minimalistic login_tty() implementation. */ -static int login_pty(int fd) -{ - setsid(); - if (ioctl(fd, TIOCSCTTY, NULL) < 0) - return -1; - if (lxc_console_set_stdfds(fd) < 0) - return -1; - if (fd > STDERR_FILENO) - close(fd); - return 0; -} - -static int get_pty_on_host_callback(void *p) -{ - struct wrapargs *wrap = p; - - close(wrap->console->master); - if (login_pty(wrap->console->slave) < 0) - return -1; - - if (wrap->command->program) - lxc_attach_run_command(wrap->command); - else - lxc_attach_run_shell(NULL); - return -1; -} - -static int get_pty_on_host(struct lxc_container *c, struct wrapargs *wrap, int *pid) -{ - struct lxc_epoll_descr descr; - struct lxc_conf *conf; - struct lxc_tty_state *ts; - int ret = -1; - struct wrapargs *args = wrap; - - if (!isatty(args->ptyfd)) { - fprintf(stderr, "Standard file descriptor does not refer to a pty\n"); - return -1; - } - - if (c->lxc_conf) { - conf = c->lxc_conf; - } else { - /* If the container is not defined and the user didn't specify a - * config file to load we will simply init a dummy config here. - */ - conf = lxc_conf_init(); - if (!conf) { - fprintf(stderr, "Failed to allocate dummy config file for the container\n"); - return -1; - } - - /* We also need a dummy rootfs path otherwise - * lxc_console_create() will not let us create a console. Note, - * I don't want this change to make it into - * lxc_console_create()'s since this function will only be - * responsible for proper /dev/{console,tty} devices. - * lxc-attach is just abusing it to also handle the pty case - * because it is very similar. However, with LXC 3.0 lxc-attach - * will need to move away from using lxc_console_create() since - * this is actually an internal symbol and we only want the - * tools to use the API with LXC 3.0. - */ - conf->rootfs.path = strdup("dummy"); - if (!conf->rootfs.path) - return -1; - } - free(conf->console.log_path); - if (my_args.console_log) - conf->console.log_path = strdup(my_args.console_log); - else - conf->console.log_path = NULL; - - /* In the case of lxc-attach our peer pty will always be the current - * controlling terminal. We clear whatever was set by the user for - * lxc.console.path here and set it NULL. lxc_console_peer_default() - * will then try to open /dev/tty. If the process doesn't have a - * controlling terminal we should still proceed. - */ - free(conf->console.path); - conf->console.path = NULL; - - /* Create pty on the host. */ - if (lxc_console_create(conf) < 0) - return -1; - ts = conf->console.tty_state; - conf->console.descr = &descr; - - /* Shift ttys to container. */ - ret = lxc_pty_map_ids(conf, &conf->console); - if (ret < 0) { - fprintf(stderr, "Failed to shift tty into container\n"); - goto err1; - } - - /* Send wrapper function on its way. */ - wrap->console = &conf->console; - if (c->attach(c, get_pty_on_host_callback, wrap, wrap->options, pid) < 0) - goto err1; - close(conf->console.slave); /* Close slave side. */ - conf->console.slave = -1; - - ret = lxc_mainloop_open(&descr); - if (ret) { - fprintf(stderr, "failed to create mainloop\n"); - goto err2; - } - - if (lxc_console_mainloop_add(&descr, &conf->console) < 0) { - fprintf(stderr, "Failed to add handlers to lxc mainloop.\n"); - goto err3; - } - - ret = lxc_mainloop(&descr, -1); - if (ret) { - fprintf(stderr, "mainloop returned an error\n"); - goto err3; - } - ret = 0; - -err3: - lxc_mainloop_close(&descr); -err2: - if (ts && ts->sigfd != -1) - lxc_console_signal_fini(ts); -err1: - lxc_console_delete(&conf->console); - - return ret; -} - -static int stdfd_is_pty(void) +static bool stdfd_is_pty(void) { if (isatty(STDIN_FILENO)) - return STDIN_FILENO; + return true; if (isatty(STDOUT_FILENO)) - return STDOUT_FILENO; + return true; if (isatty(STDERR_FILENO)) - return STDERR_FILENO; + return true; - return -1; + return false; +} + +int lxc_attach_create_log_file(const char *log_file) +{ + int fd; + + fd = open(log_file, O_CLOEXEC | O_RDWR | O_CREAT | O_APPEND, 0600); + if (fd < 0) { + fprintf(stderr, "Failed to open log file \"%s\"\n", log_file); + return -1; + } + + return fd; } int main(int argc, char *argv[]) @@ -463,6 +332,8 @@ int main(int argc, char *argv[]) attach_options.attach_flags |= LXC_ATTACH_REMOUNT_PROC_SYS; if (elevated_privileges) attach_options.attach_flags &= ~(elevated_privileges); + if (stdfd_is_pty()) + attach_options.attach_flags |= LXC_ATTACH_ALLOCATE_PTY; attach_options.namespaces = namespace_flags; attach_options.personality = new_personality; attach_options.env_policy = env_policy; @@ -474,29 +345,17 @@ int main(int argc, char *argv[]) command.argv = (char**)my_args.argv; } - struct wrapargs wrap = (struct wrapargs){ - .command = &command, - .options = &attach_options - }; - - wrap.ptyfd = stdfd_is_pty(); - if (wrap.ptyfd >= 0) { - if ((!isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO)) && my_args.console_log) { - fprintf(stderr, "-L/--pty-log can only be used when stdout and stderr refer to a pty.\n"); + if (my_args.console_log) { + attach_options.log_fd = lxc_attach_create_log_file(my_args.console_log); + if (attach_options.log_fd < 0) goto out; - } - ret = get_pty_on_host(c, &wrap, &pid); - } else { - if (my_args.console_log) { - fprintf(stderr, "-L/--pty-log can only be used when stdout and stderr refer to a pty.\n"); - goto out; - } - if (command.program) - ret = c->attach(c, lxc_attach_run_command, &command, &attach_options, &pid); - else - ret = c->attach(c, lxc_attach_run_shell, NULL, &attach_options, &pid); } + if (command.program) + ret = c->attach(c, lxc_attach_run_command, &command, &attach_options, &pid); + else + ret = c->attach(c, lxc_attach_run_shell, NULL, &attach_options, &pid); + if (ret < 0) goto out; diff --git a/src/tests/attach.c b/src/tests/attach.c index 7bf5ef179..2c771278c 100644 --- a/src/tests/attach.c +++ b/src/tests/attach.c @@ -19,16 +19,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include "lxc/utils.h" -#include "lxc/lsm/lsm.h" - -#include +#include #include #include #include #include -#include +#include + +#include "lxctest.h" +#include "utils.h" +#include "lsm/lsm.h" + +#include #define TSTNAME "lxc-attach-test" #define TSTOUT(fmt, ...) do { \ @@ -392,19 +394,60 @@ err1: int main(int argc, char *argv[]) { - int ret; + int i, ret; + struct lxc_log log; + char template[sizeof(P_tmpdir"/attach_XXXXXX")]; + int fret = EXIT_FAILURE; + + strcpy(template, P_tmpdir"/attach_XXXXXX"); + i = lxc_make_tmpfile(template, false); + if (i < 0) { + lxc_error("Failed to create temporary log file for container %s\n", TSTNAME); + exit(EXIT_FAILURE); + } else { + lxc_debug("Using \"%s\" as temporary log file for container %s\n", template, TSTNAME); + close(i); + } + + log.name = TSTNAME; + log.file = template; + log.level = "TRACE"; + log.prefix = "attach"; + log.quiet = false; + log.lxcpath = NULL; + if (lxc_log_init(&log)) + goto on_error; test_lsm_detect(); ret = test_attach(NULL, TSTNAME, "busybox"); if (ret < 0) - return EXIT_FAILURE; + goto on_error; TSTOUT("\n"); ret = test_attach(LXCPATH "/alternate-path-test", TSTNAME, "busybox"); if (ret < 0) - return EXIT_FAILURE; + goto on_error; - (void)rmdir(LXCPATH "/alternate-path-test"); TSTOUT("All tests passed\n"); - return EXIT_SUCCESS; + fret = EXIT_SUCCESS; + +on_error: + if (fret != EXIT_SUCCESS) { + int fd; + + fd = open(template, O_RDONLY); + if (fd >= 0) { + char buf[4096]; + ssize_t buflen; + while ((buflen = read(fd, buf, 1024)) > 0) { + buflen = write(STDERR_FILENO, buf, buflen); + if (buflen <= 0) + break; + } + close(fd); + } + } + (void)rmdir(LXCPATH "/alternate-path-test"); + (void)unlink(template); + exit(fret); } diff --git a/src/tests/lxc-test-lxc-attach b/src/tests/lxc-test-lxc-attach index 380a6db30..2aa0b830b 100755 --- a/src/tests/lxc-test-lxc-attach +++ b/src/tests/lxc-test-lxc-attach @@ -190,17 +190,6 @@ fi rm -f $out $err -if [ $allocate_pty = "pty" ]; then - # Test whether logging pty output to a file works. - trap "rm -f /tmp/ptylog" EXIT INT QUIT PIPE - lxc-attach -n busy -L /tmp/ptylog -- hostname || FAIL "to allocate or setup pty" - if [ ! -s /tmp/ptylog ]; then - FAIL "lxc-attach -n busy -L /tmp/ptylog -- hostname" - fi - - rm -f /tmp/ptylog -fi - lxc-destroy -n busy -f exit 0