X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fnspawn%2Fnspawn.c;h=646c6c02f387063e03e91c664ada0abe6331b27d;hp=80903a7aef0069b8a2b810edaa2133fcb0b685c0;hb=82adf6af7c72b852449346835f33184a841b4796;hpb=b3451bed41453304735b68726571d49fb676d466 diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 80903a7ae..646c6c02f 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -33,8 +33,6 @@ #include #include #include -#include -#include #include #include #include @@ -42,10 +40,14 @@ #include #include #include +#include +#ifdef HAVE_SELINUX +#include +#endif -#include -#include - +#include "sd-daemon.h" +#include "sd-bus.h" +#include "sd-id128.h" #include "log.h" #include "util.h" #include "mkdir.h" @@ -56,17 +58,16 @@ #include "strv.h" #include "path-util.h" #include "loopback-setup.h" -#include "sd-id128.h" #include "dev-setup.h" #include "fdset.h" #include "build.h" #include "fileio.h" #include "bus-util.h" +#include "bus-error.h" #include "ptyfwd.h" - -#ifndef TTY_GID -#define TTY_GID 5 -#endif +#include "bus-kernel.h" +#include "env-util.h" +#include "def.h" typedef enum LinkJournal { LINK_NO, @@ -79,6 +80,8 @@ static char *arg_directory = NULL; static char *arg_user = NULL; static sd_id128_t arg_uuid = {}; static char *arg_machine = NULL; +static char *arg_selinux_context = NULL; +static char *arg_selinux_apifs_context = NULL; static const char *arg_slice = NULL; static bool arg_private_network = false; static bool arg_read_only = false; @@ -109,31 +112,43 @@ static uint64_t arg_retain = (1ULL << CAP_SYS_RESOURCE) | (1ULL << CAP_SYS_BOOT) | (1ULL << CAP_AUDIT_WRITE) | - (1ULL << CAP_AUDIT_CONTROL); + (1ULL << CAP_AUDIT_CONTROL) | + (1ULL << CAP_MKNOD); static char **arg_bind = NULL; static char **arg_bind_ro = NULL; +static char **arg_setenv = NULL; +static bool arg_quiet = false; static int help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" "Spawn a minimal namespace container for debugging, testing and building.\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -D --directory=NAME Root directory for the container\n" - " -b --boot Boot up full system (i.e. invoke init)\n" - " -u --user=USER Run the command under specified user or uid\n" - " --uuid=UUID Set a specific machine UUID for the container\n" - " -M --machine=NAME Set the machine name for the container\n" - " -S --slice=SLICE Place the container in the specified slice\n" - " --private-network Disable network in container\n" - " --read-only Mount the root directory read-only\n" - " --capability=CAP In addition to the default, retain specified\n" - " capability\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, host\n" - " -j Equivalent to --link-journal=host\n" - " --bind=PATH[:PATH] Bind mount a file or directory from the host into\n" - " the container\n" - " --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n", + " -h --help Show this help\n" + " --version Print version string\n" + " -D --directory=NAME Root directory for the container\n" + " -b --boot Boot up full system (i.e. invoke init)\n" + " -u --user=USER Run the command under specified user or uid\n" + " --uuid=UUID Set a specific machine UUID for the container\n" + " -M --machine=NAME Set the machine name for the container\n" + " -S --slice=SLICE Place the container in the specified slice\n" + " -Z --selinux-context=SECLABEL\n" + " Set the SELinux security context to be used by\n" + " processes in the container\n" + " -L --selinux-apifs-context=SECLABEL\n" + " Set the SELinux security context to be used by\n" + " API/tmpfs file systems in the container\n" + " --private-network Disable network in container\n" + " --read-only Mount the root directory read-only\n" + " --capability=CAP In addition to the default, retain specified\n" + " capability\n" + " --drop-capability=CAP Drop the specified capability from the default set\n" + " --link-journal=MODE Link up guest journal, one of no, auto, guest, host\n" + " -j Equivalent to --link-journal=host\n" + " --bind=PATH[:PATH] Bind mount a file or directory from the host into\n" + " the container\n" + " --bind-ro=PATH[:PATH] Similar, but creates a read-only bind mount\n" + " --setenv=NAME=VALUE Pass an environment variable to PID 1\n" + " -q --quiet Do not show status information\n", program_invocation_short_name); return 0; @@ -147,27 +162,34 @@ static int parse_argv(int argc, char *argv[]) { ARG_UUID, ARG_READ_ONLY, ARG_CAPABILITY, + ARG_DROP_CAPABILITY, ARG_LINK_JOURNAL, ARG_BIND, - ARG_BIND_RO + ARG_BIND_RO, + ARG_SETENV, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "directory", required_argument, NULL, 'D' }, - { "user", required_argument, NULL, 'u' }, - { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, - { "boot", no_argument, NULL, 'b' }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "capability", required_argument, NULL, ARG_CAPABILITY }, - { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "machine", required_argument, NULL, 'M' }, - { "slice", required_argument, NULL, 'S' }, - { NULL, 0, NULL, 0 } + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "directory", required_argument, NULL, 'D' }, + { "user", required_argument, NULL, 'u' }, + { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { "boot", no_argument, NULL, 'b' }, + { "uuid", required_argument, NULL, ARG_UUID }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "capability", required_argument, NULL, ARG_CAPABILITY }, + { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, + { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, + { "bind", required_argument, NULL, ARG_BIND }, + { "bind-ro", required_argument, NULL, ARG_BIND_RO }, + { "machine", required_argument, NULL, 'M' }, + { "slice", required_argument, NULL, 'S' }, + { "setenv", required_argument, NULL, ARG_SETENV }, + { "selinux-context", required_argument, NULL, 'Z' }, + { "selinux-apifs-context", required_argument, NULL, 'L' }, + { "quiet", no_argument, NULL, 'q' }, + {} }; int c, r; @@ -175,13 +197,12 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hD:u:bM:jS:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:q", options, NULL)) >= 0) { switch (c) { case 'h': - help(); - return 0; + return help(); case ARG_VERSION: puts(PACKAGE_STRING); @@ -192,7 +213,7 @@ static int parse_argv(int argc, char *argv[]) { free(arg_directory); arg_directory = canonicalize_file_name(optarg); if (!arg_directory) { - log_error("Failed to canonicalize root directory."); + log_error("Invalid root directory: %m"); return -ENOMEM; } @@ -242,11 +263,20 @@ static int parse_argv(int argc, char *argv[]) { break; + case 'Z': + arg_selinux_context = optarg; + break; + + case 'L': + arg_selinux_apifs_context = optarg; + break; + case ARG_READ_ONLY: arg_read_only = true; break; - case ARG_CAPABILITY: { + case ARG_CAPABILITY: + case ARG_DROP_CAPABILITY: { char *state, *word; size_t length; @@ -265,7 +295,11 @@ static int parse_argv(int argc, char *argv[]) { } free(t); - arg_retain |= 1ULL << (uint64_t) cap; + + if (c == ARG_CAPABILITY) + arg_retain |= 1ULL << (uint64_t) cap; + else + arg_retain &= ~(1ULL << (uint64_t) cap); } break; @@ -327,12 +361,32 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_SETENV: { + char **n; + + if (!env_assignment_is_valid(optarg)) { + log_error("Environment variable assignment '%s' is not valid.", optarg); + return -EINVAL; + } + + n = strv_env_set(arg_setenv, optarg); + if (!n) + return log_oom(); + + strv_free(arg_setenv); + arg_setenv = n; + break; + } + + case 'q': + arg_quiet = true; + break; + case '?': return -EINVAL; default: - log_error("Unknown option code %c", c); - return -EINVAL; + assert_not_reached("Unhandled option"); } } @@ -370,6 +424,10 @@ static int mount_all(const char *dest) { for (k = 0; k < ELEMENTSOF(mount_table); k++) { _cleanup_free_ char *where = NULL; +#ifdef HAVE_SELINUX + _cleanup_free_ char *options = NULL; +#endif + const char *o; int t; where = strjoin(dest, "/", mount_table[k].where, NULL); @@ -392,11 +450,24 @@ static int mount_all(const char *dest) { mkdir_p(where, 0755); +#ifdef HAVE_SELINUX + if (arg_selinux_apifs_context && + (streq_ptr(mount_table[k].what, "tmpfs") || streq_ptr(mount_table[k].what, "devpts"))) { + options = strjoin(mount_table[k].options, ",context=\"", arg_selinux_apifs_context, "\"", NULL); + if (!options) + return log_oom(); + + o = options; + } else +#endif + o = mount_table[k].options; + + if (mount(mount_table[k].what, where, mount_table[k].type, mount_table[k].flags, - mount_table[k].options) < 0 && + o) < 0 && mount_table[k].fatal) { log_error("mount(%s) failed: %m", where); @@ -413,39 +484,46 @@ static int mount_binds(const char *dest, char **l, unsigned long flags) { char **x, **y; STRV_FOREACH_PAIR(x, y, l) { - _cleanup_free_ char *where = NULL; + char *where; struct stat source_st, dest_st; + int r; if (stat(*x, &source_st) < 0) { log_error("failed to stat %s: %m", *x); return -errno; } - where = strjoin(dest, "/", *y, NULL); - if (!where) - return log_oom(); - - if (stat(where, &dest_st) == 0) { + where = strappenda(dest, *y); + r = stat(where, &dest_st); + if (r == 0) { if ((source_st.st_mode & S_IFMT) != (dest_st.st_mode & S_IFMT)) { log_error("The file types of %s and %s do not match. Refusing bind mount", *x, where); return -EINVAL; } - } else { - /* Create the mount point, but be conservative -- refuse to create block - * and char devices. */ - if (S_ISDIR(source_st.st_mode)) - mkdir_p_label(where, 0755); - else if (S_ISFIFO(source_st.st_mode)) - mkfifo(where, 0644); - else if (S_ISSOCK(source_st.st_mode)) - mknod(where, 0644 | S_IFSOCK, 0); - else if (S_ISREG(source_st.st_mode)) - touch(where); - else { - log_error("Refusing to create mountpoint for file: %s", *x); - return -ENOTSUP; + } else if (errno == ENOENT) { + r = mkdir_parents_label(where, 0755); + if (r < 0) { + log_error("Failed to bind mount %s: %s", *x, strerror(-r)); + return r; } + } else { + log_error("Failed to bind mount %s: %s", *x, strerror(errno)); + return -errno; + } + /* Create the mount point, but be conservative -- refuse to create block + * and char devices. */ + if (S_ISDIR(source_st.st_mode)) + mkdir_label(where, 0755); + else if (S_ISFIFO(source_st.st_mode)) + mkfifo(where, 0644); + else if (S_ISSOCK(source_st.st_mode)) + mknod(where, 0644 | S_IFSOCK, 0); + else if (S_ISREG(source_st.st_mode)) + touch(where); + else { + log_error("Refusing to create mountpoint for file: %s", *x); + return -ENOTSUP; } if (mount(*x, where, "bind", MS_BIND, NULL) < 0) { @@ -604,40 +682,30 @@ static int copy_devnodes(const char *dest) { u = umask(0000); NULSTR_FOREACH(d, devnodes) { - struct stat st; _cleanup_free_ char *from = NULL, *to = NULL; + struct stat st; - asprintf(&from, "/dev/%s", d); - asprintf(&to, "%s/dev/%s", dest, d); - - if (!from || !to) { - log_oom(); - - if (r == 0) - r = -ENOMEM; - - break; - } + from = strappend("/dev/", d); + to = strjoin(dest, "/dev/", d, NULL); + if (!from || !to) + return log_oom(); if (stat(from, &st) < 0) { if (errno != ENOENT) { log_error("Failed to stat %s: %m", from); - if (r == 0) - r = -errno; + return -errno; } } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { log_error("%s is not a char or block device, cannot copy", from); - if (r == 0) - r = -EIO; + return -EIO; } else if (mknod(to, st.st_mode, st.st_rdev) < 0) { log_error("mknod(%s) failed: %m", dest); - if (r == 0) - r = -errno; + return -errno; } } @@ -792,14 +860,11 @@ static int setup_hostname(void) { } static int setup_journal(const char *directory) { - sd_id128_t machine_id; + sd_id128_t machine_id, this_id; _cleanup_free_ char *p = NULL, *b = NULL, *q = NULL, *d = NULL; char *id; int r; - if (arg_link_journal == LINK_NO) - return 0; - p = strappend(directory, "/etc/machine-id"); if (!p) return log_oom(); @@ -823,6 +888,24 @@ static int setup_journal(const char *directory) { return r; } + r = sd_id128_get_machine(&this_id); + if (r < 0) { + log_error("Failed to retrieve machine ID: %s", strerror(-r)); + return r; + } + + if (sd_id128_equal(machine_id, this_id)) { + log_full(arg_link_journal == LINK_AUTO ? LOG_WARNING : LOG_ERR, + "Host and machine ids are equal (%s): refusing to link journals", id); + if (arg_link_journal == LINK_AUTO) + return 0; + return + -EEXIST; + } + + if (arg_link_journal == LINK_NO) + return 0; + free(p); p = strappend("/var/log/journal/", id); q = strjoin(directory, "/var/log/journal/", id, NULL); @@ -923,16 +1006,36 @@ static int setup_journal(const char *directory) { return 0; } +static int setup_kdbus(const char *dest, const char *path) { + const char *p; + + if (!path) + return 0; + + p = strappenda(dest, "/dev/kdbus"); + if (mkdir(p, 0755) < 0) { + log_error("Failed to create kdbus path: %m"); + return -errno; + } + + if (mount(path, p, "bind", MS_BIND, NULL) < 0) { + log_error("Failed to mount kdbus domain path: %m"); + return -errno; + } + + return 0; +} + static int drop_capabilities(void) { return capability_bounding_set_drop(~arg_retain, false); } -static int register_machine(void) { +static int register_machine(pid_t pid) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_bus_unref_ sd_bus *bus = NULL; int r; - r = sd_bus_open_system(&bus); + r = sd_bus_default_system(&bus); if (r < 0) { log_error("Failed to open system bus: %s", strerror(-r)); return r; @@ -951,17 +1054,69 @@ static int register_machine(void) { SD_BUS_MESSAGE_APPEND_ID128(arg_uuid), "nspawn", "container", - (uint32_t) 0, + (uint32_t) pid, strempty(arg_directory), !isempty(arg_slice), "Slice", "s", arg_slice); if (r < 0) { - log_error("Failed to register machine: %s", error.message ? error.message : strerror(-r)); + log_error("Failed to register machine: %s", bus_error_message(&error, r)); return r; } return 0; } +static int terminate_machine(pid_t pid) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_unref_ sd_bus *bus = NULL; + const char *path; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) { + log_error("Failed to open system bus: %s", strerror(-r)); + return r; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineByPID", + &error, + &reply, + "u", + (uint32_t) pid); + if (r < 0) { + /* Note that the machine might already have been + * cleaned up automatically, hence don't consider it a + * failure if we cannot get the machine object. */ + log_debug("Failed to get machine: %s", bus_error_message(&error, r)); + return 0; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + path, + "org.freedesktop.machine1.Machine", + "Terminate", + &error, + NULL, + NULL); + if (r < 0) { + log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); + return 0; + } + + return 0; +} + static bool audit_enabled(void) { int fd; @@ -976,12 +1131,14 @@ static bool audit_enabled(void) { int main(int argc, char *argv[]) { pid_t pid = 0; int r = EXIT_FAILURE, k; - _cleanup_close_ int master = -1; + _cleanup_close_ int master = -1, kdbus_fd = -1, sync_fd = -1; int n_fd_passed; const char *console = NULL; sigset_t mask; _cleanup_close_pipe_ int kmsg_socket_pair[2] = { -1, -1 }; _cleanup_fdset_free_ FDSet *fds = NULL; + _cleanup_free_ char *kdbus_domain = NULL; + const char *ns; log_parse_environment(); log_open(); @@ -1011,7 +1168,7 @@ int main(int argc, char *argv[]) { path_kill_slashes(arg_directory); if (!arg_machine) { - arg_machine = strdup(path_get_file_name(arg_directory)); + arg_machine = strdup(basename(arg_directory)); if (!arg_machine) { log_oom(); goto finish; @@ -1046,7 +1203,7 @@ int main(int argc, char *argv[]) { goto finish; } - if (path_is_os_tree(arg_directory) <= 0) { + if (arg_boot && path_is_os_tree(arg_directory) <= 0) { log_error("Directory %s doesn't look like an OS root directory (/etc/os-release is missing). Refusing.", arg_directory); goto finish; } @@ -1075,15 +1232,23 @@ int main(int argc, char *argv[]) { goto finish; } - log_info("Spawning container %s on %s. Press ^] three times within 1s to abort execution.", arg_machine, arg_directory); + if (!arg_quiet) + log_info("Spawning container %s on %s. Press ^] three times within 1s to abort execution.", arg_machine, arg_directory); if (unlockpt(master) < 0) { log_error("Failed to unlock tty: %m"); goto finish; } + ns = strappenda("machine-", arg_machine); + kdbus_fd = bus_kernel_create_domain(ns, &kdbus_domain); + if (r < 0) + log_debug("Failed to create kdbus domain: %s", strerror(-r)); + else + log_debug("Successfully created kdbus domain as %s", kdbus_domain); + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { - log_error("Failed to create kmsg socket pair."); + log_error("Failed to create kmsg socket pair: %m"); goto finish; } @@ -1096,6 +1261,12 @@ int main(int argc, char *argv[]) { for (;;) { siginfo_t status; + sync_fd = eventfd(0, EFD_CLOEXEC); + if (sync_fd < 0) { + log_error("Failed to create event fd: %m"); + goto finish; + } + pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWIPC|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS|(arg_private_network ? CLONE_NEWNET : 0), NULL); if (pid < 0) { if (errno == EINVAL) @@ -1113,7 +1284,7 @@ int main(int argc, char *argv[]) { gid_t gid = (gid_t) -1; unsigned n_env = 2; const char *envp[] = { - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "PATH=" DEFAULT_PATH_SPLIT_USR, "container=systemd-nspawn", /* LXC sets container=lxc, so follow the scheme here */ NULL, /* TERM */ NULL, /* HOME */ @@ -1124,6 +1295,8 @@ int main(int argc, char *argv[]) { NULL, /* LISTEN_PID */ NULL }; + char **env_use; + eventfd_t x; envp[n_env] = strv_find_prefix(environ, "TERM="); if (envp[n_env]) @@ -1171,10 +1344,6 @@ int main(int argc, char *argv[]) { goto child_fail; } - r = register_machine(); - if (r < 0) - goto finish; - /* Mark everything as slave, so that we still * receive mounts from the real root, but don't * propagate mounts to the real root. */ @@ -1233,6 +1402,9 @@ int main(int argc, char *argv[]) { if (mount_binds(arg_directory, arg_bind_ro, MS_RDONLY) < 0) goto child_fail; + if (setup_kdbus(arg_directory, kdbus_domain) < 0) + goto child_fail; + if (chdir(arg_directory) < 0) { log_error("chdir(%s) failed: %m", arg_directory); goto child_fail; @@ -1348,6 +1520,28 @@ int main(int argc, char *argv[]) { setup_hostname(); + eventfd_read(sync_fd, &x); + close_nointr_nofail(sync_fd); + sync_fd = -1; + + if (!strv_isempty(arg_setenv)) { + char **n; + + n = strv_env_merge(2, envp, arg_setenv); + if (!n) { + log_oom(); + goto child_fail; + } + + env_use = n; + } else + env_use = (char**) envp; + +#ifdef HAVE_SELINUX + if (arg_selinux_context) + if (setexeccon(arg_selinux_context) < 0) + log_error("setexeccon(\"%s\") failed: %m", arg_selinux_context); +#endif if (arg_boot) { char **a; size_t l; @@ -1359,18 +1553,18 @@ int main(int argc, char *argv[]) { memcpy(a + 1, argv + optind, l * sizeof(char*)); a[0] = (char*) "/usr/lib/systemd/systemd"; - execve(a[0], a, (char**) envp); + execve(a[0], a, env_use); a[0] = (char*) "/lib/systemd/systemd"; - execve(a[0], a, (char**) envp); + execve(a[0], a, env_use); a[0] = (char*) "/sbin/init"; - execve(a[0], a, (char**) envp); + execve(a[0], a, env_use); } else if (argc > optind) - execvpe(argv[optind], argv + optind, (char**) envp); + execvpe(argv[optind], argv + optind, env_use); else { chdir(home ? home : "/root"); - execle("/bin/bash", "-bash", NULL, (char**) envp); + execle("/bin/bash", "-bash", NULL, env_use); } log_error("execv() failed: %m"); @@ -1382,15 +1576,27 @@ int main(int argc, char *argv[]) { fdset_free(fds); fds = NULL; + r = register_machine(pid); + if (r < 0) + goto finish; + + eventfd_write(sync_fd, 1); + close_nointr_nofail(sync_fd); + sync_fd = -1; + k = process_pty(master, &mask, arg_boot ? pid : 0, SIGRTMIN+3); if (k < 0) { r = EXIT_FAILURE; break; } - putc('\n', stdout); + if (!arg_quiet) + putc('\n', stdout); /* Kill if it is not dead yet anyway */ + terminate_machine(pid); + + /* Redundant, but better safe than sorry */ kill(pid, SIGKILL); k = wait_for_terminate(pid, &status); @@ -1408,16 +1614,21 @@ int main(int argc, char *argv[]) { break; } - log_debug("Container %s exited successfully.", arg_machine); + if (!arg_quiet) + log_debug("Container %s exited successfully.", arg_machine); break; } else if (status.si_code == CLD_KILLED && status.si_status == SIGINT) { - log_info("Container %s has been shut down.", arg_machine); + + if (!arg_quiet) + log_info("Container %s has been shut down.", arg_machine); r = 0; break; } else if (status.si_code == CLD_KILLED && status.si_status == SIGHUP) { - log_info("Container %s is being rebooted.", arg_machine); + + if (!arg_quiet) + log_info("Container %s is being rebooted.", arg_machine); continue; } else if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED) { @@ -1438,6 +1649,7 @@ finish: free(arg_directory); free(arg_machine); + free(arg_setenv); return r; }