Merge pull request #6328 from yuwata/runtime-preserve

core: Allow preserving contents of RuntimeDirectory over process restart
This commit is contained in:
Lennart Poettering 2017-07-17 10:02:19 +02:00 committed by GitHub
commit 7398320f9a
9 changed files with 137 additions and 25 deletions

View file

@ -1654,26 +1654,31 @@
<varlistentry>
<term><varname>RuntimeDirectory=</varname></term>
<listitem><para>Takes a list of directory names. If set, one
or more directories by the specified names will be created
below <filename>/run</filename> (for system services) or below
<varname>$XDG_RUNTIME_DIR</varname> (for user services) when
the unit is started, and removed when the unit is stopped. The
directories will have the access mode specified in
<varname>RuntimeDirectoryMode=</varname>, and will be owned by
the user and group specified in <varname>User=</varname> and
<varname>Group=</varname>. 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 <literal>/</literal>, i.e.
must refer to simple directories to create or remove. This is
particularly useful for unprivileged daemons that cannot
create runtime directories in <filename>/run</filename> 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
<citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
<listitem><para>Takes a whitespace-separated list of directory names. The specified directory names must be
relative, and may not include <literal>.</literal> or <literal>..</literal>. If set, one or more directories
including their parents by the specified names will be created below <filename>/run</filename> (for system
services) or below <varname>$XDG_RUNTIME_DIR</varname> (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
<varname>RuntimeDirectoryPreserve=</varname> is configured to <option>restart</option> or <option>yes</option>.
The lowest subdirectories will have the access mode specified in <varname>RuntimeDirectoryMode=</varname>,
and be owned by the user and group specified in <varname>User=</varname> and <varname>Group=</varname>.
This implies <varname>ReadWritePaths=</varname>, that is, the directories specified
in this option are accessible with the access mode specified in <varname>RuntimeDirectoryMode=</varname>
even if <varname>ProtectSystem=</varname> is set to <option>strict</option>.
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 <filename>/run</filename> 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
<citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
<para>Example: if a system service unit has the following,
<programlisting>RuntimeDirectory=foo/bar baz</programlisting>
the service manager creates <filename>/run/foo</filename> (if it does not exist), <filename>/run/foo/bar</filename>,
and <filename>/run/baz</filename>. The directories <filename>/run/foo/bar</filename> and <filename>/run/baz</filename>
except <filename>/run/foo</filename> are owned by the user and group specified in <varname>User=</varname> and
<varname>Group=</varname>, and removed when the service is stopped.
</para></listitem>
</varlistentry>
<varlistentry>
@ -1682,7 +1687,23 @@
<listitem><para>Specifies the access mode of the directories specified in
<varname>RuntimeDirectory=</varname> as an octal number. Defaults to
<constant>0755</constant>. See "Permissions" in
<citerefentry project='man-pages'><refentrytitle>path_resolution</refentrytitle><manvolnum>7</manvolnum></citerefentry> for a discussion of the meaning of permission bits.
<citerefentry project='man-pages'><refentrytitle>path_resolution</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for a discussion of the meaning of permission bits.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>RuntimeDirectoryPreserve=</varname></term>
<listitem><para>Takes a boolean argument or <option>restart</option>.
If set to <option>no</option> (the default), the directories specified in <varname>RuntimeDirectory=</varname>
are always removed when the service stops. If set to <option>restart</option> the directories are preserved
when the service is both automatically and manually restarted. Here, the automatic restart means the operation
specified in <varname>Restart=</varname>, and manual restart means the one triggered by
<command>systemctl restart foo.service</command>. If set to <option>yes</option>, then the directories are not
removed when the service is stopped. Note that since the runtime directory <filename>/run</filename> is a mount
point of <literal>tmpfs</literal>, then for system services the directories specified in
<varname>RuntimeDirectory=</varname> are removed when the system is rebooted.
</para></listitem>
</varlistentry>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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