Merge pull request #27135 from poettering/pin-fdstore
Allow the per-service fdstore to be "pinned", i.e. preserved as long as the unit info remains in memory
This commit is contained in:
@@ -1479,8 +1479,6 @@ Features:
|
||||
* maybe rework get_user_creds() to query the user database if $SHELL is used
|
||||
for root, but only then.
|
||||
|
||||
* be stricter with fds we receive for the fdstore: close them asynchronously
|
||||
|
||||
* calenderspec: add support for week numbers and day numbers within a
|
||||
year. This would allow us to define "bi-weekly" triggers safely.
|
||||
|
||||
|
||||
@@ -2619,6 +2619,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
readonly u FileDescriptorStoreMax = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly u NFileDescriptorStore = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
|
||||
readonly s FileDescriptorStorePreserve = '...';
|
||||
readonly s StatusText = '...';
|
||||
readonly i StatusErrno = ...;
|
||||
readonly s Result = '...';
|
||||
@@ -3244,6 +3246,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<!--property NFileDescriptorStore is not documented!-->
|
||||
|
||||
<!--property FileDescriptorStorePreserve is not documented!-->
|
||||
|
||||
<!--property StatusErrno is not documented!-->
|
||||
|
||||
<!--property ReloadResult is not documented!-->
|
||||
@@ -3830,6 +3834,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="NFileDescriptorStore"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="FileDescriptorStorePreserve"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StatusText"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="StatusErrno"/>
|
||||
|
||||
+18
-10
@@ -538,12 +538,16 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
<varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>,
|
||||
<varname>LogsDirectory=</varname> and <varname>RuntimeDirectory=</varname>, see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details. For timer units this may be used to clear out the persistent timestamp data if
|
||||
for details. It may also be used to clear the file decriptor store as enabled via
|
||||
<varname>FileDescriptorStoreMax=</varname>, see
|
||||
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details. For timer units this may be used to clear out the persistent timestamp data if
|
||||
<varname>Persistent=</varname> is used and <option>--what=state</option> is selected, see
|
||||
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This
|
||||
command only applies to units that use either of these settings. If <option>--what=</option> is
|
||||
not specified, both the cache and runtime data are removed (as these two types of data are
|
||||
generally redundant and reproducible on the next invocation of the unit).</para>
|
||||
not specified, the cache and runtime data as well as the file descriptor store are removed (as
|
||||
these three types of resources are generally redundant and reproducible on the next invocation of
|
||||
the unit). Note that the specified units must be stopped to invoke this operation.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
@@ -2193,13 +2197,17 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
|
||||
|
||||
<listitem>
|
||||
<para>Select what type of per-unit resources to remove when the <command>clean</command> command is
|
||||
invoked, see below. Takes one of <constant>configuration</constant>, <constant>state</constant>,
|
||||
<constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant> to select the
|
||||
type of resource. This option may be specified more than once, in which case all specified resource
|
||||
types are removed. Also accepts the special value <constant>all</constant> as a shortcut for
|
||||
specifying all five resource types. If this option is not specified defaults to the combination of
|
||||
<constant>cache</constant> and <constant>runtime</constant>, i.e. the two kinds of resources that
|
||||
are generally considered to be redundant and can be reconstructed on next invocation.</para>
|
||||
invoked, see above. Takes one of <constant>configuration</constant>, <constant>state</constant>,
|
||||
<constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant>,
|
||||
<constant>fdstore</constant> to select the type of resource. This option may be specified more than
|
||||
once, in which case all specified resource types are removed. Also accepts the special value
|
||||
<constant>all</constant> as a shortcut for specifying all six resource types. If this option is not
|
||||
specified defaults to the combination of <constant>cache</constant>, <constant>runtime</constant>
|
||||
and <constant>fdstore</constant>, i.e. the three kinds of resources that are generally considered
|
||||
to be redundant and can be reconstructed on next invocation. Note that the explicit removal of the
|
||||
<constant>fdstore</constant> resource type is only useful if the
|
||||
<varname>FileDescriptorStorePreserve=</varname> option is enabled, since the file descriptor store
|
||||
is otherwise cleaned automatically when the unit is stopped.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
||||
+19
-2
@@ -1066,7 +1066,7 @@
|
||||
<literal>FDSTORE=1</literal> messages. This is useful for implementing services that can restart
|
||||
after an explicit request or a crash without losing state. Any open sockets and other file
|
||||
descriptors which should not be closed during the restart may be stored this way. Application state
|
||||
can either be serialized to a file in <filename>/run/</filename>, or better, stored in a
|
||||
can either be serialized to a file in <varname>RuntimeDirectory=</varname>, or stored in a
|
||||
<citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
memory file descriptor. Defaults to 0, i.e. no file descriptors may be stored in the service
|
||||
manager. All file descriptors passed to the service manager from a specific service are passed back
|
||||
@@ -1075,7 +1075,8 @@
|
||||
details about the precise protocol used and the order in which the file descriptors are passed). Any
|
||||
file descriptors passed to the service manager are automatically closed when
|
||||
<constant>POLLHUP</constant> or <constant>POLLERR</constant> is seen on them, or when the service is
|
||||
fully stopped and no job is queued or being executed for it. If this option is used,
|
||||
fully stopped and no job is queued or being executed for it (the latter can be tweaked with
|
||||
<varname>FileDescriptorStorePreserve=</varname>, see below). If this option is used,
|
||||
<varname>NotifyAccess=</varname> (see above) should be set to open access to the notification socket
|
||||
provided by systemd. If <varname>NotifyAccess=</varname> is not set, it will be implicitly set to
|
||||
<option>main</option>.</para>
|
||||
@@ -1097,6 +1098,22 @@
|
||||
details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>FileDescriptorStorePreserve=</varname></term>
|
||||
<listitem><para>Takes one of <constant>no</constant>, <constant>yes</constant>,
|
||||
<constant>restart</constant> and controls when to release the service's file descriptor store
|
||||
(i.e. when to close the contained file descriptors, if any). If set to <constant>no</constant> the
|
||||
file descriptor store is automatically released when the service is stopped; if
|
||||
<constant>restart</constant> (the default) it is kept around as long as the unit is neither inactive
|
||||
nor failed, or a job is queued for the service, or the service is expected to be restarted. If
|
||||
<constant>yes</constant> the file descriptor store is kept around until the unit is removed from
|
||||
memory (i.e. is not referenced anymore and inactive). The latter is useful to keep entries in the
|
||||
file descriptor store pinned until the service manage exits.</para>
|
||||
|
||||
<para>Use <command>systemctl clean --what=fdstore …</command> to release the file descriptor store
|
||||
explicitly.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>USBFunctionDescriptors=</varname></term>
|
||||
<listitem><para>Configure the location of a file containing
|
||||
|
||||
@@ -201,6 +201,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
|
||||
[SERVICE_FAILED] = "failed",
|
||||
[SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart",
|
||||
[SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
|
||||
[SERVICE_DEAD_RESOURCES_PINNED] = "dead-resources-pinned",
|
||||
[SERVICE_AUTO_RESTART] = "auto-restart",
|
||||
[SERVICE_CLEANING] = "cleaning",
|
||||
};
|
||||
|
||||
@@ -146,6 +146,7 @@ typedef enum ServiceState {
|
||||
SERVICE_FAILED,
|
||||
SERVICE_DEAD_BEFORE_AUTO_RESTART,
|
||||
SERVICE_FAILED_BEFORE_AUTO_RESTART,
|
||||
SERVICE_DEAD_RESOURCES_PINNED, /* Like SERVICE_DEAD, but with pinned resources */
|
||||
SERVICE_AUTO_RESTART,
|
||||
SERVICE_CLEANING,
|
||||
_SERVICE_STATE_MAX,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
|
||||
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput);
|
||||
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);
|
||||
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
|
||||
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode);
|
||||
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_proc, protect_proc, ProtectProc);
|
||||
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_proc_subset, proc_subset, ProcSubset);
|
||||
@@ -1318,7 +1318,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), 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("RuntimeDirectoryPreserve", "s", bus_property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
@@ -1695,7 +1695,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_home, ProtectHome, protect_home_fr
|
||||
static BUS_DEFINE_SET_TRANSIENT_PARSE(keyring_mode, ExecKeyringMode, exec_keyring_mode_from_string);
|
||||
static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_proc, ProtectProc, protect_proc_from_string);
|
||||
static BUS_DEFINE_SET_TRANSIENT_PARSE(proc_subset, ProcSubset, proc_subset_from_string);
|
||||
static BUS_DEFINE_SET_TRANSIENT_PARSE(preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string);
|
||||
BUS_DEFINE_SET_TRANSIENT_PARSE(exec_preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string);
|
||||
static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality);
|
||||
static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check);
|
||||
static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint64_t, "%" PRIu64, capability_set_to_string);
|
||||
@@ -2047,7 +2047,7 @@ int bus_exec_context_set_transient_property(
|
||||
return bus_set_transient_proc_subset(u, name, &c->proc_subset, message, flags, error);
|
||||
|
||||
if (streq(name, "RuntimeDirectoryPreserve"))
|
||||
return bus_set_transient_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error);
|
||||
return bus_set_transient_exec_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error);
|
||||
|
||||
if (streq(name, "UMask"))
|
||||
return bus_set_transient_mode_t(u, name, &c->umask, message, flags, error);
|
||||
|
||||
@@ -28,6 +28,8 @@ int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *inte
|
||||
int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
|
||||
int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
|
||||
int bus_property_get_exec_ex_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
|
||||
int bus_property_get_exec_preserve_mode(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
|
||||
|
||||
int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
|
||||
int bus_set_transient_exec_command(Unit *u, const char *name, ExecCommand **exec_command, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
|
||||
int bus_set_transient_exec_preserve_mode(Unit *u, const char *name, ExecPreserveMode *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
|
||||
|
||||
@@ -349,6 +349,7 @@ const sd_bus_vtable bus_service_vtable[] = {
|
||||
SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("NFileDescriptorStore", "u", property_get_size_as_uint32, offsetof(Service, n_fd_store), 0),
|
||||
SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0),
|
||||
SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
@@ -599,6 +600,9 @@ static int bus_service_set_transient_property(
|
||||
if (streq(name, "FileDescriptorStoreMax"))
|
||||
return bus_set_transient_unsigned(u, name, &s->n_fd_store_max, message, flags, error);
|
||||
|
||||
if (streq(name, "FileDescriptorStorePreserve"))
|
||||
return bus_set_transient_exec_preserve_mode(u, name, &s->fd_store_preserve_mode, message, flags, error);
|
||||
|
||||
if (streq(name, "NotifyAccess"))
|
||||
return bus_set_transient_notify_access(u, name, &s->notify_access, message, flags, error);
|
||||
|
||||
|
||||
+11
-10
@@ -90,6 +90,12 @@ static int property_get_can_clean(
|
||||
return r;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) {
|
||||
r = sd_bus_message_append(reply, "s", "fdstore");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
@@ -696,6 +702,7 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
ExecCleanMask m;
|
||||
const char *i;
|
||||
|
||||
r = sd_bus_message_read(message, "s", &i);
|
||||
@@ -704,17 +711,11 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
if (streq(i, "all"))
|
||||
mask |= EXEC_CLEAN_ALL;
|
||||
else {
|
||||
ExecDirectoryType t;
|
||||
m = exec_clean_mask_from_string(i);
|
||||
if (m < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
|
||||
|
||||
t = exec_resource_type_from_string(i);
|
||||
if (t < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
|
||||
|
||||
mask |= 1U << t;
|
||||
}
|
||||
mask |= m;
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(message);
|
||||
|
||||
@@ -7582,6 +7582,23 @@ void exec_directory_sort(ExecDirectory *d) {
|
||||
}
|
||||
}
|
||||
|
||||
ExecCleanMask exec_clean_mask_from_string(const char *s) {
|
||||
ExecDirectoryType t;
|
||||
|
||||
assert(s);
|
||||
|
||||
if (streq(s, "all"))
|
||||
return EXEC_CLEAN_ALL;
|
||||
if (streq(s, "fdstore"))
|
||||
return EXEC_CLEAN_FDSTORE;
|
||||
|
||||
t = exec_resource_type_from_string(s);
|
||||
if (t < 0)
|
||||
return (ExecCleanMask) t;
|
||||
|
||||
return 1U << t;
|
||||
}
|
||||
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
|
||||
|
||||
|
||||
+4
-1
@@ -161,8 +161,9 @@ typedef enum ExecCleanMask {
|
||||
EXEC_CLEAN_CACHE = 1U << EXEC_DIRECTORY_CACHE,
|
||||
EXEC_CLEAN_LOGS = 1U << EXEC_DIRECTORY_LOGS,
|
||||
EXEC_CLEAN_CONFIGURATION = 1U << EXEC_DIRECTORY_CONFIGURATION,
|
||||
EXEC_CLEAN_FDSTORE = 1U << _EXEC_DIRECTORY_TYPE_MAX,
|
||||
EXEC_CLEAN_NONE = 0,
|
||||
EXEC_CLEAN_ALL = (1U << _EXEC_DIRECTORY_TYPE_MAX) - 1,
|
||||
EXEC_CLEAN_ALL = (1U << (_EXEC_DIRECTORY_TYPE_MAX+1)) - 1,
|
||||
_EXEC_CLEAN_MASK_INVALID = -EINVAL,
|
||||
} ExecCleanMask;
|
||||
|
||||
@@ -520,6 +521,8 @@ void exec_directory_done(ExecDirectory *d);
|
||||
int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink);
|
||||
void exec_directory_sort(ExecDirectory *d);
|
||||
|
||||
ExecCleanMask exec_clean_mask_from_string(const char *s);
|
||||
|
||||
extern const struct hash_ops exec_set_credential_hash_ops;
|
||||
extern const struct hash_ops exec_load_credential_hash_ops;
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
{{type}}.MountFlags, config_parse_exec_mount_propagation_flag, 0, offsetof({{type}}, exec_context.mount_propagation_flag)
|
||||
{{type}}.MountAPIVFS, config_parse_exec_mount_apivfs, 0, offsetof({{type}}, exec_context)
|
||||
{{type}}.Personality, config_parse_personality, 0, offsetof({{type}}, exec_context.personality)
|
||||
{{type}}.RuntimeDirectoryPreserve, config_parse_runtime_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
|
||||
{{type}}.RuntimeDirectoryPreserve, config_parse_exec_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
|
||||
{{type}}.RuntimeDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode)
|
||||
{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME])
|
||||
{{type}}.StateDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode)
|
||||
@@ -433,6 +433,7 @@ Service.SysVStartPriority, config_parse_warn_compat,
|
||||
Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking)
|
||||
Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name)
|
||||
Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max)
|
||||
Service.FileDescriptorStorePreserve, config_parse_exec_preserve_mode, 0, offsetof(Service, fd_store_preserve_mode)
|
||||
Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access)
|
||||
Service.Sockets, config_parse_service_sockets, 0, 0
|
||||
Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0
|
||||
|
||||
@@ -138,7 +138,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to pa
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome, "Failed to parse protect home value");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem, "Failed to parse protect system value");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse runtime directory preserve mode");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse resource preserve mode");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_exit_type, service_exit_type, ServiceExitType, "Failed to parse service exit type");
|
||||
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier");
|
||||
|
||||
@@ -101,7 +101,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_selinux_context);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_apparmor_profile);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
|
||||
|
||||
@@ -1250,6 +1250,26 @@ static unsigned manager_dispatch_cleanup_queue(Manager *m) {
|
||||
return n;
|
||||
}
|
||||
|
||||
static unsigned manager_dispatch_release_resources_queue(Manager *m) {
|
||||
unsigned n = 0;
|
||||
Unit *u;
|
||||
|
||||
assert(m);
|
||||
|
||||
while ((u = m->release_resources_queue)) {
|
||||
assert(u->in_release_resources_queue);
|
||||
|
||||
LIST_REMOVE(release_resources_queue, m->release_resources_queue, u);
|
||||
u->in_release_resources_queue = false;
|
||||
|
||||
n++;
|
||||
|
||||
unit_release_resources(u);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
enum {
|
||||
GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */
|
||||
GC_OFFSET_UNSURE, /* No clue */
|
||||
@@ -3158,6 +3178,9 @@ int manager_loop(Manager *m) {
|
||||
if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
|
||||
continue;
|
||||
|
||||
if (manager_dispatch_release_resources_queue(m) > 0)
|
||||
continue;
|
||||
|
||||
if (manager_dispatch_dbus_queue(m) > 0)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -195,6 +195,9 @@ struct Manager {
|
||||
/* Units that have BindsTo= another unit, and might need to be shutdown because the bound unit is not active. */
|
||||
LIST_HEAD(Unit, stop_when_bound_queue);
|
||||
|
||||
/* Units that have resources open, and where it might be good to check if they can be released now */
|
||||
LIST_HEAD(Unit, release_resources_queue);
|
||||
|
||||
sd_event *event;
|
||||
|
||||
/* This maps PIDs we care about to units that are interested in. We allow multiple units to be interested in
|
||||
|
||||
+119
-48
@@ -69,6 +69,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
|
||||
[SERVICE_FAILED] = UNIT_FAILED,
|
||||
[SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
|
||||
[SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
|
||||
[SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE,
|
||||
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
|
||||
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
|
||||
};
|
||||
@@ -97,6 +98,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
|
||||
[SERVICE_FAILED] = UNIT_FAILED,
|
||||
[SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
|
||||
[SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
|
||||
[SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE,
|
||||
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
|
||||
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
|
||||
};
|
||||
@@ -139,6 +141,8 @@ static void service_init(Unit *u) {
|
||||
s->oom_policy = _OOM_POLICY_INVALID;
|
||||
s->reload_begin_usec = USEC_INFINITY;
|
||||
s->reload_signal = SIGHUP;
|
||||
|
||||
s->fd_store_preserve_mode = EXEC_PRESERVE_RESTART;
|
||||
}
|
||||
|
||||
static void service_unwatch_control_pid(Service *s) {
|
||||
@@ -196,9 +200,14 @@ static int service_set_main_pid(Service *s, pid_t pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void service_close_socket_fd(Service *s) {
|
||||
void service_release_socket_fd(Service *s) {
|
||||
assert(s);
|
||||
|
||||
if (s->socket_fd < 0 && !UNIT_ISSET(s->accept_socket) && !s->socket_peer)
|
||||
return;
|
||||
|
||||
log_unit_debug(UNIT(s), "Closing connection socket.");
|
||||
|
||||
/* Undo the effect of service_set_socket_fd(). */
|
||||
|
||||
s->socket_fd = asynchronous_close(s->socket_fd);
|
||||
@@ -380,41 +389,36 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
|
||||
sd_event_source_disable_unref(fs->event_source);
|
||||
|
||||
free(fs->fdname);
|
||||
safe_close(fs->fd);
|
||||
asynchronous_close(fs->fd);
|
||||
free(fs);
|
||||
}
|
||||
|
||||
static void service_release_fd_store(Service *s) {
|
||||
assert(s);
|
||||
|
||||
if (!s->fd_store)
|
||||
return;
|
||||
|
||||
log_unit_debug(UNIT(s), "Releasing all stored fds");
|
||||
|
||||
while (s->fd_store)
|
||||
service_fd_store_unlink(s->fd_store);
|
||||
|
||||
assert(s->n_fd_store == 0);
|
||||
}
|
||||
|
||||
static void service_release_resources(Unit *u) {
|
||||
Service *s = SERVICE(u);
|
||||
|
||||
static void service_release_stdio_fd(Service *s) {
|
||||
assert(s);
|
||||
|
||||
/* Don't release resources if this is a transitionary failed/dead state */
|
||||
if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
|
||||
if (s->stdin_fd < 0 && s->stdout_fd < 0 && s->stdout_fd < 0)
|
||||
return;
|
||||
|
||||
if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
|
||||
return;
|
||||
log_unit_debug(UNIT(s), "Releasing stdin/stdout/stderr file descriptors.");
|
||||
|
||||
log_unit_debug(u, "Releasing resources.");
|
||||
|
||||
s->stdin_fd = safe_close(s->stdin_fd);
|
||||
s->stdout_fd = safe_close(s->stdout_fd);
|
||||
s->stderr_fd = safe_close(s->stderr_fd);
|
||||
|
||||
service_release_fd_store(s);
|
||||
s->stdin_fd = asynchronous_close(s->stdin_fd);
|
||||
s->stdout_fd = asynchronous_close(s->stdout_fd);
|
||||
s->stderr_fd = asynchronous_close(s->stderr_fd);
|
||||
}
|
||||
|
||||
static void service_done(Unit *u) {
|
||||
Service *s = SERVICE(u);
|
||||
|
||||
@@ -450,8 +454,6 @@ static void service_done(Unit *u) {
|
||||
s->usb_function_descriptors = mfree(s->usb_function_descriptors);
|
||||
s->usb_function_strings = mfree(s->usb_function_strings);
|
||||
|
||||
service_close_socket_fd(s);
|
||||
|
||||
service_stop_watchdog(s);
|
||||
|
||||
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
|
||||
@@ -459,7 +461,9 @@ static void service_done(Unit *u) {
|
||||
|
||||
s->bus_name_pid_lookup_slot = sd_bus_slot_unref(s->bus_name_pid_lookup_slot);
|
||||
|
||||
service_release_resources(u);
|
||||
service_release_socket_fd(s);
|
||||
service_release_stdio_fd(s);
|
||||
service_release_fd_store(s);
|
||||
}
|
||||
|
||||
static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
|
||||
@@ -477,6 +481,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us
|
||||
}
|
||||
|
||||
static int service_add_fd_store(Service *s, int fd, const char *name, bool do_poll) {
|
||||
struct stat st;
|
||||
ServiceFDStore *fs;
|
||||
int r;
|
||||
|
||||
@@ -485,17 +490,23 @@ static int service_add_fd_store(Service *s, int fd, const char *name, bool do_po
|
||||
assert(s);
|
||||
assert(fd >= 0);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
log_unit_debug(UNIT(s), "Trying to stash fd for dev=" DEVNUM_FORMAT_STR "/inode=%" PRIu64, DEVNUM_FORMAT_VAL(st.st_dev), (uint64_t) st.st_ino);
|
||||
|
||||
if (s->n_fd_store >= s->n_fd_store_max)
|
||||
return -EXFULL; /* Our store is full.
|
||||
* Use this errno rather than E[NM]FILE to distinguish from
|
||||
* the case where systemd itself hits the file limit. */
|
||||
/* Our store is full. Use this errno rather than E[NM]FILE to distinguish from the case
|
||||
* where systemd itself hits the file limit. */
|
||||
return log_unit_debug_errno(UNIT(s), SYNTHETIC_ERRNO(EXFULL), "Hit fd store limit.");
|
||||
|
||||
LIST_FOREACH(fd_store, i, s->fd_store) {
|
||||
r = same_fd(i->fd, fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
safe_close(fd);
|
||||
log_unit_debug(UNIT(s), "Suppressing duplicate fd in fd store.");
|
||||
asynchronous_close(fd);
|
||||
return 0; /* fd already included */
|
||||
}
|
||||
}
|
||||
@@ -538,7 +549,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo
|
||||
assert(s);
|
||||
|
||||
while (fdset_size(fds) > 0) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
_cleanup_(asynchronous_closep) int fd = -EBADF;
|
||||
|
||||
fd = fdset_steal_first(fds);
|
||||
if (fd < 0)
|
||||
@@ -553,7 +564,8 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo
|
||||
return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m");
|
||||
if (r > 0)
|
||||
log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fd, strna(name));
|
||||
fd = -EBADF;
|
||||
|
||||
TAKE_FD(fd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1030,8 +1042,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
|
||||
if (s->n_fd_store_max > 0)
|
||||
fprintf(f,
|
||||
"%sFile Descriptor Store Max: %u\n"
|
||||
"%sFile Descriptor Store Pin: %s\n"
|
||||
"%sFile Descriptor Store Current: %zu\n",
|
||||
prefix, s->n_fd_store_max,
|
||||
prefix, exec_preserve_mode_to_string(s->fd_store_preserve_mode),
|
||||
prefix, s->n_fd_store);
|
||||
|
||||
service_dump_fdstore(s, f, prefix);
|
||||
@@ -1243,20 +1257,12 @@ static void service_set_state(Service *s, ServiceState state) {
|
||||
|
||||
if (IN_SET(state,
|
||||
SERVICE_DEAD, SERVICE_FAILED,
|
||||
SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART)) {
|
||||
SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
|
||||
SERVICE_DEAD_RESOURCES_PINNED)) {
|
||||
unit_unwatch_all_pids(UNIT(s));
|
||||
unit_dequeue_rewatch_pids(UNIT(s));
|
||||
}
|
||||
|
||||
if (!IN_SET(state,
|
||||
SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
|
||||
SERVICE_RUNNING,
|
||||
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
|
||||
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
|
||||
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) &&
|
||||
!(state == SERVICE_DEAD && UNIT(s)->job))
|
||||
service_close_socket_fd(s);
|
||||
|
||||
if (state != SERVICE_START)
|
||||
s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
|
||||
|
||||
@@ -1359,7 +1365,8 @@ static int service_coldplug(Unit *u) {
|
||||
if (!IN_SET(s->deserialized_state,
|
||||
SERVICE_DEAD, SERVICE_FAILED,
|
||||
SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
|
||||
SERVICE_CLEANING)) {
|
||||
SERVICE_CLEANING,
|
||||
SERVICE_DEAD_RESOURCES_PINNED)) {
|
||||
(void) unit_enqueue_rewatch_pids(u);
|
||||
(void) unit_setup_exec_runtime(u);
|
||||
}
|
||||
@@ -1947,6 +1954,12 @@ static bool service_will_restart(Unit *u) {
|
||||
return unit_will_restart_default(u);
|
||||
}
|
||||
|
||||
static ServiceState service_determine_dead_state(Service *s) {
|
||||
assert(s);
|
||||
|
||||
return s->fd_store && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD;
|
||||
}
|
||||
|
||||
static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
|
||||
ServiceState end_state, restart_state;
|
||||
int r;
|
||||
@@ -1963,11 +1976,11 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
|
||||
|
||||
if (s->result == SERVICE_SUCCESS) {
|
||||
unit_log_success(UNIT(s));
|
||||
end_state = SERVICE_DEAD;
|
||||
end_state = service_determine_dead_state(s);
|
||||
restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
|
||||
} else if (s->result == SERVICE_SKIP_CONDITION) {
|
||||
unit_log_skip(UNIT(s), service_result_to_string(s->result));
|
||||
end_state = SERVICE_DEAD;
|
||||
end_state = service_determine_dead_state(s);
|
||||
restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
|
||||
} else {
|
||||
unit_log_failure(UNIT(s), service_result_to_string(s->result));
|
||||
@@ -2031,6 +2044,10 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
|
||||
/* Also, remove the runtime directory */
|
||||
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
|
||||
|
||||
/* Also get rid of the fd store, if that's configured. */
|
||||
if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO)
|
||||
service_release_fd_store(s);
|
||||
|
||||
/* Get rid of the IPC bits of the user */
|
||||
unit_unref_uid_gid(UNIT(s), true);
|
||||
|
||||
@@ -2709,7 +2726,7 @@ static int service_start(Unit *u) {
|
||||
if (IN_SET(s->state, SERVICE_AUTO_RESTART, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
|
||||
return -EAGAIN;
|
||||
|
||||
assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED));
|
||||
assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED));
|
||||
|
||||
r = unit_acquire_invocation_id(u);
|
||||
if (r < 0)
|
||||
@@ -2768,7 +2785,7 @@ static int service_stop(Unit *u) {
|
||||
|
||||
case SERVICE_AUTO_RESTART:
|
||||
/* A restart will be scheduled or is in progress. */
|
||||
service_set_state(s, SERVICE_DEAD);
|
||||
service_set_state(s, service_determine_dead_state(s));
|
||||
return 0;
|
||||
|
||||
case SERVICE_CONDITION:
|
||||
@@ -2797,6 +2814,7 @@ static int service_stop(Unit *u) {
|
||||
case SERVICE_FAILED_BEFORE_AUTO_RESTART:
|
||||
case SERVICE_DEAD:
|
||||
case SERVICE_FAILED:
|
||||
case SERVICE_DEAD_RESOURCES_PINNED:
|
||||
default:
|
||||
/* Unknown state, or unit_stop() should already have handled these */
|
||||
assert_not_reached();
|
||||
@@ -3405,7 +3423,7 @@ static bool service_may_gc(Unit *u) {
|
||||
|
||||
/* Only allow collection of actually dead services, i.e. not those that are in the transitionary
|
||||
* SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */
|
||||
if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED))
|
||||
if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -3632,6 +3650,7 @@ static void service_notify_cgroup_empty_event(Unit *u) {
|
||||
case SERVICE_DEAD_BEFORE_AUTO_RESTART:
|
||||
case SERVICE_FAILED_BEFORE_AUTO_RESTART:
|
||||
case SERVICE_AUTO_RESTART:
|
||||
case SERVICE_DEAD_RESOURCES_PINNED:
|
||||
unit_prune_cgroup(u);
|
||||
break;
|
||||
|
||||
@@ -4720,7 +4739,7 @@ int service_set_socket_fd(
|
||||
|
||||
assert(!s->socket_peer);
|
||||
|
||||
if (s->state != SERVICE_DEAD)
|
||||
if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED))
|
||||
return -EAGAIN;
|
||||
|
||||
if (getpeername_pretty(fd, true, &peer_text) >= 0) {
|
||||
@@ -4757,7 +4776,7 @@ static void service_reset_failed(Unit *u) {
|
||||
assert(s);
|
||||
|
||||
if (s->state == SERVICE_FAILED)
|
||||
service_set_state(s, SERVICE_DEAD);
|
||||
service_set_state(s, service_determine_dead_state(s));
|
||||
|
||||
s->result = SERVICE_SUCCESS;
|
||||
s->reload_result = SERVICE_SUCCESS;
|
||||
@@ -4845,22 +4864,39 @@ static const char* service_status_text(Unit *u) {
|
||||
|
||||
static int service_clean(Unit *u, ExecCleanMask mask) {
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
bool may_clean_fdstore = false;
|
||||
Service *s = SERVICE(u);
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(mask != 0);
|
||||
|
||||
if (s->state != SERVICE_DEAD)
|
||||
if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED))
|
||||
return -EBUSY;
|
||||
|
||||
/* Determine if there's anything we could potentially clean */
|
||||
r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (strv_isempty(l))
|
||||
return -EUNATCH;
|
||||
if (mask & EXEC_CLEAN_FDSTORE)
|
||||
may_clean_fdstore = s->n_fd_store > 0 || s->n_fd_store_max > 0;
|
||||
|
||||
if (strv_isempty(l) && !may_clean_fdstore)
|
||||
return -EUNATCH; /* Nothing to potentially clean */
|
||||
|
||||
/* Let's clean the stuff we can clean quickly */
|
||||
if (may_clean_fdstore)
|
||||
service_release_fd_store(s);
|
||||
|
||||
/* If we are done, leave quickly */
|
||||
if (strv_isempty(l)) {
|
||||
if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store)
|
||||
service_set_state(s, SERVICE_DEAD);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need to clean disk stuff. This is slow, hence do it out of process, and change state */
|
||||
service_unwatch_control_pid(s);
|
||||
s->clean_result = SERVICE_SUCCESS;
|
||||
s->control_command = NULL;
|
||||
@@ -4887,10 +4923,21 @@ fail:
|
||||
|
||||
static int service_can_clean(Unit *u, ExecCleanMask *ret) {
|
||||
Service *s = SERVICE(u);
|
||||
ExecCleanMask mask = 0;
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(ret);
|
||||
|
||||
return exec_context_get_clean_mask(&s->exec_context, ret);
|
||||
r = exec_context_get_clean_mask(&s->exec_context, &mask);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (s->n_fd_store_max > 0)
|
||||
mask |= EXEC_CLEAN_FDSTORE;
|
||||
|
||||
*ret = mask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *service_finished_job(Unit *u, JobType t, JobResult result) {
|
||||
@@ -4919,6 +4966,30 @@ static int service_can_start(Unit *u) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void service_release_resources(Unit *u) {
|
||||
Service *s = SERVICE(ASSERT_PTR(u));
|
||||
|
||||
/* Invoked by the unit state engine, whenever it realizes that unit is dead and there's no job
|
||||
* anymore for it, and it hence is a good idea to release resources */
|
||||
|
||||
/* Don't release resources if this is a transitionary failed/dead state
|
||||
* (i.e. SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART), insist on a permanent
|
||||
* failure state. */
|
||||
if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED))
|
||||
return;
|
||||
|
||||
log_unit_debug(u, "Releasing resources...");
|
||||
|
||||
service_release_socket_fd(s);
|
||||
service_release_stdio_fd(s);
|
||||
|
||||
if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES)
|
||||
service_release_fd_store(s);
|
||||
|
||||
if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store)
|
||||
service_set_state(s, SERVICE_DEAD);
|
||||
}
|
||||
|
||||
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
|
||||
[SERVICE_RESTART_NO] = "no",
|
||||
[SERVICE_RESTART_ON_SUCCESS] = "on-success",
|
||||
|
||||
+2
-1
@@ -206,6 +206,7 @@ struct Service {
|
||||
ServiceFDStore *fd_store;
|
||||
size_t n_fd_store;
|
||||
unsigned n_fd_store_max;
|
||||
ExecPreserveMode fd_store_preserve_mode;
|
||||
|
||||
char *usb_function_descriptors;
|
||||
char *usb_function_strings;
|
||||
@@ -243,7 +244,7 @@ static inline usec_t service_get_watchdog_usec(Service *s) {
|
||||
extern const UnitVTable service_vtable;
|
||||
|
||||
int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net);
|
||||
void service_close_socket_fd(Service *s);
|
||||
void service_release_socket_fd(Service *s);
|
||||
|
||||
usec_t service_restart_usec_next(Service *s);
|
||||
|
||||
|
||||
+1
-1
@@ -2393,7 +2393,7 @@ static void socket_enter_running(Socket *s, int cfd_in) {
|
||||
if (r < 0) {
|
||||
/* We failed to activate the new service, but it still exists. Let's make sure the
|
||||
* service closes and forgets the connection fd again, immediately. */
|
||||
service_close_socket_fd(SERVICE(service));
|
||||
service_release_socket_fd(SERVICE(service));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
@@ -884,6 +884,7 @@ static int timer_can_clean(Unit *u, ExecCleanMask *ret) {
|
||||
Timer *t = TIMER(u);
|
||||
|
||||
assert(t);
|
||||
assert(ret);
|
||||
|
||||
*ret = t->persistent ? EXEC_CLEAN_STATE : 0;
|
||||
return 0;
|
||||
|
||||
+80
-19
@@ -394,37 +394,59 @@ static bool unit_success_failure_handler_has_jobs(Unit *unit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void unit_release_resources(Unit *u) {
|
||||
UnitActiveState state;
|
||||
ExecContext *ec;
|
||||
|
||||
assert(u);
|
||||
|
||||
if (u->job || u->nop_job)
|
||||
return;
|
||||
|
||||
if (u->perpetual)
|
||||
return;
|
||||
|
||||
state = unit_active_state(u);
|
||||
if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED))
|
||||
return;
|
||||
|
||||
if (unit_will_restart(u))
|
||||
return;
|
||||
|
||||
ec = unit_get_exec_context(u);
|
||||
if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART)
|
||||
exec_context_destroy_runtime_directory(ec, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
|
||||
|
||||
if (UNIT_VTABLE(u)->release_resources)
|
||||
UNIT_VTABLE(u)->release_resources(u);
|
||||
}
|
||||
|
||||
bool unit_may_gc(Unit *u) {
|
||||
UnitActiveState state;
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
|
||||
/* Checks whether the unit is ready to be unloaded for garbage collection.
|
||||
* Returns true when the unit may be collected, and false if there's some
|
||||
* reason to keep it loaded.
|
||||
/* Checks whether the unit is ready to be unloaded for garbage collection. Returns true when the
|
||||
* unit may be collected, and false if there's some reason to keep it loaded.
|
||||
*
|
||||
* References from other units are *not* checked here. Instead, this is done
|
||||
* in unit_gc_sweep(), but using markers to properly collect dependency loops.
|
||||
* References from other units are *not* checked here. Instead, this is done in unit_gc_sweep(), but
|
||||
* using markers to properly collect dependency loops.
|
||||
*/
|
||||
|
||||
if (u->job || u->nop_job)
|
||||
return false;
|
||||
|
||||
state = unit_active_state(u);
|
||||
|
||||
/* If the unit is inactive and failed and no job is queued for it, then release its runtime resources */
|
||||
if (UNIT_IS_INACTIVE_OR_FAILED(state) &&
|
||||
UNIT_VTABLE(u)->release_resources)
|
||||
UNIT_VTABLE(u)->release_resources(u);
|
||||
|
||||
if (u->perpetual)
|
||||
return false;
|
||||
|
||||
if (sd_bus_track_count(u->bus_track) > 0)
|
||||
return false;
|
||||
|
||||
/* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
|
||||
state = unit_active_state(u);
|
||||
|
||||
/* But we keep the unit object around for longer when it is referenced or configured to not be
|
||||
* gc'ed */
|
||||
switch (u->collect_mode) {
|
||||
|
||||
case COLLECT_INACTIVE:
|
||||
@@ -458,10 +480,10 @@ bool unit_may_gc(Unit *u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UNIT_VTABLE(u)->may_gc && !UNIT_VTABLE(u)->may_gc(u))
|
||||
return false;
|
||||
if (!UNIT_VTABLE(u)->may_gc)
|
||||
return true;
|
||||
|
||||
return true;
|
||||
return UNIT_VTABLE(u)->may_gc(u);
|
||||
}
|
||||
|
||||
void unit_add_to_load_queue(Unit *u) {
|
||||
@@ -565,6 +587,40 @@ void unit_submit_to_stop_when_bound_queue(Unit *u) {
|
||||
u->in_stop_when_bound_queue = true;
|
||||
}
|
||||
|
||||
static bool unit_can_release_resources(Unit *u) {
|
||||
ExecContext *ec;
|
||||
|
||||
assert(u);
|
||||
|
||||
if (UNIT_VTABLE(u)->release_resources)
|
||||
return true;
|
||||
|
||||
ec = unit_get_exec_context(u);
|
||||
if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void unit_submit_to_release_resources_queue(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
if (u->in_release_resources_queue)
|
||||
return;
|
||||
|
||||
if (u->job || u->nop_job)
|
||||
return;
|
||||
|
||||
if (u->perpetual)
|
||||
return;
|
||||
|
||||
if (!unit_can_release_resources(u))
|
||||
return;
|
||||
|
||||
LIST_PREPEND(release_resources_queue, u->manager->release_resources_queue, u);
|
||||
u->in_release_resources_queue = true;
|
||||
}
|
||||
|
||||
static void unit_clear_dependencies(Unit *u) {
|
||||
assert(u);
|
||||
|
||||
@@ -781,6 +837,9 @@ Unit* unit_free(Unit *u) {
|
||||
if (u->in_stop_when_bound_queue)
|
||||
LIST_REMOVE(stop_when_bound_queue, u->manager->stop_when_bound_queue, u);
|
||||
|
||||
if (u->in_release_resources_queue)
|
||||
LIST_REMOVE(release_resources_queue, u->manager->release_resources_queue, u);
|
||||
|
||||
bpf_firewall_close(u);
|
||||
|
||||
hashmap_free(u->bpf_foreign_by_key);
|
||||
@@ -2567,7 +2626,6 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags)
|
||||
assert(j);
|
||||
|
||||
if (j->state == JOB_WAITING)
|
||||
|
||||
/* So we reached a different state for this job. Let's see if we can run it now if it failed previously
|
||||
* due to EAGAIN. */
|
||||
job_add_to_run_queue(j);
|
||||
@@ -2771,6 +2829,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
|
||||
|
||||
/* Maybe the unit should be GC'ed now? */
|
||||
unit_add_to_gc_queue(u);
|
||||
|
||||
/* Maybe we can release some resources now? */
|
||||
unit_submit_to_release_resources_queue(u);
|
||||
}
|
||||
|
||||
if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) {
|
||||
@@ -5822,8 +5883,8 @@ void unit_destroy_runtime_data(Unit *u, const ExecContext *context) {
|
||||
assert(u);
|
||||
assert(context);
|
||||
|
||||
if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
|
||||
(context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u)))
|
||||
/* EXEC_PRESERVE_RESTART is handled via unit_release_resources()! */
|
||||
if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO)
|
||||
exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
|
||||
|
||||
exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id);
|
||||
|
||||
@@ -317,6 +317,9 @@ typedef struct Unit {
|
||||
/* Queue of units that have a BindTo= dependency on some other unit, and should possibly be shut down */
|
||||
LIST_FIELDS(Unit, stop_when_bound_queue);
|
||||
|
||||
/* Queue of units that should be checked if they can release resources now */
|
||||
LIST_FIELDS(Unit, release_resources_queue);
|
||||
|
||||
/* PIDs we keep an eye on. Note that a unit might have many
|
||||
* more, but these are the ones we care enough about to
|
||||
* process SIGCHLD for */
|
||||
@@ -479,6 +482,7 @@ typedef struct Unit {
|
||||
bool in_stop_when_unneeded_queue:1;
|
||||
bool in_start_when_upheld_queue:1;
|
||||
bool in_stop_when_bound_queue:1;
|
||||
bool in_release_resources_queue:1;
|
||||
|
||||
bool sent_dbus_new_signal:1;
|
||||
|
||||
@@ -832,6 +836,8 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c);
|
||||
int unit_choose_id(Unit *u, const char *name);
|
||||
int unit_set_description(Unit *u, const char *description);
|
||||
|
||||
void unit_release_resources(Unit *u);
|
||||
|
||||
bool unit_may_gc(Unit *u);
|
||||
|
||||
static inline bool unit_is_extrinsic(Unit *u) {
|
||||
@@ -853,6 +859,7 @@ void unit_add_to_target_deps_queue(Unit *u);
|
||||
void unit_submit_to_stop_when_unneeded_queue(Unit *u);
|
||||
void unit_submit_to_start_when_upheld_queue(Unit *u);
|
||||
void unit_submit_to_stop_when_bound_queue(Unit *u);
|
||||
void unit_submit_to_release_resources_queue(Unit *u);
|
||||
|
||||
int unit_merge(Unit *u, Unit *other);
|
||||
int unit_merge_by_name(Unit *u, const char *other);
|
||||
|
||||
@@ -2198,7 +2198,8 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con
|
||||
"USBFunctionStrings",
|
||||
"OOMPolicy",
|
||||
"TimeoutStartFailureMode",
|
||||
"TimeoutStopFailureMode"))
|
||||
"TimeoutStopFailureMode",
|
||||
"FileDescriptorStorePreserve"))
|
||||
return bus_append_string(m, field, eq);
|
||||
|
||||
if (STR_IN_SET(field, "PermissionsStartOnly",
|
||||
|
||||
@@ -21,7 +21,7 @@ int verb_clean_or_freeze(int argc, char *argv[], void *userdata) {
|
||||
polkit_agent_open_maybe();
|
||||
|
||||
if (!arg_clean_what) {
|
||||
arg_clean_what = strv_new("cache", "runtime");
|
||||
arg_clean_what = strv_new("cache", "runtime", "fdstore");
|
||||
if (!arg_clean_what)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
@@ -937,7 +937,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
|
||||
"state\n"
|
||||
"cache\n"
|
||||
"logs\n"
|
||||
"configuration");
|
||||
"configuration\n"
|
||||
"fdstore");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[Service]
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
FileDescriptorStoreMax=10
|
||||
FileDescriptorStorePreserve=restart
|
||||
ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/fdstore-pin.sh 0
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
@@ -0,0 +1,8 @@
|
||||
[Service]
|
||||
Type=notify
|
||||
NotifyAccess=all
|
||||
FileDescriptorStoreMax=10
|
||||
FileDescriptorStorePreserve=yes
|
||||
ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/fdstore-pin.sh 1
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
PINNED="$1"
|
||||
COUNTER="/tmp/fdstore-invoked.$PINNED"
|
||||
FILE="/tmp/fdstore-data.$PINNED"
|
||||
|
||||
# This script is called six times: thrice from a service unit where the fdstore
|
||||
# is pinned, and thrice where it isn't. The second iteration of each series is
|
||||
# a restart, the third a stop followed by a start
|
||||
|
||||
if [ -e "$COUNTER" ] ; then
|
||||
read -r N < "$COUNTER"
|
||||
else
|
||||
N=0
|
||||
fi
|
||||
|
||||
echo "Invocation #$N with PINNED=$PINNED."
|
||||
|
||||
if [ "$N" -eq 0 ] ; then
|
||||
# First iteration
|
||||
test "${LISTEN_FDS:-0}" -eq 0
|
||||
test ! -e "$FILE"
|
||||
echo waldi > "$FILE"
|
||||
systemd-notify --fd=3 --fdname="fd-$N-$PINNED" 3< "$FILE"
|
||||
elif [ "$N" -eq 1 ] || { [ "$N" -eq 2 ] && [ "$PINNED" -eq 1 ]; } ; then
|
||||
# Second iteration, or iteration with pinning on
|
||||
test "${LISTEN_FDS:-0}" -eq 1
|
||||
# We reopen fd #3 here, so that the read offset is at zero each time (hence no <&3 here…)
|
||||
read -r word < /proc/self/fd/3
|
||||
test "$word" = "waldi"
|
||||
else
|
||||
test "${LISTEN_FDS:-0}" -eq 0
|
||||
test -e "$FILE"
|
||||
fi
|
||||
|
||||
if [ "$N" -ge 2 ] ; then
|
||||
rm "$COUNTER" "$FILE"
|
||||
else
|
||||
echo $((N + 1)) > "$COUNTER"
|
||||
fi
|
||||
|
||||
systemd-notify --ready --status="Ready"
|
||||
|
||||
exec sleep infinity
|
||||
@@ -0,0 +1,3 @@
|
||||
[Unit]
|
||||
After=fdstore-pin.service fdstore-nopin.service
|
||||
Wants=fdstore-pin.service fdstore-nopin.service
|
||||
@@ -6,3 +6,5 @@ Description=TEST-80-NOTIFYACCESS
|
||||
ExecStartPre=rm -f /failed /testok
|
||||
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
|
||||
Type=oneshot
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
|
||||
@@ -48,6 +48,8 @@ assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
|
||||
|
||||
rm /tmp/syncfifo1 /tmp/syncfifo2
|
||||
|
||||
# Now test basic fdstore behaviour
|
||||
|
||||
MYSCRIPT="/tmp/myscript$RANDOM.sh"
|
||||
cat >> "$MYSCRIPT" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
@@ -74,6 +76,55 @@ systemd-analyze fdstore "$MYUNIT" --json=short | grep -P -q '\[{"fdname":"quux",
|
||||
systemctl stop "$MYUNIT"
|
||||
rm "$MYSCRIPT"
|
||||
|
||||
systemd-analyze log-level debug
|
||||
|
||||
# Test fdstore pinning (this will pull in fdstore-pin.service fdstore-nopin.service)
|
||||
systemctl start fdstore-pin.target
|
||||
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P FileDescriptorStorePreserve)" yes
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P FileDescriptorStorePreserve)" restart
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
|
||||
|
||||
# The file descriptor store should survive service restarts
|
||||
systemctl restart fdstore-pin.service fdstore-nopin.service
|
||||
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
|
||||
|
||||
# It should not survive the service stop plus a later start (unless pinned)
|
||||
systemctl stop fdstore-pin.service fdstore-nopin.service
|
||||
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
|
||||
|
||||
systemctl start fdstore-pin.service fdstore-nopin.service
|
||||
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
|
||||
|
||||
systemctl stop fdstore-pin.service fdstore-nopin.service
|
||||
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
|
||||
|
||||
systemctl clean fdstore-pin.service --what=fdstore
|
||||
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 0
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
|
||||
assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead
|
||||
assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
|
||||
|
||||
touch /testok
|
||||
rm /failed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user