diff options
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | man/systemctl.xml | 10 | ||||
-rw-r--r-- | man/systemd.special.xml | 45 | ||||
-rw-r--r-- | shell-completion/bash/systemd-cgtop | 10 | ||||
-rw-r--r-- | src/core/dbus-manager.c | 32 | ||||
-rw-r--r-- | src/core/execute.c | 20 | ||||
-rw-r--r-- | src/core/main.c | 21 | ||||
-rw-r--r-- | src/core/manager.h | 5 | ||||
-rw-r--r-- | src/core/shutdown.c | 22 | ||||
-rw-r--r-- | src/notify/notify.c | 2 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 30 | ||||
-rw-r--r-- | src/sysv-generator/sysv-generator.c | 2 | ||||
-rw-r--r-- | src/test/test-execute.c | 8 | ||||
-rw-r--r-- | test/exec-runtimedirectory-mode.service | 8 | ||||
-rw-r--r-- | test/exec-runtimedirectory-owner.service | 9 | ||||
-rw-r--r-- | test/exec-runtimedirectory.service | 7 | ||||
-rw-r--r-- | units/.gitignore | 1 | ||||
-rw-r--r-- | units/exit.target | 17 | ||||
-rw-r--r-- | units/systemd-exit.service.in | 17 |
19 files changed, 218 insertions, 51 deletions
diff --git a/Makefile.am b/Makefile.am index 3b35083d0a..5b1431c866 100644 --- a/Makefile.am +++ b/Makefile.am @@ -474,6 +474,7 @@ dist_systemunit_DATA = \ units/getty.target \ units/halt.target \ units/kexec.target \ + units/exit.target \ units/local-fs.target \ units/local-fs-pre.target \ units/initrd.target \ @@ -550,6 +551,7 @@ nodist_systemunit_DATA = \ units/systemd-poweroff.service \ units/systemd-reboot.service \ units/systemd-kexec.service \ + units/systemd-exit.service \ units/systemd-fsck@.service \ units/systemd-fsck-root.service \ units/systemd-machine-id-commit.service \ @@ -601,6 +603,7 @@ EXTRA_DIST += \ units/systemd-poweroff.service.in \ units/systemd-reboot.service.in \ units/systemd-kexec.service.in \ + units/systemd-exit.service.in \ units/user/systemd-exit.service.in \ units/systemd-fsck@.service.in \ units/systemd-fsck-root.service.in \ diff --git a/man/systemctl.xml b/man/systemctl.xml index d8d433e4d3..c1359d1678 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1633,13 +1633,17 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service </varlistentry> <varlistentry> - <term><command>exit</command></term> + <term><command>exit <optional><replaceable>EXIT_CODE</replaceable></optional></command></term> <listitem> <para>Ask the systemd manager to quit. This is only supported for user service managers (i.e. in conjunction - with the <option>--user</option> option) and will fail - otherwise.</para> + with the <option>--user</option> option) or in containers + and is equivalent to <command>poweroff</command> otherwise.</para> + + <para>The systemd manager can exit with a non-zero exit + code if the optional argument + <replaceable>EXIT_CODE</replaceable> is given.</para> </listitem> </varlistentry> diff --git a/man/systemd.special.xml b/man/systemd.special.xml index e4700d950b..6e0dff9b47 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -212,6 +212,26 @@ </listitem> </varlistentry> <varlistentry> + <term><filename>exit.target</filename></term> + <listitem> + <para>A special service unit for shutting down the system or + user service manager. It also works in containers and is + equivalent to <filename>poweroff.target</filename> on + non-container systems.</para> + + <para>Applications wanting to terminate the user service + manager should start this unit. If systemd receives + <constant>SIGTERM</constant> or <constant>SIGINT</constant> + when running as user service daemon, it will start this + unit.</para> + + <para>Normally, this pulls in + <filename>shutdown.target</filename> which in turn should be + conflicted by all units that want to be shut down on user + service manager exit.</para> + </listitem> + </varlistentry> + <varlistentry> <term><filename>final.target</filename></term> <listitem> <para>A special target unit that is used during the shutdown @@ -797,6 +817,7 @@ <para>When systemd runs as a user instance, the following special units are available, which have similar definitions as their system counterparts: + <filename>exit.target</filename>, <filename>default.target</filename>, <filename>shutdown.target</filename>, <filename>sockets.target</filename>, @@ -806,30 +827,6 @@ <filename>printer.target</filename>, <filename>smartcard.target</filename>, <filename>sound.target</filename>.</para> - - <para>In addition, the following special unit is understood only - when systemd runs as service instance:</para> - - <variablelist> - <varlistentry> - <term><filename>exit.target</filename></term> - <listitem> - <para>A special service unit for shutting down the user - service manager.</para> - - <para>Applications wanting to terminate the user service - manager should start this unit. If systemd receives - <constant>SIGTERM</constant> or <constant>SIGINT</constant> - when running as user service daemon, it will start this - unit.</para> - - <para>Normally, this pulls in - <filename>shutdown.target</filename> which in turn should be - conflicted by all units that want to be shut down on user - service manager exit.</para> - </listitem> - </varlistentry> - </variablelist> </refsect1> <refsect1> diff --git a/shell-completion/bash/systemd-cgtop b/shell-completion/bash/systemd-cgtop index eefb8fc5c4..f1ed22fd55 100644 --- a/shell-completion/bash/systemd-cgtop +++ b/shell-completion/bash/systemd-cgtop @@ -34,8 +34,8 @@ _systemd_cgtop() { local comps local -A OPTS=( - [STANDALONE]='-h --help --version -p -t -c -m -i -b --batch -n --iterations -d --delay' - [ARG]='--cpu --depth -M --machine' + [STANDALONE]='-h --help --version -p -t -c -m -i -b --batch -r --raw -k -P' + [ARG]='--cpu --depth -M --machine --recursive -n --iterations -d --delay --order' ) _init_completion || return @@ -45,6 +45,12 @@ _systemd_cgtop() { --machine|-M) comps=$( __get_machines ) ;; + --recursive) + comps='yes no' + ;; + --order) + comps='path tasks cpu memory io' + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 4e5d67fc19..561b6f8bfa 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1201,8 +1201,10 @@ static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *er if (r < 0) return r; - if (m->running_as == MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Exit is only supported for user service managers."); + /* Exit() (in contrast to SetExitCode()) is actually allowed even if + * we are running on the host. It will fall back on reboot() in + * systemd-shutdown if it cannot do the exit() because it isn't a + * container. */ m->exit_code = MANAGER_EXIT; @@ -1450,6 +1452,30 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd return sd_bus_reply_method_return(message, NULL); } +static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { + uint8_t code; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "exit", error); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(message, 'y', &code); + if (r < 0) + return r; + + if (m->running_as == MANAGER_SYSTEM && detect_container() <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "ExitCode can only be set for user service managers or in containers."); + + m->return_value = code; + + return sd_bus_reply_method_return(message, NULL); +} + static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; Manager *m = userdata; @@ -1933,6 +1959,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0), SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0), SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0), + SD_BUS_PROPERTY("ExitCode", "y", bus_property_get_unsigned, offsetof(Manager, return_value), 0), SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), @@ -1986,6 +2013,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("UnitNew", "so", 0), SD_BUS_SIGNAL("UnitRemoved", "so", 0), diff --git a/src/core/execute.c b/src/core/execute.c index 3c308e3e3e..6e14848cd4 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -629,15 +629,6 @@ static int enforce_groups(const ExecContext *context, const char *username, gid_ * we avoid NSS lookups for gid=0. */ if (context->group || username) { - - if (context->group) { - const char *g = context->group; - - r = get_group_creds(&g, &gid); - if (r < 0) - return r; - } - /* First step, initialize groups from /etc/groups */ if (username && gid != 0) { if (initgroups(username, gid) < 0) @@ -1414,6 +1405,17 @@ static int exec_child( } } + if (context->group) { + const char *g = context->group; + + r = get_group_creds(&g, &gid); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; + } + } + + /* If a socket is connected to STDIN/STDOUT/STDERR, we * must sure to drop O_NONBLOCK */ if (socket_fd >= 0) diff --git a/src/core/main.c b/src/core/main.c index 200fe740da..9b59648279 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1254,6 +1254,7 @@ int main(int argc, char *argv[]) { char *switch_root_dir = NULL, *switch_root_init = NULL; struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); const char *error_message = NULL; + uint8_t shutdown_exit_code = 0; #ifdef HAVE_SYSV_COMPAT if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { @@ -1764,11 +1765,6 @@ int main(int argc, char *argv[]) { switch (m->exit_code) { - case MANAGER_EXIT: - retval = EXIT_SUCCESS; - log_debug("Exit."); - goto finish; - case MANAGER_RELOAD: log_info("Reloading."); @@ -1810,11 +1806,13 @@ int main(int argc, char *argv[]) { log_notice("Switching root."); goto finish; + case MANAGER_EXIT: case MANAGER_REBOOT: case MANAGER_POWEROFF: case MANAGER_HALT: case MANAGER_KEXEC: { static const char * const table[_MANAGER_EXIT_CODE_MAX] = { + [MANAGER_EXIT] = "exit", [MANAGER_REBOOT] = "reboot", [MANAGER_POWEROFF] = "poweroff", [MANAGER_HALT] = "halt", @@ -1836,8 +1834,10 @@ int main(int argc, char *argv[]) { finish: pager_close(); - if (m) + if (m) { arg_shutdown_watchdog = m->shutdown_watchdog; + shutdown_exit_code = m->return_value; + } m = manager_free(m); for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) @@ -1978,7 +1978,8 @@ finish: if (shutdown_verb) { char log_level[DECIMAL_STR_MAX(int) + 1]; - const char* command_line[9] = { + char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; + const char* command_line[11] = { SYSTEMD_SHUTDOWN_BINARY_PATH, shutdown_verb, "--log-level", log_level, @@ -2015,6 +2016,12 @@ finish: if (log_get_show_location()) command_line[pos++] = "--log-location"; + if (streq(shutdown_verb, "exit")) { + command_line[pos++] = "--exit-code"; + command_line[pos++] = exit_code; + xsprintf(exit_code, "%d", shutdown_exit_code); + } + assert(pos < ELEMENTSOF(command_line)); if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) { diff --git a/src/core/manager.h b/src/core/manager.h index b955982100..1384eb33a4 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -242,6 +242,11 @@ struct Manager { bool test_run:1; + /* If non-zero, exit with the following value when the systemd + * process terminate. Useful for containers: systemd-nspawn could get + * the return value. */ + uint8_t return_value; + ShowStatus show_status; bool confirm_spawn; bool no_console_output; diff --git a/src/core/shutdown.c b/src/core/shutdown.c index 8cc6efc5b8..5296efce1d 100644 --- a/src/core/shutdown.c +++ b/src/core/shutdown.c @@ -48,6 +48,7 @@ #define FINALIZE_ATTEMPTS 50 static char* arg_verb; +static uint8_t arg_exit_code; static int parse_argv(int argc, char *argv[]) { enum { @@ -55,6 +56,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOG_TARGET, ARG_LOG_COLOR, ARG_LOG_LOCATION, + ARG_EXIT_CODE, }; static const struct option options[] = { @@ -62,6 +64,7 @@ static int parse_argv(int argc, char *argv[]) { { "log-target", required_argument, NULL, ARG_LOG_TARGET }, { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, {} }; @@ -110,6 +113,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_EXIT_CODE: + r = safe_atou8(optarg, &arg_exit_code); + if (r < 0) + log_error("Failed to parse exit code %s, ignoring", optarg); + + break; + case '\001': if (!arg_verb) arg_verb = optarg; @@ -183,6 +193,8 @@ int main(int argc, char *argv[]) { cmd = RB_HALT_SYSTEM; else if (streq(arg_verb, "kexec")) cmd = LINUX_REBOOT_CMD_KEXEC; + else if (streq(arg_verb, "exit")) + cmd = 0; /* ignored, just checking that arg_verb is valid */ else { r = -EINVAL; log_error("Unknown action '%s'.", arg_verb); @@ -339,6 +351,16 @@ int main(int argc, char *argv[]) { if (!in_container) sync(); + if (streq(arg_verb, "exit")) { + if (in_container) + exit(arg_exit_code); + else { + /* We cannot exit() on the host, fallback on another + * method. */ + cmd = RB_POWER_OFF; + } + } + switch (cmd) { case LINUX_REBOOT_CMD_KEXEC: diff --git a/src/notify/notify.c b/src/notify/notify.c index c303bcf718..772e3db69d 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -191,7 +191,7 @@ int main(int argc, char* argv[]) { goto finish; } - r = sd_pid_notify(arg_pid, false, n); + r = sd_pid_notify(arg_pid ? arg_pid : getppid(), false, n); if (r < 0) { log_error_errno(r, "Failed to notify init system: %m"); goto finish; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index d1e443b8a6..e20fc1bbbb 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3005,6 +3005,7 @@ static int prepare_firmware_setup(sd_bus *bus) { } static int start_special(sd_bus *bus, char **args) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; enum action a; int r; @@ -3029,6 +3030,31 @@ static int start_special(sd_bus *bus, char **args) { r = update_reboot_param_file(args[1]); if (r < 0) return r; + } else if (a == ACTION_EXIT && strv_length(args) > 1) { + /* If the exit code is not given on the command line, don't + * reset it to zero: just keep it as it might have been set + * previously. */ + uint8_t code = 0; + + r = safe_atou8(args[1], &code); + if (r < 0) { + log_error("Invalid exit code."); + return -EINVAL; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetExitCode", + &error, + NULL, + "y", code); + if (r < 0) { + log_error("Failed to execute operation: %s", bus_error_message(&error, r)); + return r; + } } if (arg_force >= 2 && @@ -6224,7 +6250,7 @@ static void systemctl_help(void) { " poweroff Shut down and power-off the system\n" " reboot [ARG] Shut down and reboot the system\n" " kexec Shut down and reboot the system with kexec\n" - " exit Request user instance exit\n" + " exit [EXIT_CODE] Request user instance or container exit\n" " switch-root ROOT [INIT] Change to a different root file system\n" " suspend Suspend the system\n" " hibernate Hibernate the system\n" @@ -7211,7 +7237,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { { "default", EQUAL, 1, start_special }, { "rescue", EQUAL, 1, start_special }, { "emergency", EQUAL, 1, start_special }, - { "exit", EQUAL, 1, start_special }, + { "exit", LESS, 2, start_special }, { "reset-failed", MORE, 1, reset_failed }, { "enable", MORE, 2, enable_unit, NOBUS }, { "disable", MORE, 2, enable_unit, NOBUS }, diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c index 45b119362c..f9950c8ab9 100644 --- a/src/sysv-generator/sysv-generator.c +++ b/src/sysv-generator/sysv-generator.c @@ -739,7 +739,7 @@ static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { if (hidden_file(de->d_name)) continue; - if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) { log_warning_errno(errno, "stat() failed on %s/%s: %m", *path, de->d_name); continue; } diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 0f4172e722..dd8ab7dcb8 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -137,6 +137,12 @@ static void test_exec_umask(Manager *m) { test(m, "exec-umask-0177.service", 0, CLD_EXITED); } +static void test_exec_runtimedirectory(Manager *m) { + test(m, "exec-runtimedirectory.service", 0, CLD_EXITED); + test(m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED); + test(m, "exec-runtimedirectory-owner.service", 0, CLD_EXITED); +} + int main(int argc, char *argv[]) { test_function_t tests[] = { test_exec_workingdirectory, @@ -150,6 +156,7 @@ int main(int argc, char *argv[]) { test_exec_group, test_exec_environment, test_exec_umask, + test_exec_runtimedirectory, NULL, }; test_function_t *test = NULL; @@ -165,6 +172,7 @@ int main(int argc, char *argv[]) { return EXIT_TEST_SKIP; } + assert_se(setenv("XDG_RUNTIME_DIR", "/tmp/", 1) == 0); assert_se(set_unit_path(TEST_DIR) >= 0); r = manager_new(MANAGER_USER, true, &m); diff --git a/test/exec-runtimedirectory-mode.service b/test/exec-runtimedirectory-mode.service new file mode 100644 index 0000000000..ba6d7ee39f --- /dev/null +++ b/test/exec-runtimedirectory-mode.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for RuntimeDirectoryMode + +[Service] +ExecStart=/bin/sh -c 's=$(stat -c %a /tmp/test-exec_runtimedirectory-mode); echo $s; exit $(test $s = "750")' +Type=oneshot +RuntimeDirectory=test-exec_runtimedirectory-mode +RuntimeDirectoryMode=0750 diff --git a/test/exec-runtimedirectory-owner.service b/test/exec-runtimedirectory-owner.service new file mode 100644 index 0000000000..077e08d1c5 --- /dev/null +++ b/test/exec-runtimedirectory-owner.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for RuntimeDirectory owner (must not be the default group of the user if Group is set) + +[Service] +ExecStart=/bin/sh -c 'f=/tmp/test-exec_runtimedirectory-owner;g=$(stat -c %G $f); echo "$g"; exit $(test $g = "nobody")' +Type=oneshot +Group=nobody +User=root +RuntimeDirectory=test-exec_runtimedirectory-owner diff --git a/test/exec-runtimedirectory.service b/test/exec-runtimedirectory.service new file mode 100644 index 0000000000..c12a6c63d6 --- /dev/null +++ b/test/exec-runtimedirectory.service @@ -0,0 +1,7 @@ +[Unit] +Description=Test for RuntimeDirectory + +[Service] +ExecStart=/bin/sh -c 'exit $(test -d /tmp/test-exec_runtimedirectory)' +Type=oneshot +RuntimeDirectory=test-exec_runtimedirectory diff --git a/units/.gitignore b/units/.gitignore index d45492d06b..049371884a 100644 --- a/units/.gitignore +++ b/units/.gitignore @@ -30,6 +30,7 @@ /systemd-fsck@.service /systemd-machine-id-commit.service /systemd-halt.service +/systemd-exit.service /systemd-hibernate.service /systemd-hostnamed.service /systemd-hybrid-sleep.service diff --git a/units/exit.target b/units/exit.target new file mode 100644 index 0000000000..f5f953d112 --- /dev/null +++ b/units/exit.target @@ -0,0 +1,17 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Exit the container +Documentation=man:systemd.special(7) +DefaultDependencies=no +Requires=systemd-exit.service +After=systemd-exit.service +AllowIsolate=yes + +[Install] +Alias=ctrl-alt-del.target diff --git a/units/systemd-exit.service.in b/units/systemd-exit.service.in new file mode 100644 index 0000000000..2dbfb36b41 --- /dev/null +++ b/units/systemd-exit.service.in @@ -0,0 +1,17 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Exit the Session +Documentation=man:systemd.special(7) +DefaultDependencies=no +Requires=shutdown.target +After=shutdown.target + +[Service] +Type=oneshot +ExecStart=@SYSTEMCTL@ --force exit |