* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#define _GNU_SOURCE
-#include <assert.h>
-#include <sys/wait.h>
-#include <sys/types.h>
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <lxc/lxccontainer.h>
#include "attach.h"
#include "arguments.h"
-#include "config.h"
-#include "confile.h"
-#include "namespace.h"
#include "caps.h"
+#include "confile.h"
+#include "console.h"
#include "log.h"
+#include "list.h"
+#include "mainloop.h"
#include "utils.h"
+#if HAVE_PTY_H
+#include <pty.h>
+#else
+#include <../include/openpty.h>
+#endif
+
lxc_log_define(lxc_attach_ui, lxc);
static const struct option my_longopts[] = {
{"keep-env", no_argument, 0, 501},
{"keep-var", required_argument, 0, 502},
{"set-var", required_argument, 0, 'v'},
+ {"pty-log", required_argument, 0, 'L'},
+ {"rcfile", required_argument, 0, 'f'},
LXC_COMMON_OPTIONS
};
{
ssize_t count = 0;
- assert(array);
+ if (!array)
+ return -1;
if (*array)
for (; (*array)[count]; count++);
*capacity = new_capacity;
}
- assert(*array);
+ if (!(*array))
+ return -1;
(*array)[count] = value;
return 0;
return -1;
}
break;
+ case 'L':
+ args->console_log = arg;
+ break;
+ case 'f':
+ args->rcfile = arg;
+ break;
}
return 0;
--keep-env Keep all current environment variables. This\n\
is the current default behaviour, but is likely to\n\
change in the future.\n\
+ -L, --pty-log=FILE\n\
+ Log pty output to FILE\n\
-v, --set-var Set an additional variable that is seen by the\n\
attached program in the container. May be specified\n\
multiple times.\n\
--keep-var Keep an additional environment variable. Only\n\
applicable if --clear-env is specified. May be used\n\
- multiple times.\n",
+ multiple times.\n\
+ -f, --rcfile=FILE\n\
+ Load configuration file FILE\n\
+",
.options = my_longopts,
.parser = my_parser,
.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)
+{
+ int ret = -1;
+ struct wrapargs *args = wrap;
+ struct lxc_epoll_descr descr;
+ struct lxc_conf *conf;
+ struct lxc_tty_state *ts;
+
+ INFO("Trying to allocate a pty on the host");
+
+ if (!isatty(args->ptyfd)) {
+ ERROR("Standard file descriptor does not refer to a pty\n.");
+ return -1;
+ }
+
+ conf = c->lxc_conf;
+ 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 to "/dev/tty". Doing this will (a)
+ * prevent segfaults when the container has been setup with
+ * lxc.console = none and (b) provide an easy way to ensure that we
+ * always do the correct thing. strdup() must be used since console.path
+ * is free()ed when we call lxc_container_put(). */
+ free(conf->console.path);
+ conf->console.path = strdup("/dev/tty");
+ if (!conf->console.path)
+ return -1;
+
+ /* 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. */
+ if (ttys_shift_ids(conf) < 0) {
+ ERROR("Failed to shift tty into container");
+ 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. */
+
+ ret = lxc_mainloop_open(&descr);
+ if (ret) {
+ ERROR("failed to create mainloop");
+ goto err2;
+ }
+
+ if (lxc_console_mainloop_add(&descr, conf) < 0) {
+ ERROR("Failed to add handlers to lxc mainloop.");
+ goto err3;
+ }
+
+ ret = lxc_mainloop(&descr, -1);
+ if (ret) {
+ ERROR("mainloop returned an error");
+ goto err3;
+ }
+ ret = 0;
+
+err3:
+ lxc_mainloop_close(&descr);
+err2:
+ if (ts && ts->sigfd != -1)
+ lxc_console_sigwinch_fini(ts);
+err1:
+ lxc_console_delete(&conf->console);
+
+ return ret;
+}
+
+static int stdfd_is_pty(void)
+{
+ if (isatty(STDIN_FILENO))
+ return STDIN_FILENO;
+ if (isatty(STDOUT_FILENO))
+ return STDOUT_FILENO;
+ if (isatty(STDERR_FILENO))
+ return STDERR_FILENO;
+
+ return -1;
+}
+
int main(int argc, char *argv[])
{
- int ret;
+ int ret = -1, r;
+ int wexit = 0;
pid_t pid;
lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT;
- lxc_attach_command_t command;
+ lxc_attach_command_t command = (lxc_attach_command_t){.program = NULL};
- ret = lxc_caps_init();
- if (ret)
- return 1;
+ r = lxc_caps_init();
+ if (r)
+ exit(EXIT_FAILURE);
- ret = lxc_arguments_parse(&my_args, argc, argv);
- if (ret)
- return 1;
+ r = lxc_arguments_parse(&my_args, argc, argv);
+ if (r)
+ exit(EXIT_FAILURE);
if (!my_args.log_file)
my_args.log_file = "none";
- ret = lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority,
+ r = lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority,
my_args.progname, my_args.quiet, my_args.lxcpath[0]);
- if (ret)
- return 1;
+ if (r)
+ exit(EXIT_FAILURE);
lxc_log_options_no_override();
+ if (geteuid()) {
+ if (access(my_args.lxcpath[0], O_RDONLY) < 0) {
+ if (!my_args.quiet)
+ fprintf(stderr, "You lack access to %s\n", my_args.lxcpath[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ struct lxc_container *c = lxc_container_new(my_args.name, my_args.lxcpath[0]);
+ if (!c)
+ exit(EXIT_FAILURE);
+
+ if (my_args.rcfile) {
+ c->clear_config(c);
+ if (!c->load_config(c, my_args.rcfile)) {
+ ERROR("Failed to load rcfile");
+ lxc_container_put(c);
+ exit(EXIT_FAILURE);
+ }
+ c->configfile = strdup(my_args.rcfile);
+ if (!c->configfile) {
+ ERROR("Out of memory setting new config filename");
+ lxc_container_put(c);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!c->may_control(c)) {
+ fprintf(stderr, "Insufficent privileges to control %s\n", c->name);
+ lxc_container_put(c);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!c->is_defined(c)) {
+ fprintf(stderr, "Error: container %s is not defined\n", c->name);
+ lxc_container_put(c);
+ exit(EXIT_FAILURE);
+ }
+
if (remount_sys_proc)
attach_options.attach_flags |= LXC_ATTACH_REMOUNT_PROC_SYS;
if (elevated_privileges)
attach_options.extra_env_vars = extra_env;
attach_options.extra_keep_env = extra_keep;
- if (my_args.argc) {
+ if (my_args.argc > 0) {
command.program = my_args.argv[0];
command.argv = (char**)my_args.argv;
- ret = lxc_attach(my_args.name, my_args.lxcpath[0], lxc_attach_run_command, &command, &attach_options, &pid);
+ }
+
+ 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");
+ goto out;
+ }
+ ret = get_pty_on_host(c, &wrap, &pid);
} else {
- ret = lxc_attach(my_args.name, my_args.lxcpath[0], lxc_attach_run_shell, NULL, &attach_options, &pid);
+ 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 (ret < 0)
- return 1;
+ goto out;
ret = lxc_wait_for_pid_status(pid);
if (ret < 0)
- return 1;
+ goto out;
if (WIFEXITED(ret))
- return WEXITSTATUS(ret);
-
- return 1;
+ wexit = WEXITSTATUS(ret);
+out:
+ lxc_container_put(c);
+ if (ret >= 0)
+ exit(wexit);
+ exit(EXIT_FAILURE);
}