diff options
author | Lennart Poettering <lennart@poettering.net> | 2025-01-15 20:55:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-15 20:55:01 +0100 |
commit | f8214e2cca9fffcf2931ef2038199735e4170205 (patch) | |
tree | 18dca4f10d5af4e27510f1bb441d76b6386bc7f5 | |
parent | random-util: fix compilation error (diff) | |
parent | logind: improve log message we generate when a user logs in (diff) | |
download | systemd-f8214e2cca9fffcf2931ef2038199735e4170205.tar.xz systemd-f8214e2cca9fffcf2931ef2038199735e4170205.zip |
pam-systemd: introduce "user-light" session type, and make "background-light" the default for system users (#35987)
This implements one idea from #34988: default to "user-light" and
"background-light" for system users, so that the service manager is only
pulled in for sessions that likely need them, i.e. not cron jobs or ftp
logins.
This is a compat break to some degree, but I think a worthy one. I
updated the NEWS file to explain this.
-rw-r--r-- | NEWS | 17 | ||||
-rw-r--r-- | TODO | 6 | ||||
-rw-r--r-- | man/pam_systemd.xml | 10 | ||||
-rw-r--r-- | man/run0.xml | 16 | ||||
-rw-r--r-- | man/sd_session_is_active.xml | 8 | ||||
-rw-r--r-- | src/login/logind-dbus.c | 26 | ||||
-rw-r--r-- | src/login/logind-session.c | 10 | ||||
-rw-r--r-- | src/login/logind-session.h | 23 | ||||
-rw-r--r-- | src/login/pam_systemd.c | 35 | ||||
-rw-r--r-- | src/run/run.c | 130 | ||||
-rw-r--r-- | src/shared/parse-argument.c | 20 | ||||
-rw-r--r-- | src/shared/parse-argument.h | 1 | ||||
-rw-r--r-- | src/shared/varlink-io.systemd.Login.c | 4 | ||||
-rwxr-xr-x | test/units/TEST-35-LOGIN.sh | 34 |
14 files changed, 296 insertions, 44 deletions
@@ -16,6 +16,23 @@ CHANGES WITH 258 in spe: enabled by default. This brings cmdline expansion of transient scopes on par with services. + * systemd-logind PAM sessions that previously were automatically + determined to be of class "background", and which are owned by root + or system accounts, will now automatically be set to class + "background-light" instead. PAM sessions that previously were + automatically determined to be of class "user", and which are owned + by non-root system users, will now automatically be set to class + "user-light" instead. This effectively means that cron jobs or FTP + sessions (i.e. all PAM sessions that have no TTY assigned and neither + are graphical) for system users no longer pull in a service manager + by default. This behaviour can be changed by explicitly setting the + session class (for example via the class= parameter to + pam_systemd.so, or by setting the XDG_SESSION_CLASS environment + variable as input for the service's PAM stack). This change does not + affect graphical sessions, nor does it affect regular users. This is + an incompatible change of sorts, since per-user services will + typically not be available for such PAM sessions of system users. + Announcements of Future Feature Removals: * The D-Bus method org.freedesktop.systemd1.StartAuxiliaryScope() is @@ -134,12 +134,6 @@ Features: really be recognizable via a message id and come with an explanatory catalog message -* logind: introduce "user-light" session class, that is to "user" what - "background-light" is to "background". Then, in logind, if no user class is - specified, and we are not logging in graphically default to this session - class for non-regular users. Effect: if you log into a system user for some - reason, yu won't get the service manager by default. - * introduce new ANSI sequence for communicating log level and structured error metadata to terminals. diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml index 18c3636b6b..1093df9f82 100644 --- a/man/pam_systemd.xml +++ b/man/pam_systemd.xml @@ -116,6 +116,14 @@ <entry>Similar to <literal>user</literal> but sessions of this class are not ordered after <citerefentry><refentrytitle>systemd-user-sessions.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, i.e. may be started before regular sessions are allowed to be established. This session class is the default for sessions of the root user that would otherwise qualify for the <constant>user</constant> class, see above. (Added in v256.)</entry> </row> <row> + <entry><constant>user-light</constant></entry> + <entry>Similar to <constant>user</constant>, but sessions of this class will not pull in the <citerefentry><refentrytitle>user@.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> of the user, and thus possibly have no service manager of the user running. (Added in v258.)</entry> + </row> + <row> + <entry><constant>user-early-light</constant></entry> + <entry>Similar to <constant>user-early</constant>, but sessions of this class will not pull in the <citerefentry><refentrytitle>user@.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> of the user, and thus possibly have no service manager of the user running. (Added in v258.)</entry> + </row> + <row> <entry><constant>user-incomplete</constant></entry> <entry>Similar to <literal>user</literal> but for sessions which are not fully set up yet, i.e. have no home directory mounted or similar. This is used by <citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> to allow users to log in via <citerefentry project='man-pages'><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry> before their home directory is mounted, delaying the mount until the user provided the unlock password. Sessions of this class are upgraded to the regular <constant>user</constant> class once the home directory is activated.</entry> </row> @@ -133,7 +141,7 @@ </row> <row> <entry><constant>background-light</constant></entry> - <entry>Similar to <constant>background</constant>, but sessions of this class will not pull in the <citerefentry><refentrytitle>user@.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> of the user, and thus possibly have no services of the user running. (Added in v256.)</entry> + <entry>Similar to <constant>background</constant>, but sessions of this class will not pull in the <citerefentry><refentrytitle>user@.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> of the user, and thus possibly have no service manager of the user running. (Added in v256.)</entry> </row> <row> <entry><constant>manager</constant></entry> diff --git a/man/run0.xml b/man/run0.xml index 16774813d4..59aa6d05d9 100644 --- a/man/run0.xml +++ b/man/run0.xml @@ -225,6 +225,21 @@ </varlistentry> <varlistentry> + <term><option>--lightweight=<replaceable>BOOLEAN</replaceable></option></term> + + <listitem><para>Controls whether to activate the per-user service manager for the target user. By + default if the target user is <literal>root</literal> or a system user the per-user service manager + is not activated as effect of the <command>run0</command> invocation, otherwise it is.</para> + + <para>This ultimately controls the <varname>$XDG_SESSION_CLASS</varname> variable + <citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry> + respects.</para> + + <xi:include href="version-info.xml" xpointer="v258"/> + </listitem> + </varlistentry> + + <varlistentry> <term><option>--machine=</option></term> <listitem> @@ -321,6 +336,7 @@ <member><citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry></member> <member><citerefentry project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry></member> <member><citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member> + <member><citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry></member> </simplelist></para> </refsect1> diff --git a/man/sd_session_is_active.xml b/man/sd_session_is_active.xml index 1cbef64e00..92cd669dc4 100644 --- a/man/sd_session_is_active.xml +++ b/man/sd_session_is_active.xml @@ -216,10 +216,10 @@ <para><function>sd_session_get_class()</function> may be used to determine the class of the session identified by the specified session identifier. The returned string is one of <literal>user</literal>, - <literal>user-early</literal>, <literal>user-incomplete</literal>, <literal>greeter</literal>, - <literal>lock-screen</literal>, <literal>background</literal>, <literal>background-light</literal>, - <literal>manager</literal> or <literal>manager-early</literal> and needs to be freed with the libc - <citerefentry + <literal>user-early</literal>, <literal>user-light</literal>, <literal>user-early-light</literal>, + <literal>user-incomplete</literal>, <literal>greeter</literal>, <literal>lock-screen</literal>, + <literal>background</literal>, <literal>background-light</literal>, <literal>manager</literal> or + <literal>manager-early</literal> and needs to be freed with the libc <citerefentry project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry> call after use.</para> diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 28bc7b6cef..5d3e2e01b5 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -893,6 +893,7 @@ int manager_create_session( const char *remote_host, Session **ret_session) { + bool mangle_class = false; int r; assert(m); @@ -920,6 +921,10 @@ int manager_create_session( class = SESSION_BACKGROUND; else class = SESSION_USER; + + /* If we determined the class automatically, then let's later potentially change it to early + * or light flavours, once we learn the disposition of the user */ + mangle_class = true; } /* Check if we are already in a logind session, and if so refuse. */ @@ -962,6 +967,25 @@ int manager_create_session( if (r < 0) goto fail; + /* If we picked the session class on our own, and the user is not a regular one, and the session is + * not a graphical one then do not pull in session manager by default. For root make a special + * exception: for TTY logins leave the service manager on, but relax the /run/nologin + * restrictions. */ + if (mangle_class && + IN_SET(user_record_disposition(user->user_record), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC)) { + + if (class == SESSION_USER) { + if (user_record_is_root(user->user_record)) + class = SESSION_USER_EARLY; + else if (SESSION_TYPE_IS_GRAPHICAL(type)) + class = SESSION_USER; + else + class = SESSION_USER_LIGHT; + + } else if (class == SESSION_BACKGROUND) + class = SESSION_BACKGROUND_LIGHT; + } + r = manager_add_session(m, id, &session); if (r < 0) goto fail; @@ -1835,7 +1859,7 @@ static int have_multiple_sessions( /* Check for other users' sessions. Greeter sessions do not * count, and non-login sessions do not count either. */ HASHMAP_FOREACH(session, m->sessions) - if (IN_SET(session->class, SESSION_USER, SESSION_USER_EARLY) && + if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) && session->user->user_record->uid != uid) return true; diff --git a/src/login/logind-session.c b/src/login/logind-session.c index bc48609c26..e8c3ae621b 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -859,7 +859,13 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) { "SESSION_ID=%s", s->id, "USER_ID=%s", s->user->user_record->user_name, "LEADER="PID_FMT, s->leader.pid, - LOG_MESSAGE("New session %s of user %s.", s->id, s->user->user_record->user_name)); + "CLASS=%s", session_class_to_string(s->class), + "TYPE=%s", session_type_to_string(s->type), + LOG_MESSAGE("New session '%s' of user '%s' with class '%s' and type '%s'.", + s->id, + s->user->user_record->user_name, + session_class_to_string(s->class), + session_type_to_string(s->type))); if (!dual_timestamp_is_set(&s->timestamp)) dual_timestamp_now(&s->timestamp); @@ -1704,6 +1710,8 @@ static const char* const session_class_table[_SESSION_CLASS_MAX] = { [SESSION_USER] = "user", [SESSION_USER_EARLY] = "user-early", [SESSION_USER_INCOMPLETE] = "user-incomplete", + [SESSION_USER_LIGHT] = "user-light", + [SESSION_USER_EARLY_LIGHT] = "user-early-light", [SESSION_GREETER] = "greeter", [SESSION_LOCK_SCREEN] = "lock-screen", [SESSION_BACKGROUND] = "background", diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 8ecd90b7ae..c0cf03ff54 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -23,6 +23,8 @@ typedef enum SessionClass { SESSION_USER, /* A regular user session */ SESSION_USER_EARLY, /* A user session, that is not ordered after systemd-user-sessions.service (i.e. for root) */ SESSION_USER_INCOMPLETE, /* A user session that is only half-way set up and doesn't pull in the service manager, and can be upgraded to a full user session later */ + SESSION_USER_LIGHT, /* Just like SESSION_USER, but doesn't pull in service manager */ + SESSION_USER_EARLY_LIGHT, /* Just like SESSION_USER_EARLY, but doesn't pull in service manager */ SESSION_GREETER, /* A login greeter pseudo-session */ SESSION_LOCK_SCREEN, /* A lock screen */ SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */ @@ -36,10 +38,12 @@ typedef enum SessionClass { /* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. It's * generally set for root sessions, but no one else. */ -#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY) +#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_USER_EARLY_LIGHT, SESSION_MANAGER_EARLY) /* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */ -#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_INCOMPLETE, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT) +#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), \ + SESSION_USER, SESSION_USER_EARLY, SESSION_USER_INCOMPLETE, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT, \ + SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT) /* Which session classes want their own per-user service manager? */ #define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND) @@ -48,22 +52,25 @@ typedef enum SessionClass { #define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY, SESSION_NONE)) /* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/ -#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) +#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_EARLY_LIGHT, SESSION_USER_LIGHT, SESSION_GREETER)) /* Which session classes have a lock screen concept? */ -#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) +#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_EARLY_LIGHT, SESSION_USER_LIGHT)) /* Which sessions are candidates to become "display" sessions */ -#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) +#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_EARLY_LIGHT, SESSION_USER_LIGHT, SESSION_GREETER)) /* Which sessions classes should be subject to stop-in-idle */ -#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) +#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT)) /* Which session classes can take control of devices */ -#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) +#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT, SESSION_GREETER, SESSION_LOCK_SCREEN)) /* Which session classes allow changing session types */ -#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) +#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT, SESSION_GREETER, SESSION_LOCK_SCREEN)) + +/* Which session classes are taken into acccount when deciding whether shutdown shall be allowed if other users are logged in */ +#define SESSION_CLASS_IS_INHIBITOR_LIKE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT) typedef enum SessionType { SESSION_UNSPECIFIED, diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index fbc172e9a5..00fc2c360d 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -755,7 +755,7 @@ static int apply_user_record_settings( if (nice_is_valid(ur->nice_level)) { if (nice(ur->nice_level) < 0) - pam_syslog_errno(handle, LOG_ERR, errno, + pam_syslog_errno(handle, LOG_WARNING, errno, "Failed to set nice level to %i, ignoring: %m", ur->nice_level); else pam_debug_syslog(handle, debug, @@ -763,7 +763,6 @@ static int apply_user_record_settings( } for (int rl = 0; rl < _RLIMIT_MAX; rl++) { - if (!ur->rlimits[rl]) continue; @@ -1007,14 +1006,36 @@ static void session_context_mangle( c->vtnr = 0; } - if (isempty(c->type)) + if (isempty(c->type)) { c->type = !isempty(c->display) ? "x11" : !isempty(c->tty) ? "tty" : "unspecified"; + pam_debug_syslog(handle, debug, "Automatically chose session type '%s'.", c->type); + } - if (isempty(c->class)) - c->class = streq(c->type, "unspecified") ? "background" : - ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) && - streq(c->type, "tty")) ? "user-early" : "user"); + if (isempty(c->class)) { + c->class = streq(c->type, "unspecified") ? "background" : "user"; + + /* For non-regular users tweak the type a bit: + * + * - Allow root tty logins *before* systemd-user-sessions.service is run, to allow early boot + * logins to debug things. + * + * - Non-graphical sessions shall be invoked without service manager. + * + * (Note that this somewhat replicates the class mangling logic on systemd-logind.service's + * server side to some degree, in case clients allocate a session and don't specify a + * class. This is somewhat redundant, but we need the class set up properly below.) */ + + if (IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC)) { + if (streq(c->class, "user")) + c->class = user_record_is_root(ur) ? "user-early" : + (STR_IN_SET(c->type, "x11", "wayland", "mir") ? "user" : "user-light"); + else if (streq(c->class, "background")) + c->class = "background-light"; + } + + pam_debug_syslog(handle, debug, "Automatically chose session class '%s'.", c->class); + } if (c->incomplete) { if (streq(c->class, "user")) diff --git a/src/run/run.c b/src/run/run.c index 3f7e0a6360..7538029548 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -88,6 +88,7 @@ static bool arg_ignore_failure = false; static char *arg_background = NULL; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static char *arg_shell_prompt_prefix = NULL; +static int arg_lightweight = -1; STATIC_DESTRUCTOR_REGISTER(arg_description, freep); STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep); @@ -199,6 +200,8 @@ static int help_sudo_mode(void) { " --pty Request allocation of a pseudo TTY for stdio\n" " --pipe Request direct pipe for stdio\n" " --shell-prompt-prefix=PREFIX Set $SHELL_PROMPT_PREFIX\n" + " --lightweight=BOOLEAN Control whether to register a session with service manager\n" + " or without\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -778,6 +781,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { ARG_PTY, ARG_PIPE, ARG_SHELL_PROMPT_PREFIX, + ARG_LIGHTWEIGHT, }; /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions @@ -802,6 +806,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { { "pty", no_argument, NULL, ARG_PTY }, { "pipe", no_argument, NULL, ARG_PIPE }, { "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX }, + { "lightweight", required_argument, NULL, ARG_LIGHTWEIGHT }, {}, }; @@ -914,6 +919,12 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { return r; break; + case ARG_LIGHTWEIGHT: + r = parse_tristate_argument("--lightweight=", optarg, &arg_lightweight); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -947,6 +958,8 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { if (IN_SET(arg_stdio, ARG_STDIO_NONE, ARG_STDIO_AUTO)) arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT; + log_debug("Using %s stdio mode.", arg_stdio == ARG_STDIO_PTY ? "pty" : "direct"); + arg_expand_environment = false; arg_send_sighup = true; @@ -1045,6 +1058,28 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { return log_error_errno(r, "Failed to set $SHELL_PROMPT_PREFIX environment variable: %m"); } + /* When using run0 to acquire privileges temporarily, let's not pull in session manager by + * default. Note that pam_logind/systemd-logind doesn't distinguish between run0-style privilege + * escalation on a TTY and first class (getty-style) TTY logins (and thus gives root a per-session + * manager for interactive TTY sessions), hence let's override the logic explicitly here. We only do + * this for root though, under the assumption that if a regular user temporarily transitions into + * another regular user it's a better default that the full user environment is uniformly + * available. */ + if (arg_lightweight < 0 && !strv_env_get(arg_environment, "XDG_SESSION_CLASS") && privileged_execution()) + arg_lightweight = true; + + if (arg_lightweight >= 0) { + const char *class = + arg_lightweight ? (arg_stdio == ARG_STDIO_PTY ? (privileged_execution() ? "user-early-light" : "user-light") : "background-light") : + (arg_stdio == ARG_STDIO_PTY ? (privileged_execution() ? "user-early" : "user") : "background"); + + log_debug("Setting XDG_SESSION_CLASS to '%s'.", class); + + r = strv_env_assign(&arg_environment, "XDG_SESSION_CLASS", class); + if (r < 0) + return log_error_errno(r, "Failed to set $XDG_SESSION_CLASS environment variable: %m"); + } + return 1; } @@ -1901,6 +1936,46 @@ static int print_unit_invocation(const char *unit, sd_id128_t invocation_id) { return sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); } +typedef struct JobDoneContext { + char *unit; + char *start_job; + sd_bus_slot *match; +} JobDoneContext; + +static void job_done_context_done(JobDoneContext *c) { + assert(c); + + c->unit = mfree(c->unit); + c->start_job = mfree(c->start_job); + c->match = sd_bus_slot_unref(c->match); +} + +static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + JobDoneContext *c = ASSERT_PTR(userdata); + const char *path; + int r; + + assert(m); + + r = sd_bus_message_read(m, "uoss", /* id = */ NULL, &path, /* unit= */ NULL, /* result= */ NULL); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(path, c->start_job)) + return 0; + + /* Notify our caller that the service is now running, just in case. */ + (void) sd_notifyf(/* unset_environment= */ false, + "READY=1\n" + "RUN_UNIT=%s", + c->unit); + + job_done_context_done(c); + return 0; +} + static int start_transient_service(sd_bus *bus) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1974,16 +2049,6 @@ static int start_transient_service(sd_bus *bus) { assert_not_reached(); } - /* Optionally, wait for the start job to complete. If we are supposed to read the service's stdin - * lets skip this however, because we should start that already when the start job is running, and - * there's little point in waiting for the start job to complete in that case anyway, as we'll wait - * for EOF anyway, which is going to be much later. */ - if (!arg_no_block && arg_stdio == ARG_STDIO_NONE) { - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - } - if (arg_unit) { r = unit_name_mangle_with_suffix(arg_unit, "as unit", arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, @@ -1996,6 +2061,36 @@ static int start_transient_service(sd_bus *bus) { return r; } + /* Optionally, wait for the start job to complete. If we are supposed to read the service's stdin + * lets skip this however, because we should start that already when the start job is running, and + * there's little point in waiting for the start job to complete in that case anyway, as we'll wait + * for EOF anyway, which is going to be much later. */ + _cleanup_(job_done_context_done) JobDoneContext job_done_context = {}; + if (!arg_no_block) { + if (arg_stdio == ARG_STDIO_NONE) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } else { + job_done_context.unit = strdup(service); + if (!job_done_context.unit) + return log_oom(); + + /* When we are a bus client we match by sender. Direct connections OTOH have no + * initialized sender field, and hence we ignore the sender then */ + r = sd_bus_match_signal_async( + bus, + &job_done_context.match, + sd_bus_is_bus_client(bus) ? "org.freedesktop.systemd1" : NULL, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "JobRemoved", + match_job_removed, NULL, &job_done_context); + if (r < 0) + return log_error_errno(r, "Failed to install JobRemove match: %m"); + } + } + r = make_transient_service_unit(bus, &m, service, pty_path, peer_fd); if (r < 0) return r; @@ -2005,19 +2100,22 @@ static int start_transient_service(sd_bus *bus) { if (r < 0) return r; - if (w) { - const char *object; - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); + const char *object; + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + if (w) { 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; + } else if (job_done_context.match) { + job_done_context.start_job = strdup(object); + if (!job_done_context.start_job) + return log_oom(); } if (!arg_quiet) { diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index e6159c1482..177028e424 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -31,6 +31,26 @@ int parse_boolean_argument(const char *optname, const char *s, bool *ret) { } } +int parse_tristate_argument(const char *optname, const char *s, int *ret) { + int r; + + if (s) { + r = parse_boolean(s); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean argument to %s: %s.", optname, s); + + if (ret) + *ret = r; + + return r; + } else { + if (ret) + *ret = -1; + + return 0; + } +} + int parse_json_argument(const char *s, sd_json_format_flags_t *ret) { assert(s); assert(ret); diff --git a/src/shared/parse-argument.h b/src/shared/parse-argument.h index 6a1a67aef7..bd577033b8 100644 --- a/src/shared/parse-argument.h +++ b/src/shared/parse-argument.h @@ -6,6 +6,7 @@ #include "sd-json.h" int parse_boolean_argument(const char *optname, const char *s, bool *ret); +int parse_tristate_argument(const char *optname, const char *s, int *ret); int parse_json_argument(const char *s, sd_json_format_flags_t *ret); int parse_path_argument(const char *path, bool suppress_root, char **arg); int parse_signal_argument(const char *s, int *ret); diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c index 9076d47456..f5c5664f66 100644 --- a/src/shared/varlink-io.systemd.Login.c +++ b/src/shared/varlink-io.systemd.Login.c @@ -21,6 +21,10 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(user_early), SD_VARLINK_FIELD_COMMENT("Regular user session whose home directory is not available right now, but will be later, at which point the session class can be upgraded to 'user'"), SD_VARLINK_DEFINE_ENUM_VALUE(user_incomplete), + SD_VARLINK_FIELD_COMMENT("A user session that doesn't pull in the per-user service manager"), + SD_VARLINK_DEFINE_ENUM_VALUE(user_light), + SD_VARLINK_FIELD_COMMENT("The combination of user_early and user_light"), + SD_VARLINK_DEFINE_ENUM_VALUE(user_early_light), SD_VARLINK_FIELD_COMMENT("Display manager greeter screen used for login"), SD_VARLINK_DEFINE_ENUM_VALUE(greeter), SD_VARLINK_FIELD_COMMENT("Similar, but a a lock screen"), diff --git a/test/units/TEST-35-LOGIN.sh b/test/units/TEST-35-LOGIN.sh index 060e1fb18a..5a83b5847a 100755 --- a/test/units/TEST-35-LOGIN.sh +++ b/test/units/TEST-35-LOGIN.sh @@ -711,6 +711,12 @@ testcase_background() { TRANSIENTUNIT0="none$RANDOM.service" TRANSIENTUNIT1="bg$RANDOM.service" TRANSIENTUNIT2="bgg$RANDOM.service" + TRANSIENTUNIT3="bggg$RANDOM.service" + TRANSIENTUNIT4="bgggg$RANDOM.service" + RUN0UNIT0="run0$RANDOM.service" + RUN0UNIT1="runn0$RANDOM.service" + RUN0UNIT2="runnn0$RANDOM.service" + RUN0UNIT3="runnnn0$RANDOM.service" trap background_at_return RETURN @@ -745,6 +751,34 @@ EOF systemctl stop "$TRANSIENTUNIT2" systemctl stop user@"$uid".service + + # Now check that system users automatically get the light session class assigned + systemd-sysusers --inline "u lightuser" + + systemd-run -u "$TRANSIENTUNIT3" -p PAMName="$PAMSERVICE" -p "Environment=XDG_SESSION_TYPE=unspecified" -p Type=exec -p User=lightuser sleep infinity + loginctl | grep lightuser | grep -q background-light + systemctl stop "$TRANSIENTUNIT3" + + systemd-run -u "$TRANSIENTUNIT4" -p PAMName="$PAMSERVICE" -p "Environment=XDG_SESSION_TYPE=tty" -p Type=exec -p User=lightuser sleep infinity + loginctl | grep lightuser | grep -q user-light + systemctl stop "$TRANSIENTUNIT4" + + # Now check that run0's session class control works + systemd-run --service-type=notify run0 -u lightuser --unit="$RUN0UNIT0" sleep infinity + loginctl | grep lightuser | grep -q "background-light " + systemctl stop "$RUN0UNIT0" + + systemd-run --service-type=notify run0 -u lightuser --unit="$RUN0UNIT1" --lightweight=yes sleep infinity + loginctl | grep lightuser | grep -q "background-light " + systemctl stop "$RUN0UNIT1" + + systemd-run --service-type=notify run0 -u lightuser --unit="$RUN0UNIT2" --lightweight=no sleep infinity + loginctl | grep lightuser | grep -q "background " + systemctl stop "$RUN0UNIT2" + + systemd-run --service-type=notify run0 -u root --unit="$RUN0UNIT3" sleep infinity + loginctl | grep root | grep -q "background-light " + systemctl stop "$RUN0UNIT3" } testcase_varlink() { |