Merge pull request #28168 from poettering/xdg-state-home

properly support XDG_STATE_HOME
This commit is contained in:
Lennart Poettering
2023-06-28 22:30:23 +02:00
committed by GitHub
19 changed files with 240 additions and 95 deletions

View File

@@ -505,69 +505,64 @@
<varlistentry>
<term><filename>~/.cache/</filename></term>
<listitem><para>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
<varname>$XDG_CACHE_HOME</varname> set, it should use the
directory specified in it instead of this
<listitem><para>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
<varname>$XDG_CACHE_HOME</varname> set, it should use the directory specified in it instead of this
directory.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>~/.config/</filename></term>
<listitem><para>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 <varname>$XDG_CONFIG_HOME</varname> set, it
should use the directory specified in it instead of this
directory.</para></listitem>
<listitem><para>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 <varname>$XDG_CONFIG_HOME</varname> set, it should use
the directory specified in it instead of this directory.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>~/.local/bin/</filename></term>
<listitem><para>Executables that shall appear in the user's
<varname>$PATH</varname> 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 <filename>~/.local/lib/</filename> 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
<listitem><para>Executables that shall appear in the user's <varname>$PATH</varname> 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 <filename>~/.local/lib/</filename> 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.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>~/.local/lib/</filename></term>
<listitem><para>Static, private vendor data that is compatible
with all architectures.</para></listitem>
<listitem><para>Static, private vendor data that is compatible with all
architectures.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>~/.local/lib/<replaceable>arch-id</replaceable>/</filename></term>
<listitem><para>Location for placing public dynamic libraries.
The architecture identifier to use is defined on <ulink
url="https://wiki.debian.org/Multiarch/Tuples">Multiarch
Architecture Specifiers (Tuples)</ulink>
list.</para></listitem>
<listitem><para>Location for placing public dynamic libraries. The architecture identifier to use is
defined on <ulink url="https://wiki.debian.org/Multiarch/Tuples">Multiarch Architecture Specifiers
(Tuples)</ulink> list.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>~/.local/share/</filename></term>
<listitem><para>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 <varname>$XDG_DATA_HOME</varname> set, it should use the
directory specified in it instead of this
directory.</para></listitem>
<listitem><para>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 <varname>$XDG_DATA_HOME</varname> set, it should use
the directory specified in it instead of this directory.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>~/.local/state/</filename></term>
<listitem><para>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 <varname>$XDG_STATE_HOME</varname> set, it should use the directory
specified in it instead of this directory.</para></listitem>
</varlistentry>
</variablelist>

View File

@@ -55,6 +55,7 @@
<constant>SD_PATH_USER_CONFIGURATION</constant>,
<constant>SD_PATH_USER_RUNTIME</constant>,
<constant>SD_PATH_USER_STATE_PRIVATE</constant>,
<constant>SD_PATH_USER_STATE_CACHE</constant>,
<constant>SD_PATH_USER</constant>,

View 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>
@@ -1447,7 +1447,7 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
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 <varname>BindPaths=</varname> or <varname>TemporaryFileSystem=</varname>
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.</para></listitem>
using the same first parameter, but a different second parameter.</para>
<para>The directories defined by these options are always created under the standard paths used by systemd
(<filename>/var/</filename>, <filename>/run/</filename>, <filename>/etc/</filename>, …). If the service needs
@@ -1483,7 +1483,7 @@ StateDirectory=aaa/bbb ccc</programlisting>
<programlisting>RuntimeDirectory=foo:bar foo:baz</programlisting>
the service manager creates <filename index='false'>/run/foo</filename> (if it does not exist), and
<filename index='false'>/run/bar</filename> plus <filename index='false'>/run/baz</filename> as symlinks to
<filename index='false'>/run/foo</filename>.</para>
<filename index='false'>/run/foo</filename>.</para></listitem>
</varlistentry>
<varlistentry>

View 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>

View File

@@ -736,7 +736,7 @@ d /tmp/foo/bar - - - bmA:1h -</programlisting></para>
<row>
<entry><literal>%L</literal></entry>
<entry>System or user log directory</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_CONFIG_HOME</varname> with <filename index="false">/log</filename> appended, and <filename>/var/log</filename> otherwise.</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_STATE_HOME</varname> with <filename index="false">/log</filename> appended, and <filename>/var/log</filename> otherwise.</entry>
</row>
<xi:include href="standard-specifiers.xml" xpointer="m"/>
<xi:include href="standard-specifiers.xml" xpointer="M"/>
@@ -744,7 +744,7 @@ d /tmp/foo/bar - - - bmA:1h -</programlisting></para>
<row>
<entry><literal>%S</literal></entry>
<entry>System or user state directory</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_CONFIG_HOME</varname>, and <filename>/var/lib</filename> otherwise.</entry>
<entry>In <option>--user</option> mode, this is the same as <varname>$XDG_STATE_HOME</varname>, and <filename>/var/lib</filename> otherwise.</entry>
</row>
<row>
<entry><literal>%t</literal></entry>

View File

@@ -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. */

View File

@@ -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;

View 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 },
};

View 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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -3,6 +3,6 @@
#include <sys/types.h>
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);

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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;

View 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'

View File

@@ -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