diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 85990cc8aa..8356c1baae 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -1654,26 +1654,31 @@
RuntimeDirectory=
- Takes a list of directory names. If set, one
- or more directories by the specified names will be created
- below /run (for system services) or below
- $XDG_RUNTIME_DIR (for user services) when
- the unit is started, and removed when the unit is stopped. The
- directories will have the access mode specified in
- RuntimeDirectoryMode=, and will be owned by
- the user and group specified in User= and
- Group=. Use this to manage one or more
- runtime directories of the unit and bind their lifetime to the
- daemon runtime. The specified directory names must be
- relative, and may not include a /, i.e.
- must refer to simple directories to create or remove. This is
- particularly useful for unprivileged daemons that cannot
- create runtime directories in /run due to
- lack of privileges, and to make sure the runtime directory is
- cleaned up automatically after use. For runtime directories
- that require more complex or different configuration or
- lifetime guarantees, please consider using
- tmpfiles.d5.
+ Takes a whitespace-separated list of directory names. The specified directory names must be
+ relative, and may not include . or ... If set, one or more directories
+ including their parents by the specified names will be created below /run (for system
+ services) or below $XDG_RUNTIME_DIR (for user services) when the unit is started. The
+ lowest subdirectories are removed when the unit is stopped. It is possible to preserve the directories if
+ RuntimeDirectoryPreserve= is configured to or .
+ The lowest subdirectories will have the access mode specified in RuntimeDirectoryMode=,
+ and be owned by the user and group specified in User= and Group=.
+ This implies ReadWritePaths=, that is, the directories specified
+ in this option are accessible with the access mode specified in RuntimeDirectoryMode=
+ even if ProtectSystem= is set to .
+ Use this to manage one or more runtime directories of the unit and bind their
+ lifetime to the daemon runtime. This is particularly useful for unprivileged daemons that cannot create
+ runtime directories in /run due to lack of privileges, and to make sure the runtime
+ directory is cleaned up automatically after use. For runtime directories that require more complex or
+ different configuration or lifetime guarantees, please consider using
+ tmpfiles.d5.
+
+ Example: if a system service unit has the following,
+ RuntimeDirectory=foo/bar baz
+ the service manager creates /run/foo (if it does not exist), /run/foo/bar,
+ and /run/baz. The directories /run/foo/bar and /run/baz
+ except /run/foo are owned by the user and group specified in User= and
+ Group=, and removed when the service is stopped.
+
@@ -1682,7 +1687,23 @@
Specifies the access mode of the directories specified in
RuntimeDirectory= as an octal number. Defaults to
0755. See "Permissions" in
- path_resolution7 for a discussion of the meaning of permission bits.
+ path_resolution7
+ for a discussion of the meaning of permission bits.
+
+
+
+
+ RuntimeDirectoryPreserve=
+
+ Takes a boolean argument or .
+ If set to (the default), the directories specified in RuntimeDirectory=
+ are always removed when the service stops. If set to the directories are preserved
+ when the service is both automatically and manually restarted. Here, the automatic restart means the operation
+ specified in Restart=, and manual restart means the one triggered by
+ systemctl restart foo.service. If set to , then the directories are not
+ removed when the service is stopped. Note that since the runtime directory /run is a mount
+ point of tmpfs, then for system services the directories specified in
+ RuntimeDirectory= are removed when the system is rebooted.
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index c041a7d94c..0958c238f0 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -55,6 +55,8 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInp
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode);
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
+
static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome);
static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem);
@@ -850,6 +852,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("Personality", "s", property_get_personality, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, runtime_directory_mode), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, runtime_directory), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1678,6 +1681,41 @@ int bus_exec_context_set_transient_property(
return 1;
+ } else if (streq(name, "RuntimeDirectoryPreserve")) {
+ const char *s;
+ ExecPreserveMode m;
+
+ r = sd_bus_message_read(message, "s", &s);
+ if (r < 0)
+ return r;
+
+ m = exec_preserve_mode_from_string(s);
+ if (m < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid preserve mode");
+
+ if (mode != UNIT_CHECK) {
+ c->runtime_directory_preserve_mode = m;
+
+ unit_write_drop_in_private_format(u, mode, name, "RuntimeDirectoryPreserve=%s", exec_preserve_mode_to_string(m));
+ }
+
+ return 1;
+
+ } else if (streq(name, "RuntimeDirectoryMode")) {
+ mode_t m;
+
+ r = sd_bus_message_read(message, "u", &m);
+ if (r < 0)
+ return r;
+
+ if (mode != UNIT_CHECK) {
+ c->runtime_directory_mode = m;
+
+ unit_write_drop_in_private_format(u, mode, name, "RuntimeDirectoryMode=%040o", m);
+ }
+
+ return 1;
+
} else if (streq(name, "RuntimeDirectory")) {
_cleanup_strv_free_ char **l = NULL;
char **p;
diff --git a/src/core/execute.c b/src/core/execute.c
index d72e5bf08c..fd769b2973 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -1858,6 +1858,10 @@ static int setup_runtime_directory(
if (!p)
return -ENOMEM;
+ r = mkdir_parents_label(p, 0755);
+ if (r < 0)
+ return r;
+
r = mkdir_p_label(p, context->runtime_directory_mode);
if (r < 0)
return r;
@@ -3428,6 +3432,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode);
+ fprintf(f, "%sRuntimeDirectoryPreserve: %s\n", prefix, exec_preserve_mode_to_string(c->runtime_directory_preserve_mode));
+
STRV_FOREACH(d, c->runtime_directory)
fprintf(f, "%sRuntimeDirectory: %s\n", prefix, *d);
@@ -4160,3 +4166,11 @@ static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode);
+
+static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = {
+ [EXEC_PRESERVE_NO] = "no",
+ [EXEC_PRESERVE_YES] = "yes",
+ [EXEC_PRESERVE_RESTART] = "restart",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES);
diff --git a/src/core/execute.h b/src/core/execute.h
index 9f07aa4aef..93713e48cc 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -72,6 +72,14 @@ typedef enum ExecOutput {
_EXEC_OUTPUT_INVALID = -1
} ExecOutput;
+typedef enum ExecPreserveMode {
+ EXEC_PRESERVE_NO,
+ EXEC_PRESERVE_YES,
+ EXEC_PRESERVE_RESTART,
+ _EXEC_PRESERVE_MODE_MAX,
+ _EXEC_PRESERVE_MODE_INVALID = -1
+} ExecPreserveMode;
+
struct ExecStatus {
dual_timestamp start_timestamp;
dual_timestamp exit_timestamp;
@@ -211,6 +219,7 @@ struct ExecContext {
char **runtime_directory;
mode_t runtime_directory_mode;
+ ExecPreserveMode runtime_directory_preserve_mode;
bool memory_deny_write_execute;
bool restrict_realtime;
@@ -330,3 +339,6 @@ ExecInput exec_input_from_string(const char *s) _pure_;
const char* exec_utmp_mode_to_string(ExecUtmpMode i) _const_;
ExecUtmpMode exec_utmp_mode_from_string(const char *s) _pure_;
+
+const char* exec_preserve_mode_to_string(ExecPreserveMode i) _const_;
+ExecPreserveMode exec_preserve_mode_from_string(const char *s) _pure_;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 5b5a86250e..f67494f059 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -105,6 +105,7 @@ $1.MountFlags, config_parse_exec_mount_flags, 0,
$1.MountAPIVFS, config_parse_bool, 0, offsetof($1, exec_context.mount_apivfs)
$1.Personality, config_parse_personality, 0, offsetof($1, exec_context.personality)
$1.RuntimeDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.runtime_directory_mode)
+$1.RuntimeDirectoryPreserve, config_parse_runtime_preserve_mode, 0, offsetof($1, exec_context.runtime_directory_preserve_mode)
$1.RuntimeDirectory, config_parse_runtime_directory, 0, offsetof($1, exec_context.runtime_directory)
m4_ifdef(`HAVE_PAM',
`$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name)',
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 9d5c39b3dd..00b7f69cd8 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -3701,6 +3701,8 @@ int config_parse_job_mode_isolate(
return 0;
}
+DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse runtime directory preserve mode");
+
int config_parse_runtime_directory(
const char *unit,
const char *filename,
@@ -3750,7 +3752,7 @@ int config_parse_runtime_directory(
continue;
}
- if (!filename_is_valid(k)) {
+ if (!path_is_safe(k) || path_is_absolute(k)) {
log_syntax(unit, LOG_ERR, filename, line, 0,
"Runtime directory is not valid, ignoring assignment: %s", rvalue);
continue;
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index fc27a07955..4174f19483 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -102,6 +102,7 @@ int config_parse_exec_selinux_context(const char *unit, const char *filename, un
int config_parse_exec_apparmor_profile(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_smack_process_label(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_address_families(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_runtime_preserve_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_runtime_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_set_status(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_namespace_path_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/core/service.c b/src/core/service.c
index 4c577db8d7..601ca2eb20 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1480,6 +1480,18 @@ static bool service_shall_restart(Service *s) {
}
}
+static bool service_will_restart(Service *s) {
+ assert(s);
+
+ if (s->state == SERVICE_AUTO_RESTART)
+ return true;
+ if (!UNIT(s)->job)
+ return false;
+ if (UNIT(s)->job->type == JOB_START)
+ return true;
+ return false;
+}
+
static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
int r;
assert(s);
@@ -1510,8 +1522,10 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
exec_runtime_destroy(s->exec_runtime);
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
- /* Also, remove the runtime directory */
- exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
+ if (s->exec_context.runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
+ (s->exec_context.runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !service_will_restart(s)))
+ /* Also, remove the runtime directory */
+ exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
/* Get rid of the IPC bits of the user */
unit_unref_uid_gid(UNIT(s), true);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 5cbe663fa8..71451758a6 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -267,7 +267,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
"Description", "Slice", "Type", "WorkingDirectory",
"RootDirectory", "SyslogIdentifier", "ProtectSystem",
"ProtectHome", "SELinuxContext", "Restart", "RootImage",
- "NotifyAccess"))
+ "NotifyAccess", "RuntimeDirectoryPreserve"))
r = sd_bus_message_append(m, "v", "s", eq);
else if (streq(field, "SyslogLevel")) {
@@ -548,6 +548,15 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_close_container(m);
+ } else if (streq(field, "RuntimeDirectoryMode")) {
+ mode_t mode;
+
+ r = parse_mode(eq, &mode);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s value %s", field, eq);
+
+ r = sd_bus_message_append(m, "v", "u", mode);
+
} else if (streq(field, "RuntimeDirectory")) {
const char *p;