X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fmachine%2Fmachinectl.c;h=5a6bb056b1dc07ab4c1eb76a8916a87d1030b8b4;hb=a658cafa98ab55ea948c29bc87eb3945d515fb41;hp=d9a16707329e26689ff977baf8bdbb734f89c9e8;hpb=53755121e1c8ebd3db0330bc82965ecf9a986449;p=elogind.git diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index d9a167073..5a6bb056b 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,12 +48,8 @@ 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_LOCAL, - TRANSPORT_REMOTE, - TRANSPORT_CONTAINER -} arg_transport = TRANSPORT_LOCAL; static bool arg_ask_password = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static char *arg_host = NULL; static void pager_open_if_enabled(void) { @@ -126,7 +124,7 @@ static int show_scope_cgroup(sd_bus *bus, const char *unit, pid_t leader) { assert(bus); assert(unit); - if (arg_transport == TRANSPORT_REMOTE) + if (arg_transport == BUS_TRANSPORT_REMOTE) return 0; path = unit_dbus_path_from_name(unit); @@ -520,6 +518,239 @@ 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 = -1, 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" @@ -540,7 +771,8 @@ static int help(void) { " 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; @@ -628,12 +860,12 @@ static int parse_argv(int argc, char *argv[]) { break; case 'H': - arg_transport = TRANSPORT_REMOTE; + arg_transport = BUS_TRANSPORT_REMOTE; arg_host = optarg; break; case 'M': - arg_transport = TRANSPORT_CONTAINER; + arg_transport = BUS_TRANSPORT_CONTAINER; arg_host = optarg; break; @@ -649,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; @@ -666,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; @@ -725,11 +958,6 @@ 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); } @@ -749,21 +977,14 @@ int main(int argc, char*argv[]) { goto finish; } - if (arg_transport == TRANSPORT_LOCAL) - r = sd_bus_open_system(&bus); - else if (arg_transport == TRANSPORT_REMOTE) - r = sd_bus_open_system_remote(arg_host, &bus); - else if (arg_transport == TRANSPORT_CONTAINER) - r = sd_bus_open_system_container(arg_host, &bus); - else - assert_not_reached("Uh, invalid transport..."); + r = bus_open_transport(arg_transport, arg_host, false, &bus); if (r < 0) { - log_error("Failed to connect to machined: %s", strerror(-r)); + log_error("Failed to create bus connection: %s", strerror(-r)); ret = EXIT_FAILURE; goto finish; } - r = machinectl_main(bus, argc, argv, r); + r = machinectl_main(bus, argc, argv); ret = r < 0 ? EXIT_FAILURE : r; finish: