diff --git a/man/file-hierarchy.xml b/man/file-hierarchy.xml index 9e91af9060..5c9b76674c 100644 --- a/man/file-hierarchy.xml +++ b/man/file-hierarchy.xml @@ -505,69 +505,64 @@ ~/.cache/ - Persistent user cache data. User programs may - place non-essential data in this directory. Flushing this - directory should have no effect on operation of programs, - except for increased runtimes necessary to rebuild these - caches. If an application finds - $XDG_CACHE_HOME set, it should use the - directory specified in it instead of this + Persistent user cache data. User programs may place non-essential data in this + directory. Flushing this directory should have no effect on operation of programs, except for + increased runtimes necessary to rebuild these caches. If an application finds + $XDG_CACHE_HOME set, it should use the directory specified in it instead of this directory. ~/.config/ - Application configuration and state. When a - new user is created, this directory will be empty or not exist - at all. Applications should fall back to defaults should their - configuration or state in this directory be missing. If an - application finds $XDG_CONFIG_HOME set, it - should use the directory specified in it instead of this - directory. + Application configuration. When a new user is created, this directory will be empty + or not exist at all. Applications should fall back to defaults should their configuration in this + directory be missing. If an application finds $XDG_CONFIG_HOME set, it should use + the directory specified in it instead of this directory. ~/.local/bin/ - Executables that shall appear in the user's - $PATH search path. It is recommended not to - place executables in this directory that are not useful for - invocation from a shell; these should be placed in a - subdirectory of ~/.local/lib/ instead. - Care should be taken when placing architecture-dependent - binaries in this place, which might be problematic if the home - directory is shared between multiple hosts with different + Executables that shall appear in the user's $PATH search path. It + is recommended not to place executables in this directory that are not useful for invocation from a + shell; these should be placed in a subdirectory of ~/.local/lib/ instead. Care + should be taken when placing architecture-dependent binaries in this place, which might be + problematic if the home directory is shared between multiple hosts with different architectures. ~/.local/lib/ - Static, private vendor data that is compatible - with all architectures. + Static, private vendor data that is compatible with all + architectures. ~/.local/lib/arch-id/ - Location for placing public dynamic libraries. - The architecture identifier to use is defined on Multiarch - Architecture Specifiers (Tuples) - list. + Location for placing public dynamic libraries. The architecture identifier to use is + defined on Multiarch Architecture Specifiers + (Tuples) list. ~/.local/share/ - Resources shared between multiple packages, - such as fonts or artwork. Usually, the precise location and - format of files stored below this directory is subject to - specifications that ensure interoperability. If an application - finds $XDG_DATA_HOME set, it should use the - directory specified in it instead of this - directory. + Resources shared between multiple packages, such as fonts or artwork. Usually, the + precise location and format of files stored below this directory is subject to specifications that + ensure interoperability. If an application finds $XDG_DATA_HOME set, it should use + the directory specified in it instead of this directory. + + + + ~/.local/state/ + + Application state. When a new user is created, this directory will be empty or not + exist at all. Applications should fall back to defaults should their state in this directory be + missing. If an application finds $XDG_STATE_HOME set, it should use the directory + specified in it instead of this directory. diff --git a/man/sd_path_lookup.xml b/man/sd_path_lookup.xml index eda43960e7..4c1346712b 100644 --- a/man/sd_path_lookup.xml +++ b/man/sd_path_lookup.xml @@ -55,6 +55,7 @@ SD_PATH_USER_CONFIGURATION, SD_PATH_USER_RUNTIME, + SD_PATH_USER_STATE_PRIVATE, SD_PATH_USER_STATE_CACHE, SD_PATH_USER, diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 4752e0e0f3..3960deac3a 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1378,7 +1378,7 @@ CapabilityBoundingSet=~CAP_B CAP_C StateDirectory= /var/lib/ - $XDG_CONFIG_HOME + $XDG_STATE_HOME $STATE_DIRECTORY @@ -1390,7 +1390,7 @@ CapabilityBoundingSet=~CAP_B CAP_C LogsDirectory= /var/log/ - $XDG_CONFIG_HOME/log/ + $XDG_STATE_HOME/log/ $LOGS_DIRECTORY @@ -1447,7 +1447,7 @@ CapabilityBoundingSet=~CAP_B CAP_C The second parameter will be interpreted as a destination path that will be created as a symlink to the directory. The symlinks will be created after any BindPaths= or TemporaryFileSystem= options have been set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by - using the same first parameter, but a different second parameter. + using the same first parameter, but a different second parameter. The directories defined by these options are always created under the standard paths used by systemd (/var/, /run/, /etc/, …). If the service needs @@ -1483,7 +1483,7 @@ StateDirectory=aaa/bbb ccc RuntimeDirectory=foo:bar foo:baz the service manager creates /run/foo (if it does not exist), and /run/bar plus /run/baz as symlinks to - /run/foo. + /run/foo. diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index e9c7cb238c..8c3329995d 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -2131,7 +2131,7 @@ Note that this setting is not influenced by the Us %L Log directory root - This is either /var/log (for the system manager) or the path $XDG_CONFIG_HOME resolves to with /log appended (for user managers). + This is either /var/log (for the system manager) or the path $XDG_STATE_HOME resolves to with /log appended (for user managers). @@ -2171,7 +2171,7 @@ Note that this setting is not influenced by the Us %S State directory root - This is either /var/lib (for the system manager) or the path $XDG_CONFIG_HOME resolves to (for user managers). + This is either /var/lib (for the system manager) or the path $XDG_STATE_HOME resolves to (for user managers). %t diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 495315d55c..4c972aa985 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -736,7 +736,7 @@ d /tmp/foo/bar - - - bmA:1h - %L System or user log directory - In mode, this is the same as $XDG_CONFIG_HOME with /log appended, and /var/log otherwise. + In mode, this is the same as $XDG_STATE_HOME with /log appended, and /var/log otherwise. @@ -744,7 +744,7 @@ d /tmp/foo/bar - - - bmA:1h - %S System or user state directory - In mode, this is the same as $XDG_CONFIG_HOME, and /var/lib otherwise. + In mode, this is the same as $XDG_STATE_HOME, and /var/lib otherwise. %t diff --git a/src/core/execute.c b/src/core/execute.c index 90b19b9e9b..d850a68022 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2466,6 +2466,7 @@ static int create_many_symlinks(const char *root, const char *source, char **sym } static int setup_exec_directory( + Unit *u, const ExecContext *context, const ExecParameters *params, uid_t uid, @@ -2511,6 +2512,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_unit_notice(u, "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_unit_warning_errno(u, r, "Unable to detect whether unit configuration directory '%s' exists, assuming not: %m", q); + + } else if (r < 0) + log_unit_warning_errno(u, 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 @@ -2559,20 +2615,19 @@ static int setup_exec_directory( goto fail; if (is_dir(p, false) > 0 && - (laccess(pp, F_OK) < 0 && errno == ENOENT)) { + (laccess(pp, F_OK) == -ENOENT)) { /* Hmm, the private directory doesn't exist yet, but the normal one exists? If so, move * it over. Most likely the service has been upgraded from one that didn't use * DynamicUser=1, to one that does. */ - log_info("Found pre-existing public %s= directory %s, migrating to %s.\n" - "Apparently, service previously had DynamicUser= turned off, and has now turned it on.", - exec_directory_type_to_string(type), p, pp); + log_unit_info(u, "Found pre-existing public %s= directory %s, migrating to %s.\n" + "Apparently, service previously had DynamicUser= turned off, and has now turned it on.", + exec_directory_type_to_string(type), p, pp); - if (rename(p, pp) < 0) { - r = -errno; + r = RET_NERRNO(rename(p, pp)); + if (r < 0) goto fail; - } } else { /* Otherwise, create the actual directory for the service */ @@ -2634,19 +2689,17 @@ static int setup_exec_directory( /* Hmm, apparently DynamicUser= was once turned on for this service, * but is no longer. Let's move the directory back up. */ - log_info("Found pre-existing private %s= directory %s, migrating to %s.\n" - "Apparently, service previously had DynamicUser= turned on, and has now turned it off.", - exec_directory_type_to_string(type), q, p); + log_unit_info(u, "Found pre-existing private %s= directory %s, migrating to %s.\n" + "Apparently, service previously had DynamicUser= turned on, and has now turned it off.", + exec_directory_type_to_string(type), q, p); - if (unlink(p) < 0) { - r = -errno; + r = RET_NERRNO(unlink(p)); + if (r < 0) goto fail; - } - if (rename(q, p) < 0) { - r = -errno; + r = RET_NERRNO(rename(q, p)); + if (r < 0) goto fail; - } } } @@ -2662,17 +2715,16 @@ static int setup_exec_directory( * as in the common case it is not written to by a service, and shall * not be writable. */ - if (stat(p, &st) < 0) { - r = -errno; + r = RET_NERRNO(stat(p, &st)); + if (r < 0) goto fail; - } /* Still complain if the access mode doesn't match */ if (((st.st_mode ^ context->directories[type].mode) & 07777) != 0) - log_warning("%s \'%s\' already exists but the mode is different. " - "(File system: %o %sMode: %o)", - exec_directory_type_to_string(type), context->directories[type].items[i].path, - st.st_mode & 07777, exec_directory_type_to_string(type), context->directories[type].mode & 07777); + log_unit_warning(u, "%s \'%s\' already exists but the mode is different. " + "(File system: %o %sMode: %o)", + exec_directory_type_to_string(type), context->directories[type].items[i].path, + st.st_mode & 07777, exec_directory_type_to_string(type), context->directories[type].mode & 07777); continue; } @@ -2686,10 +2738,15 @@ static int setup_exec_directory( if (r < 0) goto fail; + /* Skip the rest (which deals with ownership) in user mode, since ownership changes are not + * available to user code anyway */ + if (params->runtime_scope != RUNTIME_SCOPE_SYSTEM) + continue; + /* Then, change the ownership of the whole tree, if necessary. When dynamic users are used we * drop the suid/sgid bits, since we really don't want SUID/SGID files for dynamic UID/GID * assignments to exist. */ - r = path_chown_recursive(pp ?: p, uid, gid, context->dynamic_user ? 01777 : 07777); + r = path_chown_recursive(pp ?: p, uid, gid, context->dynamic_user ? 01777 : 07777, AT_SYMLINK_FOLLOW); if (r < 0) goto fail; } @@ -4066,7 +4123,7 @@ static int apply_mount_namespace( return -ENOMEM; } - if (MANAGER_IS_SYSTEM(u->manager)) { + if (params->runtime_scope == RUNTIME_SCOPE_SYSTEM) { propagate_dir = path_join("/run/systemd/propagate/", u->id); if (!propagate_dir) return -ENOMEM; @@ -4078,9 +4135,12 @@ static int apply_mount_namespace( extension_dir = strdup("/run/systemd/unit-extensions"); if (!extension_dir) return -ENOMEM; - } else + } else { + assert(params->runtime_scope == RUNTIME_SCOPE_USER); + if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0) return -ENOMEM; + } if (root_image) { r = verity_settings_prepare( @@ -4707,14 +4767,17 @@ static void log_command_line(Unit *unit, const char *msg, const char *executable LOG_UNIT_INVOCATION_ID(unit)); } -static bool exec_context_need_unprivileged_private_users(const ExecContext *context, const Manager *manager) { +static bool exec_context_need_unprivileged_private_users( + const ExecContext *context, + const ExecParameters *params) { + assert(context); - assert(manager); + assert(params); /* These options require PrivateUsers= when used in user units, as we need to be in a user namespace * to have permission to enable them when not running as root. If we have effective CAP_SYS_ADMIN * (system manager) then we have privileges and don't need this. */ - if (MANAGER_IS_SYSTEM(manager)) + if (params->runtime_scope != RUNTIME_SCOPE_USER) return false; return context->private_users || @@ -4924,7 +4987,7 @@ static int exec_child( * invocations themselves. Also note that while we'll only invoke NSS modules involved in user management they * might internally call into other NSS modules that are involved in hostname resolution, we never know. */ if (setenv("SYSTEMD_ACTIVATION_UNIT", unit->id, true) != 0 || - setenv("SYSTEMD_ACTIVATION_SCOPE", runtime_scope_to_string(unit->manager->runtime_scope), true) != 0) { + setenv("SYSTEMD_ACTIVATION_SCOPE", runtime_scope_to_string(params->runtime_scope), true) != 0) { *exit_status = EXIT_MEMORY; return log_unit_error_errno(unit, errno, "Failed to update environment: %m"); } @@ -5236,7 +5299,7 @@ static int exec_child( needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) { - r = setup_exec_directory(context, params, uid, gid, dt, needs_mount_namespace, exit_status); + r = setup_exec_directory(unit, context, params, uid, gid, dt, needs_mount_namespace, exit_status); if (r < 0) return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]); } @@ -5392,7 +5455,7 @@ static int exec_child( } } - if (needs_sandboxing && exec_context_need_unprivileged_private_users(context, unit->manager)) { + if (needs_sandboxing && exec_context_need_unprivileged_private_users(context, params)) { /* If we're unprivileged, set up the user namespace first to enable use of the other namespaces. * Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to * set up the all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */ diff --git a/src/core/execute.h b/src/core/execute.h index ee73fb6367..09f007bb4e 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -27,6 +27,7 @@ typedef struct Manager Manager; #include "numa-util.h" #include "open-file.h" #include "path-util.h" +#include "runtime-scope.h" #include "set.h" #include "time-util.h" @@ -140,7 +141,7 @@ struct ExecRuntime { }; typedef enum ExecDirectoryType { - EXEC_DIRECTORY_RUNTIME = 0, + EXEC_DIRECTORY_RUNTIME, EXEC_DIRECTORY_STATE, EXEC_DIRECTORY_CACHE, EXEC_DIRECTORY_LOGS, @@ -418,6 +419,8 @@ typedef enum ExecFlags { /* Parameters for a specific invocation of a command. This structure is put together right before a command is * executed. */ struct ExecParameters { + RuntimeScope runtime_scope; + char **environment; int *fds; diff --git a/src/core/manager.c b/src/core/manager.c index 23df5ce191..8a081d0056 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -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 }, }; diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c index 3977082cc1..9f95984eb6 100644 --- a/src/core/unit-printf.c +++ b/src/core/unit-printf.c @@ -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 diff --git a/src/core/unit.c b/src/core/unit.c index 41b520563b..81467093e7 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -5309,6 +5309,8 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) { if (r < 0) return r; + p->runtime_scope = u->manager->runtime_scope; + p->confirm_spawn = manager_get_confirm_spawn(u->manager); p->cgroup_supported = u->manager->cgroup_supported; p->prefix = u->manager->prefix; diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c index 5207480360..36c4d89e06 100644 --- a/src/libsystemd/sd-path/sd-path.c +++ b/src/libsystemd/sd-path/sd-path.c @@ -281,6 +281,9 @@ static int get_path(uint64_t type, char **buffer, const char **ret) { case SD_PATH_USER_STATE_CACHE: return from_home_dir("XDG_CACHE_HOME", ".cache", buffer, ret); + case SD_PATH_USER_STATE_PRIVATE: + return from_home_dir("XDG_STATE_HOME", ".local/state", buffer, ret); + case SD_PATH_USER: r = get_home_dir(buffer); if (r < 0) diff --git a/src/path/path.c b/src/path/path.c index 5266240247..3e022a467a 100644 --- a/src/path/path.c +++ b/src/path/path.c @@ -20,6 +20,7 @@ static const char *arg_suffix = NULL; static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_TEMPORARY] = "temporary", [SD_PATH_TEMPORARY_LARGE] = "temporary-large", + [SD_PATH_SYSTEM_BINARIES] = "system-binaries", [SD_PATH_SYSTEM_INCLUDE] = "system-include", [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private", @@ -27,6 +28,7 @@ static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_SYSTEM_SHARED] = "system-shared", [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory", [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory", + [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration", [SD_PATH_SYSTEM_RUNTIME] = "system-runtime", [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs", @@ -34,13 +36,17 @@ static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs", [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache", [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool", + [SD_PATH_USER_BINARIES] = "user-binaries", [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private", [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch", [SD_PATH_USER_SHARED] = "user-shared", + [SD_PATH_USER_CONFIGURATION] = "user-configuration", [SD_PATH_USER_RUNTIME] = "user-runtime", [SD_PATH_USER_STATE_CACHE] = "user-state-cache", + [SD_PATH_USER_STATE_PRIVATE] = "user-state-private", + [SD_PATH_USER] = "user", [SD_PATH_USER_DOCUMENTS] = "user-documents", [SD_PATH_USER_MUSIC] = "user-music", @@ -50,6 +56,7 @@ static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_USER_PUBLIC] = "user-public", [SD_PATH_USER_TEMPLATES] = "user-templates", [SD_PATH_USER_DESKTOP] = "user-desktop", + [SD_PATH_SEARCH_BINARIES] = "search-binaries", [SD_PATH_SEARCH_BINARIES_DEFAULT] = "search-binaries-default", [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private", @@ -60,18 +67,22 @@ static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration", [SD_PATH_SYSTEMD_UTIL] = "systemd-util", + [SD_PATH_SYSTEMD_SYSTEM_UNIT] = "systemd-system-unit", [SD_PATH_SYSTEMD_SYSTEM_PRESET] = "systemd-system-preset", [SD_PATH_SYSTEMD_SYSTEM_CONF] = "systemd-system-conf", - [SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT] = "systemd-search-system-unit", - [SD_PATH_SYSTEMD_SYSTEM_GENERATOR] = "systemd-system-generator", - [SD_PATH_SYSTEMD_SEARCH_SYSTEM_GENERATOR] = "systemd-search-system-generator", [SD_PATH_SYSTEMD_USER_UNIT] = "systemd-user-unit", [SD_PATH_SYSTEMD_USER_PRESET] = "systemd-user-preset", [SD_PATH_SYSTEMD_USER_CONF] = "systemd-user-conf", + + [SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT] = "systemd-search-system-unit", [SD_PATH_SYSTEMD_SEARCH_USER_UNIT] = "systemd-search-user-unit", - [SD_PATH_SYSTEMD_SEARCH_USER_GENERATOR] = "systemd-search-user-generator", + + [SD_PATH_SYSTEMD_SYSTEM_GENERATOR] = "systemd-system-generator", [SD_PATH_SYSTEMD_USER_GENERATOR] = "systemd-user-generator", + [SD_PATH_SYSTEMD_SEARCH_SYSTEM_GENERATOR] = "systemd-search-system-generator", + [SD_PATH_SYSTEMD_SEARCH_USER_GENERATOR] = "systemd-search-user-generator", + [SD_PATH_SYSTEMD_SLEEP] = "systemd-sleep", [SD_PATH_SYSTEMD_SHUTDOWN] = "systemd-shutdown", @@ -107,7 +118,7 @@ static int list_homes(void) { continue; } - printf("%s: %s\n", path_table[i], p); + printf("%s%s:%s %s\n", ansi_highlight(), path_table[i], ansi_normal(), p); } return r; diff --git a/src/shared/chown-recursive.c b/src/shared/chown-recursive.c index 883c1ccee4..6aa5f6723e 100644 --- a/src/shared/chown-recursive.c +++ b/src/shared/chown-recursive.c @@ -111,12 +111,15 @@ int path_chown_recursive( const char *path, uid_t uid, gid_t gid, - mode_t mask) { + mode_t mask, + int flags) { _cleanup_close_ int fd = -EBADF; struct stat st; - fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + assert((flags & ~AT_SYMLINK_FOLLOW) == 0); + + fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOATIME|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW)); if (fd < 0) return -errno; diff --git a/src/shared/chown-recursive.h b/src/shared/chown-recursive.h index 00038c3b32..2aab8e7414 100644 --- a/src/shared/chown-recursive.h +++ b/src/shared/chown-recursive.h @@ -3,6 +3,6 @@ #include -int path_chown_recursive(const char *path, uid_t uid, gid_t gid, mode_t mask); +int path_chown_recursive(const char *path, uid_t uid, gid_t gid, mode_t mask, int flags); int fd_chown_recursive(int fd, uid_t uid, gid_t gid, mode_t mask); diff --git a/src/systemd/sd-path.h b/src/systemd/sd-path.h index 0b61484f5e..fcd90aa967 100644 --- a/src/systemd/sd-path.h +++ b/src/systemd/sd-path.h @@ -53,9 +53,10 @@ enum { SD_PATH_USER_SHARED, /* User configuration, state, runtime ... */ - SD_PATH_USER_CONFIGURATION, /* takes both actual configuration (like /etc) and state (like /var/lib) */ + SD_PATH_USER_CONFIGURATION, SD_PATH_USER_RUNTIME, SD_PATH_USER_STATE_CACHE, + /* → SD_PATH_USER_STATE_PRIVATE is added at the bottom */ /* User resources */ SD_PATH_USER, /* $HOME itself */ @@ -82,6 +83,7 @@ enum { * replaces "path" by "search"), since this API is about dirs/paths anyway, and contains "path" * already in the prefix */ SD_PATH_SYSTEMD_UTIL, + SD_PATH_SYSTEMD_SYSTEM_UNIT, SD_PATH_SYSTEMD_SYSTEM_PRESET, SD_PATH_SYSTEMD_SYSTEM_CONF, @@ -116,6 +118,8 @@ enum { SD_PATH_SYSTEMD_SEARCH_SYSTEM_ENVIRONMENT_GENERATOR, SD_PATH_SYSTEMD_SEARCH_USER_ENVIRONMENT_GENERATOR, + SD_PATH_USER_STATE_PRIVATE, + _SD_PATH_MAX }; diff --git a/src/test/test-chown-rec.c b/src/test/test-chown-rec.c index 801b49f7b7..dcff17efec 100644 --- a/src/test/test-chown-rec.c +++ b/src/test/test-chown-rec.c @@ -104,7 +104,7 @@ TEST(chown_recursive) { assert_se(st.st_gid == gid); assert_se(has_xattr(p)); - assert_se(path_chown_recursive(t, 1, 2, 07777) >= 0); + assert_se(path_chown_recursive(t, 1, 2, 07777, 0) >= 0); p = strjoina(t, "/dir"); assert_se(lstat(p, &st) >= 0); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index eabac56320..a7de3c87fe 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -255,9 +255,9 @@ static int specifier_directory(char specifier, const void *data, const char *roo static const struct table_entry paths_user[] = { [DIRECTORY_RUNTIME] = { SD_PATH_USER_RUNTIME }, - [DIRECTORY_STATE] = { SD_PATH_USER_CONFIGURATION }, + [DIRECTORY_STATE] = { SD_PATH_USER_STATE_PRIVATE }, [DIRECTORY_CACHE] = { SD_PATH_USER_STATE_CACHE }, - [DIRECTORY_LOGS] = { SD_PATH_USER_CONFIGURATION, "log" }, + [DIRECTORY_LOGS] = { SD_PATH_USER_STATE_PRIVATE, "log" }, }; const struct table_entry *paths; diff --git a/test/test-execute/exec-specifier-user.service b/test/test-execute/exec-specifier-user.service index ee0301a426..ab565fb4fb 100644 --- a/test/test-execute/exec-specifier-user.service +++ b/test/test-execute/exec-specifier-user.service @@ -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' diff --git a/test/units/testsuite-23.statedir.sh b/test/units/testsuite-23.statedir.sh new file mode 100755 index 0000000000..b592314a09 --- /dev/null +++ b/test/units/testsuite-23.statedir.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -eux +set -o pipefail + +# Test unit configuration/state/cache/log/runtime data cleanup + +export HOME=/root +export XDG_RUNTIME_DIR=/run/user/0 + +systemctl start user@0.service + +( ! test -d "$HOME"/.local/state/foo) +( ! test -d "$HOME"/.config/foo) + +systemd-run --user -p StateDirectory=foo --wait /bin/true + +test -d "$HOME"/.local/state/foo +( ! test -L "$HOME"/.local/state/foo) +( ! test -d "$HOME"/.config/foo) + +systemd-run --user -p StateDirectory=foo -p ConfigurationDirectory=foo --wait /bin/true + +test -d "$HOME"/.local/state/foo +( ! test -L "$HOME"/.local/state/foo) +test -d "$HOME"/.config/foo + +rmdir "$HOME"/.local/state/foo "$HOME"/.config/foo + +systemd-run --user -p StateDirectory=foo -p ConfigurationDirectory=foo --wait /bin/true + +test -d "$HOME"/.local/state/foo +( ! test -L "$HOME"/.local/state/foo) +test -d "$HOME"/.config/foo + +rmdir "$HOME"/.local/state/foo "$HOME"/.config/foo + +# Now trigger an update scenario by creating a config dir first +systemd-run --user -p ConfigurationDirectory=foo --wait /bin/true + +( ! test -d "$HOME"/.local/state/foo) +test -d "$HOME"/.config/foo + +# This will look like an update and result in a symlink +systemd-run --user -p StateDirectory=foo -p ConfigurationDirectory=foo --wait /bin/true + +test -d "$HOME"/.local/state/foo +test -L "$HOME"/.local/state/foo +test -d "$HOME"/.config/foo + +test "$(readlink "$HOME"/.local/state/foo)" = ../../.config/foo + +# Check that this will work safely a second time +systemd-run --user -p StateDirectory=foo -p ConfigurationDirectory=foo --wait /bin/true + +rm "$HOME"/.local/state/foo +rmdir "$HOME"/.config/foo