summaryrefslogtreecommitdiffstats
path: root/src/run
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
commitfc53809803cd2bc2434e312b19a18fa36776da12 (patch)
treeb4b43bd6538f51965ce32856e9c053d0f90919c8 /src/run
parentAdding upstream version 255.5. (diff)
downloadsystemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz
systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/run')
-rw-r--r--src/run/meson.build14
-rw-r--r--src/run/run.c609
-rw-r--r--src/run/systemd-run0.in23
3 files changed, 558 insertions, 88 deletions
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