From efeb864cb547a2cbf96dc0053a8bdb4d9190b364 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 05:50:45 +0200 Subject: Merging upstream version 256. Signed-off-by: Daniel Baumann --- src/run/meson.build | 14 ++ src/run/run.c | 609 +++++++++++++++++++++++++++++++++++++++++------- src/run/systemd-run0.in | 23 ++ 3 files changed, 558 insertions(+), 88 deletions(-) create mode 100644 src/run/systemd-run0.in (limited to 'src/run') diff --git a/src/run/meson.build b/src/run/meson.build index 597a25a..221b441 100644 --- a/src/run/meson.build +++ b/src/run/meson.build @@ -7,3 +7,17 @@ executables += [ 'sources' : files('run.c'), }, ] + +install_emptydir(bindir) + +meson.add_install_script(sh, '-c', + ln_s.format(bindir / 'systemd-run', + bindir / 'run0')) + +custom_target( + 'systemd-run0', + input : 'systemd-run0.in', + output : 'systemd-run0', + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : pamconfdir != 'no', + install_dir : pamconfdir) diff --git a/src/run/run.c b/src/run/run.c index 88eca0f..5779403 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -17,11 +17,15 @@ #include "bus-unit-util.h" #include "bus-wait-for-jobs.h" #include "calendarspec.h" +#include "capsule-util.h" +#include "chase.h" #include "env-util.h" #include "escape.h" #include "exit-status.h" #include "fd-util.h" #include "format-util.h" +#include "fs-util.h" +#include "hostname-util.h" #include "main-func.h" #include "parse-argument.h" #include "parse-util.h" @@ -31,8 +35,10 @@ #include "ptyfwd.h" #include "signal-util.h" #include "spawn-polkit-agent.h" +#include "special.h" #include "strv.h" #include "terminal-util.h" +#include "uid-classification.h" #include "unit-def.h" #include "unit-name.h" #include "user-util.h" @@ -73,6 +79,9 @@ static bool arg_aggressive_gc = false; static char *arg_working_directory = NULL; static bool arg_shell = false; static char **arg_cmdline = NULL; +static char *arg_exec_path = NULL; +static bool arg_ignore_failure = false; +static char *arg_background = NULL; STATIC_DESTRUCTOR_REGISTER(arg_description, freep); STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep); @@ -82,6 +91,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_socket_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_timer_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_background, freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -91,8 +102,8 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n" - "\n%sRun the specified command in a transient scope or service.%s\n\n" + printf("%1$s [OPTIONS...] COMMAND [ARGUMENTS...]\n" + "\n%5$sRun the specified command in a transient scope or service.%6$s\n\n" " -h --help Show this help\n" " --version Show package version\n" " --no-ask-password Do not prompt for password\n" @@ -104,7 +115,7 @@ static int help(void) { " -p --property=NAME=VALUE Set service or scope unit property\n" " --description=TEXT Description for unit\n" " --slice=SLICE Run in the specified slice\n" - " --slice-inherit Inherit the slice\n" + " --slice-inherit Inherit the slice from the caller\n" " --expand-environment=BOOL Control expansion of environment variables\n" " --no-block Do not wait until operation finished\n" " -r --remain-after-exit Leave service around until explicitly stopped\n" @@ -122,12 +133,14 @@ static int help(void) { " -P --pipe Pass STDIN/STDOUT/STDERR directly to service\n" " -q --quiet Suppress information messages during runtime\n" " -G --collect Unload unit after it ran, even when failed\n" - " -S --shell Invoke a $SHELL interactively\n\n" - "Path options:\n" - " --path-property=NAME=VALUE Set path unit property\n\n" - "Socket options:\n" - " --socket-property=NAME=VALUE Set socket unit property\n\n" - "Timer options:\n" + " -S --shell Invoke a $SHELL interactively\n" + " --ignore-failure Ignore the exit status of the invoked process\n" + " --background=COLOR Set ANSI color for background\n" + "\n%3$sPath options:%4$s\n" + " --path-property=NAME=VALUE Set path unit property\n" + "\n%3$sSocket options:%4$s\n" + " --socket-property=NAME=VALUE Set socket unit property\n" + "\n%3$sTimer options:%4$s\n" " --on-active=SECONDS Run after SECONDS delay\n" " --on-boot=SECONDS Run SECONDS after machine was booted up\n" " --on-startup=SECONDS Run SECONDS after systemd activation\n" @@ -137,6 +150,40 @@ static int help(void) { " --on-timezone-change Run when the timezone changes\n" " --on-clock-change Run when the realtime clock jumps\n" " --timer-property=NAME=VALUE Set timer unit property\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), ansi_normal(), + ansi_highlight(), ansi_normal()); + + return 0; +} + +static int help_sudo_mode(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("run0", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n" + "\n%sElevate privileges interactively.%s\n\n" + " -h --help Show this help\n" + " -V --version Show package version\n" + " --no-ask-password Do not prompt for password\n" + " --machine=CONTAINER Operate on local container\n" + " --unit=UNIT Run under the specified unit name\n" + " --property=NAME=VALUE Set service or scope unit property\n" + " --description=TEXT Description for unit\n" + " --slice=SLICE Run in the specified slice\n" + " --slice-inherit Inherit the slice\n" + " -u --user=USER Run as system user\n" + " -g --group=GROUP Run as system group\n" + " --nice=NICE Nice level\n" + " -D --chdir=PATH Set working directory\n" + " --setenv=NAME[=VALUE] Set environment variable\n" + " --background=COLOR Set ANSI color for background\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -146,6 +193,13 @@ static int help(void) { return 0; } +static bool privileged_execution(void) { + if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) + return false; + + return !arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"); +} + static int add_timer_property(const char *name, const char *val) { char *p; @@ -162,6 +216,18 @@ static int add_timer_property(const char *name, const char *val) { return 0; } +static char **make_login_shell_cmdline(const char *shell) { + _cleanup_free_ char *argv0 = NULL; + + assert(shell); + + argv0 = strjoin("-", shell); /* The - is how shells determine if they shall be consider login shells */ + if (!argv0) + return NULL; + + return strv_new(argv0); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -194,6 +260,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_WAIT, ARG_WORKING_DIRECTORY, ARG_SHELL, + ARG_IGNORE_FAILURE, + ARG_BACKGROUND, }; static const struct option options[] = { @@ -201,6 +269,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "user", no_argument, NULL, ARG_USER }, { "system", no_argument, NULL, ARG_SYSTEM }, + { "capsule", required_argument, NULL, 'C' }, { "scope", no_argument, NULL, ARG_SCOPE }, { "unit", required_argument, NULL, 'u' }, { "description", required_argument, NULL, ARG_DESCRIPTION }, @@ -239,6 +308,8 @@ static int parse_argv(int argc, char *argv[]) { { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY }, { "same-dir", no_argument, NULL, 'd' }, { "shell", no_argument, NULL, 'S' }, + { "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE }, + { "background", required_argument, NULL, ARG_BACKGROUND }, {}, }; @@ -251,7 +322,7 @@ static int parse_argv(int argc, char *argv[]) { /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ optind = 0; - while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tPqGdSu:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hrC:H:M:E:p:tPqGdSu:", options, NULL)) >= 0) switch (c) { @@ -273,6 +344,18 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; + case 'C': + r = capsule_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + + arg_host = optarg; + arg_transport = BUS_TRANSPORT_CAPSULE; + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case ARG_SCOPE: arg_scope = true; break; @@ -282,7 +365,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_DESCRIPTION: - r = free_and_strdup(&arg_description, optarg); + r = free_and_strdup_warn(&arg_description, optarg); if (r < 0) return r; break; @@ -524,6 +607,16 @@ static int parse_argv(int argc, char *argv[]) { arg_shell = true; break; + case ARG_IGNORE_FAILURE: + arg_ignore_failure = true; + break; + + case ARG_BACKGROUND: + r = free_and_strdup_warn(&arg_background, optarg); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -556,11 +649,8 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to get current working directory: %m"); } - if (!arg_service_type) { - arg_service_type = strdup("exec"); - if (!arg_service_type) - return log_oom(); - } + if (!arg_service_type) + arg_service_type = "exec"; arg_wait = true; } @@ -654,6 +744,241 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +static int parse_argv_sudo_mode(int argc, char *argv[]) { + + enum { + ARG_NO_ASK_PASSWORD = 0x100, + ARG_HOST, + ARG_MACHINE, + ARG_UNIT, + ARG_PROPERTY, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SLICE_INHERIT, + ARG_NICE, + ARG_SETENV, + ARG_BACKGROUND, + }; + + /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions + * though (but limit the extension to long options). */ + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "machine", required_argument, NULL, ARG_MACHINE }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "property", required_argument, NULL, ARG_PROPERTY }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "slice", required_argument, NULL, ARG_SLICE }, + { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT }, + { "user", required_argument, NULL, 'u' }, + { "group", required_argument, NULL, 'g' }, + { "nice", required_argument, NULL, ARG_NICE }, + { "chdir", required_argument, NULL, 'D' }, + { "setenv", required_argument, NULL, ARG_SETENV }, + { "background", required_argument, NULL, ARG_BACKGROUND }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() + * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ + optind = 0; + while ((c = getopt_long(argc, argv, "+hVu:g:D:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help_sudo_mode(); + + case 'V': + return version(); + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_MACHINE: + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_UNIT: + arg_unit = optarg; + break; + + case ARG_PROPERTY: + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_DESCRIPTION: + r = free_and_strdup_warn(&arg_description, optarg); + if (r < 0) + return r; + break; + + case ARG_SLICE: + arg_slice = optarg; + break; + + case ARG_SLICE_INHERIT: + arg_slice_inherit = true; + break; + + case 'u': + arg_exec_user = optarg; + break; + + case 'g': + arg_exec_group = optarg; + break; + + case ARG_NICE: + r = parse_nice(optarg, &arg_nice); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", optarg); + + arg_nice_set = true; + break; + + case 'D': + r = parse_path_argument(optarg, true, &arg_working_directory); + if (r < 0) + return r; + + break; + + case ARG_SETENV: + r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + + break; + + case ARG_BACKGROUND: + r = free_and_strdup_warn(&arg_background, optarg); + if (r < 0) + return r; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (!arg_working_directory) { + if (arg_exec_user) { + /* When switching to a specific user, also switch to its home directory. */ + arg_working_directory = strdup("~"); + if (!arg_working_directory) + return log_oom(); + } else { + /* When switching to root without this being specified, then stay in the current directory */ + r = safe_getcwd(&arg_working_directory); + if (r < 0) + return log_error_errno(r, "Failed to get current working directory: %m"); + } + } + + arg_service_type = "exec"; + arg_quiet = true; + arg_wait = true; + arg_aggressive_gc = true; + + arg_stdio = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT; + arg_expand_environment = false; + arg_send_sighup = true; + + _cleanup_strv_free_ char **l = NULL; + if (argc > optind) + l = strv_copy(argv + optind); + else { + const char *e; + + e = strv_env_get(arg_environment, "SHELL"); + if (e) + arg_exec_path = strdup(e); + else { + if (arg_transport == BUS_TRANSPORT_LOCAL) { + r = get_shell(&arg_exec_path); + if (r < 0) + return log_error_errno(r, "Failed to determine shell: %m"); + } else + arg_exec_path = strdup("/bin/sh"); + } + if (!arg_exec_path) + return log_oom(); + + l = make_login_shell_cmdline(arg_exec_path); + } + if (!l) + return log_oom(); + + strv_free_and_replace(arg_cmdline, l); + + if (!arg_slice) { + arg_slice = strdup(SPECIAL_USER_SLICE); + if (!arg_slice) + return log_oom(); + } + + _cleanup_free_ char *un = NULL; + un = getusername_malloc(); + if (!un) + return log_oom(); + + /* Set a bunch of environment variables in a roughly sudo-compatible way */ + r = strv_env_assign(&arg_environment, "SUDO_USER", un); + if (r < 0) + return log_error_errno(r, "Failed to set $SUDO_USER environment variable: %m"); + + r = strv_env_assignf(&arg_environment, "SUDO_UID", UID_FMT, getuid()); + if (r < 0) + return log_error_errno(r, "Failed to set $SUDO_UID environment variable: %m"); + + r = strv_env_assignf(&arg_environment, "SUDO_GID", GID_FMT, getgid()); + if (r < 0) + return log_error_errno(r, "Failed to set $SUDO_GID environment variable: %m"); + + if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_UID=" UID_FMT, getuid()) < 0) + return log_oom(); + + if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_GID=" GID_FMT, getgid()) < 0) + return log_oom(); + + if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_USER=%s", un) < 0) + return log_oom(); + + if (strv_extend(&arg_property, "PAMName=systemd-run0") < 0) + return log_oom(); + + if (!arg_background && arg_stdio == ARG_STDIO_PTY && shall_tint_background()) { + double hue; + + if (privileged_execution()) + hue = 0; /* red */ + else + hue = 60 /* yellow */; + + r = terminal_tint_color(hue, &arg_background); + if (r < 0) + log_debug_errno(r, "Unable to get terminal background color, not tinting background: %m"); + } + + return 1; +} + static int transient_unit_set_properties(sd_bus_message *m, UnitType t, char **properties) { int r; @@ -748,7 +1073,7 @@ static int transient_kill_set_properties(sd_bus_message *m) { return 0; } -static int transient_service_set_properties(sd_bus_message *m, const char *pty_path) { +static int transient_service_set_properties(sd_bus_message *m, const char *pty_path, int pty_fd) { bool send_term = false; int r; @@ -758,6 +1083,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p bool use_ex_prop = arg_expand_environment == 0; assert(m); + assert(pty_path || pty_fd < 0); r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property); if (r < 0) @@ -808,12 +1134,22 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p } if (pty_path) { - r = sd_bus_message_append(m, - "(sv)(sv)(sv)(sv)", - "StandardInput", "s", "tty", - "StandardOutput", "s", "tty", - "StandardError", "s", "tty", - "TTYPath", "s", pty_path); + r = sd_bus_message_append(m, "(sv)", "TTYPath", "s", pty_path); + if (r < 0) + return bus_log_create_error(r); + + if (pty_fd >= 0) + r = sd_bus_message_append(m, + "(sv)(sv)(sv)", + "StandardInputFileDescriptor", "h", pty_fd, + "StandardOutputFileDescriptor", "h", pty_fd, + "StandardErrorFileDescriptor", "h", pty_fd); + else + r = sd_bus_message_append(m, + "(sv)(sv)(sv)", + "StandardInput", "s", "tty", + "StandardOutput", "s", "tty", + "StandardError", "s", "tty"); if (r < 0) return bus_log_create_error(r); @@ -902,7 +1238,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append(m, "s", arg_cmdline[0]); + r = sd_bus_message_append(m, "s", arg_exec_path ?: arg_cmdline[0]); if (r < 0) return bus_log_create_error(r); @@ -913,9 +1249,10 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p if (use_ex_prop) r = sd_bus_message_append_strv( m, - STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand")); + STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand", + arg_ignore_failure ? "ignore-failure" : NULL)); else - r = sd_bus_message_append(m, "b", false); + r = sd_bus_message_append(m, "b", arg_ignore_failure); if (r < 0) return bus_log_create_error(r); @@ -956,18 +1293,13 @@ static int transient_scope_set_properties(sd_bus_message *m, bool allow_pidfd) { if (r < 0) return r; - if (allow_pidfd) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = pidref_set_self(&pidref); - if (r < 0) - return r; + r = pidref_set_self(&pidref); + if (r < 0) + return r; - r = bus_append_scope_pidref(m, &pidref); - } else - r = sd_bus_message_append( - m, "(sv)", - "PIDs", "au", 1, getpid_cached()); + r = bus_append_scope_pidref(m, &pidref, allow_pidfd); if (r < 0) return bus_log_create_error(r); @@ -992,6 +1324,7 @@ static int transient_timer_set_properties(sd_bus_message *m) { } static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { + unsigned soft_reboots_count = 0; const char *unique, *id; char *p; int r; @@ -999,6 +1332,7 @@ static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { assert(bus); assert(t >= 0); assert(t < _UNIT_TYPE_MAX); + assert(ret); r = sd_bus_get_unique_name(bus, &unique); if (r < 0) { @@ -1030,9 +1364,27 @@ static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { "Unique name %s has unexpected format.", unique); - p = strjoin("run-u", id, ".", unit_type_to_string(t)); - if (!p) - return log_oom(); + /* The unique D-Bus names are actually unique per D-Bus instance, so on soft-reboot they will wrap + * and start over since the D-Bus broker is restarted. If there's a failed unit left behind that + * hasn't been garbage collected, we'll conflict. Append the soft-reboot counter to avoid clashing. */ + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_get_property_trivial( + bus, bus_systemd_mgr, "SoftRebootsCount", &error, 'u', &soft_reboots_count); + if (r < 0) + log_debug_errno(r, + "Failed to get SoftRebootsCount property, ignoring: %s", + bus_error_message(&error, r)); + } + + if (soft_reboots_count > 0) { + if (asprintf(&p, "run-u%s-s%u.%s", id, soft_reboots_count, unit_type_to_string(t)) < 0) + return log_oom(); + } else { + p = strjoin("run-u", id, ".", unit_type_to_string(t)); + if (!p) + return log_oom(); + } *ret = p; return 0; @@ -1085,7 +1437,7 @@ static void run_context_check_done(RunContext *c) { else done = true; - if (c->forward && done) /* If the service is gone, it's time to drain the output */ + if (c->forward && !pty_forward_is_done(c->forward) && done) /* If the service is gone, it's time to drain the output */ done = pty_forward_drain(c->forward); if (done) @@ -1155,11 +1507,18 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error } static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) { - RunContext *c = userdata; + RunContext *c = ASSERT_PTR(userdata); assert(f); - if (rcode < 0) { + if (rcode == -ECANCELED) { + log_debug_errno(rcode, "PTY forwarder disconnected."); + if (!arg_wait) + return sd_event_exit(c->event, EXIT_SUCCESS); + + /* If --wait is specified, we'll only exit the pty forwarding, but will continue to wait + * for the service to end. If the user hits ^C we'll exit too. */ + } else if (rcode < 0) { sd_event_exit(c->event, EXIT_FAILURE); return log_error_errno(rcode, "Error on PTY forwarding logic: %m"); } @@ -1172,7 +1531,8 @@ static int make_transient_service_unit( sd_bus *bus, sd_bus_message **message, const char *service, - const char *pty_path) { + const char *pty_path, + int pty_fd) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; int r; @@ -1199,7 +1559,7 @@ static int make_transient_service_unit( if (r < 0) return bus_log_create_error(r); - r = transient_service_set_properties(m, pty_path); + r = transient_service_set_properties(m, pty_path, pty_fd); if (r < 0) return r; @@ -1243,8 +1603,6 @@ static int acquire_invocation_id(sd_bus *bus, const char *unit, sd_id128_t *ret) _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *object = NULL; - const void *p; - size_t l; int r; assert(bus); @@ -1267,20 +1625,55 @@ static int acquire_invocation_id(sd_bus *bus, const char *unit, sd_id128_t *ret) if (r < 0) return log_error_errno(r, "Failed to request invocation ID for unit: %s", bus_error_message(&error, r)); - r = sd_bus_message_read_array(reply, 'y', &p, &l); + r = bus_message_read_id128(reply, ret); if (r < 0) return bus_log_parse_error(r); - if (l == 0) { - *ret = SD_ID128_NULL; - return 0; /* no uuid set */ - } + return r; /* Return true when we get a non-null invocation ID. */ +} + +static void set_window_title(PTYForward *f) { + _cleanup_free_ char *hn = NULL, *cl = NULL, *dot = NULL; + assert(f); + + if (!arg_host) + (void) gethostname_strict(&hn); + + cl = strv_join(arg_cmdline, " "); + if (!cl) + return (void) log_oom(); + + if (emoji_enabled()) + dot = strjoin(special_glyph(privileged_execution() ? SPECIAL_GLYPH_RED_CIRCLE : SPECIAL_GLYPH_YELLOW_CIRCLE), " "); + + if (arg_host || hn) + (void) pty_forward_set_titlef(f, "%s%s on %s", strempty(dot), cl, arg_host ?: hn); + else + (void) pty_forward_set_titlef(f, "%s%s", strempty(dot), cl); + + (void) pty_forward_set_title_prefix(f, dot); +} + +static int chown_to_capsule(const char *path, const char *capsule) { + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + assert(capsule); + + p = path_join("/run/capsules/", capsule); + if (!p) + return -ENOMEM; + + struct stat st; + r = chase_and_stat(p, /* root= */ NULL, CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS, /* ret_path= */ NULL, &st); + if (r < 0) + return r; - if (l != sizeof(sd_id128_t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid UUID size, %zu != %zu.", l, sizeof(sd_id128_t)); + if (uid_is_system(st.st_uid) || gid_is_system(st.st_gid)) /* paranoid safety check */ + return -EPERM; - memcpy(ret, p, l); - return !sd_id128_is_null(*ret); + return chmod_and_chown(path, 0600, st.st_uid, st.st_gid); } static int start_transient_service(sd_bus *bus) { @@ -1288,14 +1681,14 @@ static int start_transient_service(sd_bus *bus) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; _cleanup_free_ char *service = NULL, *pty_path = NULL; - _cleanup_close_ int master = -EBADF; + _cleanup_close_ int master = -EBADF, slave = -EBADF; int r; assert(bus); if (arg_stdio == ARG_STDIO_PTY) { - if (arg_transport == BUS_TRANSPORT_LOCAL) { + if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)) { master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (master < 0) return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); @@ -1304,9 +1697,21 @@ static int start_transient_service(sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to determine tty name: %m"); + if (arg_transport == BUS_TRANSPORT_CAPSULE) { + /* If we are in capsule mode, we must give the capsule UID/GID access to the PTY we just allocated first. */ + + r = chown_to_capsule(pty_path, arg_host); + if (r < 0) + return log_error_errno(r, "Failed to chown tty to capsule UID/GID: %m"); + } + if (unlockpt(master) < 0) return log_error_errno(errno, "Failed to unlock tty: %m"); + slave = open_terminal(pty_path, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (slave < 0) + return log_error_errno(slave, "Failed to open pty slave: %m"); + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = NULL; @@ -1336,6 +1741,9 @@ static int start_transient_service(sd_bus *bus) { pty_path = strdup(s); if (!pty_path) return log_oom(); + + // FIXME: Introduce OpenMachinePTYEx() that accepts ownership/permission as param + // and additionally returns the pty fd, for #33216 and #32999 } else assert_not_reached(); } @@ -1362,9 +1770,10 @@ static int start_transient_service(sd_bus *bus) { return r; } - r = make_transient_service_unit(bus, &m, service, pty_path); + r = make_transient_service_unit(bus, &m, service, pty_path, slave); if (r < 0) return r; + slave = safe_close(slave); polkit_agent_open_if_enabled(arg_transport, arg_ask_password); @@ -1381,7 +1790,7 @@ static int start_transient_service(sd_bus *bus) { r = bus_wait_for_jobs_one(w, object, - arg_quiet, + arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); if (r < 0) return r; @@ -1420,9 +1829,9 @@ static int start_transient_service(sd_bus *bus) { return log_error_errno(r, "Failed to get event loop: %m"); if (master >= 0) { - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(c.event, NULL, SIGINT, NULL, NULL); - (void) sd_event_add_signal(c.event, NULL, SIGTERM, NULL, NULL); + assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGWINCH) >= 0); + + (void) sd_event_set_signal_exit(c.event, true); if (!arg_quiet) log_info("Press ^] three times within 1s to disconnect TTY."); @@ -1435,6 +1844,11 @@ static int start_transient_service(sd_bus *bus) { /* Make sure to process any TTY events before we process bus events */ (void) pty_forward_set_priority(c.forward, SD_EVENT_PRIORITY_IMPORTANT); + + if (!isempty(arg_background)) + (void) pty_forward_set_background_color(c.forward, arg_background); + + set_window_title(c.forward); } path = unit_dbus_path_from_name(service); @@ -1482,12 +1896,13 @@ static int start_transient_service(sd_bus *bus) { if (!isempty(c.result)) log_info("Finished with result: %s", strna(c.result)); - if (c.exit_code == CLD_EXITED) - log_info("Main processes terminated with: code=%s/status=%u", - sigchld_code_to_string(c.exit_code), c.exit_status); - else if (c.exit_code > 0) - log_info("Main processes terminated with: code=%s/status=%s", - sigchld_code_to_string(c.exit_code), signal_to_string(c.exit_status)); + if (c.exit_code > 0) + log_info("Main processes terminated with: code=%s, status=%u/%s", + sigchld_code_to_string(c.exit_code), + c.exit_status, + strna(c.exit_code == CLD_EXITED ? + exit_status_to_string(c.exit_status, EXIT_STATUS_FULL) : + signal_to_string(c.exit_status))); if (timestamp_is_set(c.inactive_enter_usec) && timestamp_is_set(c.inactive_exit_usec) && @@ -1499,23 +1914,36 @@ static int start_transient_service(sd_bus *bus) { log_info("CPU time consumed: %s", FORMAT_TIMESPAN(DIV_ROUND_UP(c.cpu_usage_nsec, NSEC_PER_USEC), USEC_PER_MSEC)); - if (c.memory_peak != UINT64_MAX) - log_info("Memory peak: %s", FORMAT_BYTES(c.memory_peak)); + if (c.memory_peak != UINT64_MAX) { + const char *swap; + + if (c.memory_swap_peak != UINT64_MAX) + swap = strjoina(" (swap: ", FORMAT_BYTES(c.memory_swap_peak), ")"); + else + swap = ""; + + log_info("Memory peak: %s%s", FORMAT_BYTES(c.memory_peak), swap); + } - if (c.memory_swap_peak != UINT64_MAX) - log_info("Memory swap peak: %s", FORMAT_BYTES(c.memory_swap_peak)); + const char *ip_ingress = NULL, *ip_egress = NULL; - if (c.ip_ingress_bytes != UINT64_MAX) - log_info("IP traffic received: %s", FORMAT_BYTES(c.ip_ingress_bytes)); + if (!IN_SET(c.ip_ingress_bytes, 0, UINT64_MAX)) + ip_ingress = strjoina(" received: ", FORMAT_BYTES(c.ip_ingress_bytes)); + if (!IN_SET(c.ip_egress_bytes, 0, UINT64_MAX)) + ip_egress = strjoina(" sent: ", FORMAT_BYTES(c.ip_egress_bytes)); - if (c.ip_egress_bytes != UINT64_MAX) - log_info("IP traffic sent: %s", FORMAT_BYTES(c.ip_egress_bytes)); + if (ip_ingress || ip_egress) + log_info("IP traffic%s%s", strempty(ip_ingress), strempty(ip_egress)); - if (c.io_read_bytes != UINT64_MAX) - log_info("IO bytes read: %s", FORMAT_BYTES(c.io_read_bytes)); + const char *io_read = NULL, *io_write = NULL; - if (c.io_write_bytes != UINT64_MAX) - log_info("IO bytes written: %s", FORMAT_BYTES(c.io_write_bytes)); + if (!IN_SET(c.io_read_bytes, 0, UINT64_MAX)) + io_read = strjoina(" read: ", FORMAT_BYTES(c.io_read_bytes)); + if (!IN_SET(c.io_write_bytes, 0, UINT64_MAX)) + io_write = strjoina(" written: ", FORMAT_BYTES(c.io_write_bytes)); + + if (io_read || io_write) + log_info("IO bytes%s%s", strempty(io_read), strempty(io_write)); } /* Try to propagate the service's return value. But if the service defines @@ -1616,7 +2044,8 @@ static int start_transient_scope(sd_bus *bus) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, + arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); if (r < 0) return r; @@ -1789,7 +2218,7 @@ static int make_transient_trigger_unit( if (r < 0) return bus_log_create_error(r); - r = transient_service_set_properties(m, NULL); + r = transient_service_set_properties(m, /* pty_path = */ NULL, /* pty_fd = */ -EBADF); if (r < 0) return r; @@ -1886,7 +2315,8 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, + arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); if (r < 0) return r; @@ -1900,6 +2330,8 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) { } static bool shall_make_executable_absolute(void) { + if (arg_exec_path) + return false; if (strv_isempty(arg_cmdline)) return false; if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -1916,11 +2348,12 @@ static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); - r = parse_argv(argc, argv); + if (invoked_as(argv, "run0")) + r = parse_argv_sudo_mode(argc, argv); + else + r = parse_argv(argc, argv); if (r <= 0) return r; @@ -1966,7 +2399,7 @@ static int run(int argc, char* argv[]) { * limited direct connection */ if (arg_wait || arg_stdio != ARG_STDIO_NONE || - (arg_runtime_scope == RUNTIME_SCOPE_USER && arg_transport != BUS_TRANSPORT_LOCAL)) + (arg_runtime_scope == RUNTIME_SCOPE_USER && !IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE))) r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus); else r = bus_connect_transport_systemd(arg_transport, arg_host, arg_runtime_scope, &bus); diff --git a/src/run/systemd-run0.in b/src/run/systemd-run0.in new file mode 100644 index 0000000..11f830b --- /dev/null +++ b/src/run/systemd-run0.in @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# This file is part of systemd. +# +# Used by run0 sessions + +{% if ENABLE_HOMED %} +-account sufficient pam_systemd_home.so +{% endif %} +account required pam_unix.so + +{% if HAVE_SELINUX %} +session required pam_selinux.so close +session required pam_selinux.so open +{% endif %} +session required pam_loginuid.so +session optional pam_keyinit.so force revoke +session required pam_namespace.so +{% if ENABLE_HOMED %} +-session optional pam_systemd_home.so +{% endif %} +session optional pam_umask.so silent +session optional pam_systemd.so +session required pam_unix.so -- cgit v1.2.3