summaryrefslogtreecommitdiffstats
path: root/src/core/execute.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2023-06-26 22:42:33 +0200
committerLennart Poettering <lennart@poettering.net>2023-06-28 22:01:55 +0200
commitf9c91932b4d83faf0f95624dc82db353d0726425 (patch)
tree98e3fef1790e0b3214832dfc3d84e5bcbb1e8d40 /src/core/execute.c
parentexecute: when recursively chowning StateDirectory= when spawning services, fo... (diff)
downloadsystemd-f9c91932b4d83faf0f95624dc82db353d0726425.tar.xz
systemd-f9c91932b4d83faf0f95624dc82db353d0726425.zip
execute: add support for XDG_STATE_HOME for placing service state data in --user mode
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
Diffstat (limited to 'src/core/execute.c')
-rw-r--r--src/core/execute.c55
1 files changed, 55 insertions, 0 deletions
diff --git a/src/core/execute.c b/src/core/execute.c
index 11d707b59c..3e065b2ca8 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -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