]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
execute: add support for XDG_STATE_HOME for placing service state data in --user...
authorLennart Poettering <lennart@poettering.net>
Mon, 26 Jun 2023 20:42:33 +0000 (22:42 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 28 Jun 2023 20:01:55 +0000 (22:01 +0200)
This adds support for the new XDG_STATE_HOME env var that was added to
the xdg basedir spec. Previously, because the basedir spec didn't know
the concept we'd alias the backing dir for StateDirectory= to the one
for ConfigurationDirectory= when runnin in --user mode. With this change
we'll make separate. This brings us various benefits, such as proper
"systemctl clean" support, where we can clear service state separately
from service configuration, now in user mode too.

This does not come without complications: retaining compatibility with
older setups is difficult, because we cannot possibly identitfy which
files in existing populated config dirs are actually "state" and which
one are true" configuration.

Hence let's deal with this pragmatically: if we detect that a service
that has both dirs configured only has the configuration dir existing,
then symlink the state dir to the configuration dir to retain
compatibility.

This is not great, but it's the only somewhat reasonable way out I can
see.

Fixes: #25739
man/systemd.exec.xml
man/systemd.unit.xml
src/core/execute.c
src/core/manager.c
src/core/unit-printf.c
test/test-execute/exec-specifier-user.service

index 4752e0e0f390e9bb5fd99a03454a30aca903af84..32128d4fabf74eda39a64a608e569381fd9a9be6 100644 (file)
@@ -1378,7 +1378,7 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
               <row>
                 <entry><varname>StateDirectory=</varname></entry>
                 <entry><filename>/var/lib/</filename></entry>
-                <entry><varname>$XDG_CONFIG_HOME</varname></entry>
+                <entry><varname>$XDG_STATE_HOME</varname></entry>
                 <entry><varname>$STATE_DIRECTORY</varname></entry>
               </row>
               <row>
@@ -1390,7 +1390,7 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
               <row>
                 <entry><varname>LogsDirectory=</varname></entry>
                 <entry><filename>/var/log/</filename></entry>
-                <entry><varname>$XDG_CONFIG_HOME</varname><filename>/log/</filename></entry>
+                <entry><varname>$XDG_STATE_HOME</varname><filename>/log/</filename></entry>
                 <entry><varname>$LOGS_DIRECTORY</varname></entry>
               </row>
               <row>
index e9c7cb238c72d73de0a2553cd9d25195acb83f11..8c3329995d7911fb1905d39c4141f2541e4b9e5e 100644 (file)
@@ -2131,7 +2131,7 @@ Note that this setting is <emphasis>not</emphasis> influenced by the <varname>Us
           <row>
             <entry><literal>%L</literal></entry>
             <entry>Log directory root</entry>
-            <entry>This is either <filename>/var/log</filename> (for the system manager) or the path <literal>$XDG_CONFIG_HOME</literal> resolves to with <filename index="false">/log</filename> appended (for user managers).</entry>
+            <entry>This is either <filename>/var/log</filename> (for the system manager) or the path <varname>$XDG_STATE_HOME</varname> resolves to with <filename index="false">/log</filename> appended (for user managers).</entry>
           </row>
           <xi:include href="standard-specifiers.xml" xpointer="m"/>
           <xi:include href="standard-specifiers.xml" xpointer="M"/>
@@ -2171,7 +2171,7 @@ Note that this setting is <emphasis>not</emphasis> influenced by the <varname>Us
           <row>
             <entry><literal>%S</literal></entry>
             <entry>State directory root</entry>
-            <entry>This is either <filename>/var/lib</filename> (for the system manager) or the path <literal>$XDG_CONFIG_HOME</literal> resolves to (for user managers).</entry>
+            <entry>This is either <filename>/var/lib</filename> (for the system manager) or the path <varname>$XDG_STATE_HOME</varname> resolves to (for user managers).</entry>
           </row>
           <row>
             <entry><literal>%t</literal></entry>
index 11d707b59cf94c46704409151e7d5fb86d9e4668..3e065b2ca8ccc4a0227a116d2da2d86a77163574 100644 (file)
@@ -2511,6 +2511,61 @@ static int setup_exec_directory(
                 if (r < 0)
                         goto fail;
 
+                if (IN_SET(type, EXEC_DIRECTORY_STATE, EXEC_DIRECTORY_LOGS) && params->runtime_scope == RUNTIME_SCOPE_USER) {
+
+                        /* If we are in user mode, and a configuration directory exists but a state directory
+                         * doesn't exist, then we likely are upgrading from an older systemd version that
+                         * didn't know the more recent addition to the xdg-basedir spec: the $XDG_STATE_HOME
+                         * directory. In older systemd versions EXEC_DIRECTORY_STATE was aliased to
+                         * EXEC_DIRECTORY_CONFIGURATION, with the advent of $XDG_STATE_HOME is is now
+                         * seperated. If a service has both dirs configured but only the configuration dir
+                         * exists and the state dir does not, we assume we are looking at an update
+                         * situation. Hence, create a compatibility symlink, so that all expectations are
+                         * met.
+                         *
+                         * (We also do something similar with the log directory, which still doesn't exist in
+                         * the xdg basedir spec. We'll make it a subdir of the state dir.) */
+
+                        /* this assumes the state dir is always created before the configuration dir */
+                        assert_cc(EXEC_DIRECTORY_STATE < EXEC_DIRECTORY_LOGS);
+                        assert_cc(EXEC_DIRECTORY_LOGS < EXEC_DIRECTORY_CONFIGURATION);
+
+                        r = laccess(p, F_OK);
+                        if (r == -ENOENT) {
+                                _cleanup_free_ char *q = NULL;
+
+                                /* OK, we know that the state dir does not exist. Let's see if the dir exists
+                                 * under the configuration hierarchy. */
+
+                                if (type == EXEC_DIRECTORY_STATE)
+                                        q = path_join(params->prefix[EXEC_DIRECTORY_CONFIGURATION], context->directories[type].items[i].path);
+                                else if (type == EXEC_DIRECTORY_LOGS)
+                                        q = path_join(params->prefix[EXEC_DIRECTORY_CONFIGURATION], "log", context->directories[type].items[i].path);
+                                else
+                                        assert_not_reached();
+                                if (!q) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                r = laccess(q, F_OK);
+                                if (r >= 0) {
+                                        /* It does exist! This hence looks like an update. Symlink the
+                                         * configuration directory into the state directory. */
+
+                                        r = symlink_idempotent(q, p, /* make_relative= */ true);
+                                        if (r < 0)
+                                                goto fail;
+
+                                        log_notice("Unit state directory %s missing but matching configuration directory %s exists, assuming update from systemd 253 or older, creating compatibility symlink.", p, q);
+                                        continue;
+                                } else if (r != -ENOENT)
+                                        log_warning_errno(r, "Unable to detect whether unit configuration directory '%s' exists, assuming not: %m", q);
+
+                        } else if (r < 0)
+                                log_warning_errno(r, "Unable to detect whether unit state directory '%s' is missing, assuming it is: %m", p);
+                }
+
                 if (exec_directory_is_private(context, type)) {
                         /* So, here's one extra complication when dealing with DynamicUser=1 units. In that
                          * case we want to avoid leaving a directory around fully accessible that is owned by
index 23df5ce1913e9f9ab30ec8040d742f8314905fa3..8a081d005671f03e61e0ad9d665380078effe38d 100644 (file)
@@ -720,9 +720,9 @@ static int manager_setup_prefix(Manager *m) {
 
         static const struct table_entry paths_user[_EXEC_DIRECTORY_TYPE_MAX] = {
                 [EXEC_DIRECTORY_RUNTIME] =       { SD_PATH_USER_RUNTIME,       NULL  },
-                [EXEC_DIRECTORY_STATE] =         { SD_PATH_USER_CONFIGURATION, NULL  },
+                [EXEC_DIRECTORY_STATE] =         { SD_PATH_USER_STATE_PRIVATE, NULL  },
                 [EXEC_DIRECTORY_CACHE] =         { SD_PATH_USER_STATE_CACHE,   NULL  },
-                [EXEC_DIRECTORY_LOGS] =          { SD_PATH_USER_CONFIGURATION, "log" },
+                [EXEC_DIRECTORY_LOGS] =          { SD_PATH_USER_STATE_PRIVATE, "log" },
                 [EXEC_DIRECTORY_CONFIGURATION] = { SD_PATH_USER_CONFIGURATION, NULL  },
         };
 
index 3977082cc1d324e42648e226736d37a52a912020..9f95984eb630419f1a31cde6e45a100103097fed 100644 (file)
@@ -209,8 +209,8 @@ int unit_full_printf_full(const Unit *u, const char *format, size_t max_length,
          * %C: the cache directory root (e.g. /var/cache or $XDG_CACHE_HOME)
          * %d: the credentials directory ($CREDENTIALS_DIRECTORY)
          * %E: the configuration directory root (e.g. /etc or $XDG_CONFIG_HOME)
-         * %L: the log directory root (e.g. /var/log or $XDG_CONFIG_HOME/log)
-         * %S: the state directory root (e.g. /var/lib or $XDG_CONFIG_HOME)
+         * %L: the log directory root (e.g. /var/log or $XDG_STATE_HOME/log)
+         * %S: the state directory root (e.g. /var/lib or $XDG_STATE_HOME)
          * %t: the runtime directory root (e.g. /run or $XDG_RUNTIME_DIR)
          *
          * %h: the homedir of the running user
index ee0301a4268e81504bb2b0484735b38f0fd1efd7..ab565fb4fbb873b027268be3d89533666e68146d 100644 (file)
@@ -5,7 +5,7 @@ Description=Test for specifiers
 [Service]
 Type=oneshot
 ExecStart=sh -c 'test %t = $$XDG_RUNTIME_DIR'
-ExecStart=sh -c 'test %S = %h/.config'
+ExecStart=sh -c 'test %S = %h/.local/state'
 ExecStart=sh -c 'test %C = %h/.cache'
-ExecStart=sh -c 'test %L = %h/.config/log'
+ExecStart=sh -c 'test %L = %h/.local/state/log'
 ExecStart=sh -c 'test %E = %h/.config'