summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2025-01-15 20:55:01 +0100
committerGitHub <noreply@github.com>2025-01-15 20:55:01 +0100
commitf8214e2cca9fffcf2931ef2038199735e4170205 (patch)
tree18dca4f10d5af4e27510f1bb441d76b6386bc7f5
parentrandom-util: fix compilation error (diff)
parentlogind: improve log message we generate when a user logs in (diff)
downloadsystemd-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--NEWS17
-rw-r--r--TODO6
-rw-r--r--man/pam_systemd.xml10
-rw-r--r--man/run0.xml16
-rw-r--r--man/sd_session_is_active.xml8
-rw-r--r--src/login/logind-dbus.c26
-rw-r--r--src/login/logind-session.c10
-rw-r--r--src/login/logind-session.h23
-rw-r--r--src/login/pam_systemd.c35
-rw-r--r--src/run/run.c130
-rw-r--r--src/shared/parse-argument.c20
-rw-r--r--src/shared/parse-argument.h1
-rw-r--r--src/shared/varlink-io.systemd.Login.c4
-rwxr-xr-xtest/units/TEST-35-LOGIN.sh34
14 files changed, 296 insertions, 44 deletions
diff --git a/NEWS b/NEWS
index dde530e396..b6b1c80907 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/TODO b/TODO
index 48ab8eac84..81d8151cae 100644
--- a/TODO
+++ b/TODO
@@ -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() {