X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fmachine%2Fmachinectl.c;h=7a252c34aab02d3b59e79f3d3cb944329175c44a;hp=7500d5c1c3b416513d809c6a5c0212fd162307b1;hb=04d39279245834494baccfdb9349db8bf80abd13;hpb=a1da85830bfaa77b9eb9c54693e5573559c97e50 diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 7500d5c1c..7a252c34a 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -25,9 +25,10 @@ #include #include #include +#include +#include #include "sd-bus.h" - #include "log.h" #include "util.h" #include "macro.h" @@ -39,6 +40,7 @@ #include "unit-name.h" #include "cgroup-show.h" #include "cgroup-util.h" +#include "ptyfwd.h" static char **arg_property = NULL; static bool arg_all = false; @@ -46,13 +48,9 @@ static bool arg_full = false; static bool arg_no_pager = false; static const char *arg_kill_who = NULL; static int arg_signal = SIGTERM; -static enum transport { - TRANSPORT_NORMAL, - TRANSPORT_SSH, -} arg_transport = TRANSPORT_NORMAL; static bool arg_ask_password = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static char *arg_host = NULL; -static char *arg_user = NULL; static void pager_open_if_enabled(void) { @@ -94,9 +92,6 @@ static int list_machines(sd_bus *bus, char **args, unsigned n) { goto fail; while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { - if (r < 0) - goto fail; - printf("%-32s %-9s %-16s\n", name, class, service); k++; @@ -115,7 +110,7 @@ static int list_machines(sd_bus *bus, char **args, unsigned n) { fail: log_error("Failed to parse reply: %s", strerror(-r)); - return -EIO; + return r; } static int show_scope_cgroup(sd_bus *bus, const char *unit, pid_t leader) { @@ -129,30 +124,28 @@ static int show_scope_cgroup(sd_bus *bus, const char *unit, pid_t leader) { assert(bus); assert(unit); - if (arg_transport == TRANSPORT_SSH) + if (arg_transport == BUS_TRANSPORT_REMOTE) return 0; path = unit_dbus_path_from_name(unit); if (!path) return log_oom(); - r = sd_bus_call_method( + r = sd_bus_get_property( bus, "org.freedesktop.systemd1", path, - "org.freedesktop.DBus.Properties", - "Get", + "org.freedesktop.systemd1.Scope", + "ControlGroup", &error, &reply, - "ss", - "org.freedesktop.systemd1.Scope", - "ControlGroup"); + "s"); if (r < 0) { log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); return r; } - r = sd_bus_message_read(reply, "v", "s", &cgroup); + r = sd_bus_message_read(reply, "s", &cgroup); if (r < 0) { log_error("Failed to parse reply: %s", strerror(-r)); return r; @@ -371,9 +364,6 @@ static int show_one(const char *verb, sd_bus *bus, const char *path, bool show_p const char *name; const char *contents; - if (r < 0) - goto fail; - r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name); if (r < 0) goto fail; @@ -415,7 +405,7 @@ static int show_one(const char *verb, sd_bus *bus, const char *path, bool show_p fail: log_error("Failed to parse reply: %s", strerror(-r)); - return -EIO; + return r; } static int show(sd_bus *bus, char **args, unsigned n) { @@ -460,7 +450,7 @@ static int show(sd_bus *bus, char **args, unsigned n) { r = sd_bus_message_read(reply, "o", &path); if (r < 0) { log_error("Failed to parse reply: %s", strerror(-r)); - return -EIO; + return r; } r = show_one(args[0], bus, path, show_properties, &new_line); @@ -528,26 +518,261 @@ static int terminate_machine(sd_bus *bus, char **args, unsigned n) { return 0; } +static int openpt_in_namespace(pid_t pid, int flags) { + _cleanup_close_ int nsfd = -1, rootfd = -1; + _cleanup_free_ char *ns = NULL, *root = NULL; + _cleanup_close_pipe_ int sock[2] = { -1, -1 }; + struct msghdr mh; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control; + struct cmsghdr *cmsg; + int master, r; + pid_t child; + siginfo_t si; + + r = asprintf(&ns, "/proc/%lu/ns/mnt", (unsigned long) pid); + if (r < 0) + return -ENOMEM; + + nsfd = open(ns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (nsfd < 0) + return -errno; + + r = asprintf(&root, "/proc/%lu/root", (unsigned long) pid); + if (r < 0) + return -ENOMEM; + + rootfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (rootfd < 0) + return -errno; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sock) < 0) + return -errno; + + zero(control); + zero(mh); + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + close_nointr_nofail(sock[0]); + sock[0] = -1; + + r = setns(nsfd, CLONE_NEWNS); + if (r < 0) + _exit(EXIT_FAILURE); + + if (fchdir(rootfd) < 0) + _exit(EXIT_FAILURE); + + if (chroot(".") < 0) + _exit(EXIT_FAILURE); + + master = posix_openpt(flags); + if (master < 0) + _exit(EXIT_FAILURE); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &master, sizeof(int)); + + mh.msg_controllen = cmsg->cmsg_len; + + r = sendmsg(sock[1], &mh, MSG_NOSIGNAL); + close_nointr_nofail(master); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + close_nointr_nofail(sock[1]); + sock[1] = -1; + + if (recvmsg(sock[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0) + return -errno; + + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int *fds; + unsigned n_fds; + + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (n_fds != 1) { + close_many(fds, n_fds); + return -EIO; + } + + master = fds[0]; + } + + r = wait_for_terminate(child, &si); + if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS || master < 0) { + + if (master >= 0) + close_nointr_nofail(master); + + return r < 0 ? r : -EIO; + } + + return master; +} + +static int login_machine(sd_bus *bus, char **args, unsigned n) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_unref_ sd_bus *container_bus = NULL; + _cleanup_close_ int master = -1; + _cleanup_free_ char *getty = NULL; + const char *path, *pty, *p; + uint32_t leader; + sigset_t mask; + int r; + + assert(bus); + assert(args); + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Login only support on local machines."); + return -ENOTSUP; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachine", + &error, + &reply, + "s", args[1]); + if (r < 0) { + log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) { + log_error("Failed to parse reply: %s", strerror(-r)); + return r; + } + + r = sd_bus_get_property( + bus, + "org.freedesktop.machine1", + path, + "org.freedesktop.machine1.Machine", + "Leader", + &error, + &reply2, + "u"); + if (r < 0) { + log_error("Failed to retrieve PID of leader: %s", strerror(-r)); + return r; + } + + r = sd_bus_message_read(reply2, "u", &leader); + if (r < 0) { + log_error("Failed to parse reply: %s", strerror(-r)); + return r; + } + + master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); + if (master < 0) { + log_error("Failed to acquire pseudo tty: %s", strerror(-master)); + return master; + } + + pty = ptsname(master); + if (!pty) { + log_error("Failed to get pty name: %m"); + return -errno; + } + + p = startswith(pty, "/dev/pts/"); + if (!p) { + log_error("Invalid pty name %s.", pty); + return -EIO; + } + + r = sd_bus_open_system_container(args[1], &container_bus); + if (r < 0) { + log_error("Failed to get container bus: %s", strerror(-r)); + return r; + } + + getty = strjoin("container-getty@", p, ".service", NULL); + if (!getty) + return log_oom(); + + if (unlockpt(master) < 0) { + log_error("Failed to unlock tty: %m"); + return -errno; + } + + r = sd_bus_call_method(container_bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + &error, &reply3, + "ss", getty, "replace"); + if (r < 0) { + log_error("Failed to start getty service: %s", bus_error_message(&error, r)); + return r; + } + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1); + assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0); + + log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]); + + r = process_pty(master, &mask, 0, 0); + if (r < 0) { + log_error("Failed to process pseudo tty: %s", strerror(-r)); + return r; + } + + fputc('\n', stdout); + + log_info("Connection to container %s terminated.", args[1]); + + return 0; +} + static int help(void) { printf("%s [OPTIONS...] {COMMAND} ...\n\n" "Send control commands to or query the virtual machine and container registration manager.\n\n" " -h --help Show this help\n" " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Don't prompt for password\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" " -p --property=NAME Show only properties by this name\n" " -a --all Show all properties, including empty ones\n" - " --kill-who=WHO Who to send signal to\n" " -l --full Do not ellipsize output\n" - " -s --signal=SIGNAL Which signal to send\n" - " --no-ask-password Don't prompt for password\n" - " -H --host=[USER@]HOST Show information for remote host\n" - " --no-pager Do not pipe output into a pager\n\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n\n" "Commands:\n" " list List running VMs and containers\n" " status [NAME...] Show VM/container status\n" " show [NAME...] Show properties of one or more VMs/containers\n" " terminate [NAME...] Terminate one or more VMs/containers\n" - " kill [NAME...] Send signal to processes of a VM/container\n", + " kill [NAME...] Send signal to processes of a VM/container\n" + " login [NAME] Get a login prompt on a container\n", program_invocation_short_name); return 0; @@ -572,17 +797,17 @@ static int parse_argv(int argc, char *argv[]) { { "kill-who", required_argument, NULL, ARG_KILL_WHO }, { "signal", required_argument, NULL, 's' }, { "host", required_argument, NULL, 'H' }, - { "privileged", no_argument, NULL, 'P' }, + { "machine", required_argument, NULL, 'M' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { NULL, 0, NULL, 0 } }; - int c; + int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) { switch (c) { @@ -595,22 +820,16 @@ static int parse_argv(int argc, char *argv[]) { puts(SYSTEMD_FEATURES); return 0; - case 'p': { - char **l; - - l = strv_append(arg_property, optarg); - if (!l) - return -ENOMEM; - - strv_free(arg_property); - arg_property = l; + case 'p': + r = strv_extend(&arg_property, optarg); + if (r < 0) + return log_oom(); /* If the user asked for a particular * property, show it to him, even if it is * empty. */ arg_all = true; break; - } case 'a': arg_all = true; @@ -641,8 +860,13 @@ static int parse_argv(int argc, char *argv[]) { break; case 'H': - arg_transport = TRANSPORT_SSH; - parse_user_at_host(optarg, &arg_user, &arg_host); + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_CONTAINER; + arg_host = optarg; break; case '?': @@ -657,7 +881,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int machinectl_main(sd_bus *bus, int argc, char *argv[], const int r) { +static int machinectl_main(sd_bus *bus, int argc, char *argv[]) { static const struct { const char* verb; @@ -674,6 +898,7 @@ static int machinectl_main(sd_bus *bus, int argc, char *argv[], const int r) { { "show", MORE, 1, show }, { "terminate", MORE, 2, terminate_machine }, { "kill", MORE, 2, kill_machine }, + { "login", MORE, 2, login_machine }, }; int left; @@ -733,16 +958,11 @@ static int machinectl_main(sd_bus *bus, int argc, char *argv[], const int r) { assert_not_reached("Unknown comparison operator."); } - if (r < 0) { - log_error("Failed to get D-Bus connection: %s", strerror(-r)); - return -EIO; - } - return verbs[i].dispatch(bus, argv + optind, left); } int main(int argc, char*argv[]) { - int r, retval = EXIT_FAILURE; + int r, ret = EXIT_FAILURE; _cleanup_bus_unref_ sd_bus *bus = NULL; setlocale(LC_ALL, ""); @@ -753,28 +973,24 @@ int main(int argc, char*argv[]) { if (r < 0) goto finish; else if (r == 0) { - retval = EXIT_SUCCESS; + ret = EXIT_SUCCESS; goto finish; } - if (arg_transport == TRANSPORT_NORMAL) - r = sd_bus_open_system(&bus); - else if (arg_transport == TRANSPORT_SSH) - r = bus_connect_system_ssh(arg_host, &bus); - else - assert_not_reached("Uh, invalid transport..."); + r = bus_open_transport(arg_transport, arg_host, false, &bus); if (r < 0) { - retval = EXIT_FAILURE; + log_error("Failed to create bus connection: %s", strerror(-r)); + ret = EXIT_FAILURE; goto finish; } - r = machinectl_main(bus, argc, argv, r); - retval = r < 0 ? EXIT_FAILURE : r; + r = machinectl_main(bus, argc, argv); + ret = r < 0 ? EXIT_FAILURE : r; finish: strv_free(arg_property); pager_close(); - return retval; + return ret; }