1
0
mirror of https://github.com/systemd/systemd synced 2025-11-06 10:24:44 +01:00

Compare commits

...

36 Commits

Author SHA1 Message Date
Yu Watanabe
8457f2a395 TEST-64-UDEV-STORAGE: generate debugging logs
Let's see if it provides something useful for debugging issue #39544.
2025-11-05 05:16:48 +09:00
Luca Boccassi
1e1f63e63b test: rework dmsetup test to wait for device to disappear
There might be a delay between an umount and a refcounted device
to disappear, so the test can be flaky:

[   36.107128] TEST-50-DISSECT.sh[1662]: ++ dmsetup ls
[   36.108314] TEST-50-DISSECT.sh[1663]: ++ grep loop
[   36.109283] TEST-50-DISSECT.sh[1664]: ++ grep -c verity
[   36.110284] TEST-50-DISSECT.sh[1360]: + test 1 -eq 1
[   36.111555] TEST-50-DISSECT.sh[1360]: + umount -R /tmp/TEST-50-IMAGES.hxm/mount
[   36.112237] TEST-50-DISSECT.sh[1668]: ++ dmsetup ls
[   36.113039] TEST-50-DISSECT.sh[1669]: ++ grep loop
[   36.113833] TEST-50-DISSECT.sh[1670]: ++ grep -c verity
[   36.114517] TEST-50-DISSECT.sh[1360]: + test 0 -eq 1
[   36.116734] TEST-50-DISSECT.sh[1000]: + echo 'Subtest /usr/lib/systemd/tests/testdata/units/TEST-50-DISSECT.dissect.sh failed'

https://github.com/systemd/systemd/actions/runs/19062162467/job/54444112653?pr=39540#logs

Switch to searching for the dm entry and check for it specifically,
and wait for it to disappear before checking that it is no longer
in the dm table.

Follow-up for 10fc43e504da5962fa5f04341ae8ba92a9981be9
2025-11-05 04:08:44 +09:00
Yu Watanabe
71d64f0527
core/service: reload fixlets (#39376)
Fixes https://github.com/systemd/systemd/issues/37515
2025-11-05 02:54:38 +09:00
Yu Watanabe
d954161f9b
core/exec-invoke: gracefully handle lack of privilege for initgroups() in user mode (#39039)
Fixes #39038
2025-11-05 02:53:05 +09:00
Yu Watanabe
cfc7fd426d
sysext: Check for /etc/initrd-release in given --root= tree (#39473) 2025-11-05 02:51:25 +09:00
Yu Watanabe
82b6dc1a17
sd-json: accept NULL path in sd_json_parse_file_at() too, port one manual fdopen() to it (#39538) 2025-11-05 01:40:27 +09:00
jouyouyun
2b8c7adbec cgls: print error messages when --unit and --user-unit are used together
Mixing the `--unit` and `--user-unit` options will result in error messages.
During the parsing phase, only the `arg_show_unit` record of the last
occurrence of the option is used; all names are placed in the same `arg_names`,
thus mixing the two types of units in the query.

For example, `-u foo --user-unit bar` will also treat `foo` as a user unit and
query it in the user service.
2025-11-05 01:38:18 +09:00
Chris Down
a8c3ac6672 systemctl: Fix shutdown time parsing across DST changes
When parsing an absolute time specification like `hh:mm` for the
`shutdown` command, the code interprets a time in the past as "tomorrow
at this time". It currently implements this by adding a fixed 24-hour
duration (`USEC_PER_DAY`) to the timestamp.

This assumption breaks across DST transitions, as the day might not be
24 hours long. This can cause the shutdown to be scheduled at the wrong
time (typically off by one hour in either direction).

Change the logic to perform calendar arithmetic instead of timestamp
arithmetic. If the calculated time is in the past, we increment
`tm.tm_mday` and call `mktime_or_timegm_usec()` a second time.

This delegates all date normalization logic to `mktime()`, which
correctly handles all edge cases, including DST transitions, month-end
rollovers, and leap years.

Fixes: https://github.com/systemd/systemd/issues/39232
2025-11-05 01:36:47 +09:00
Kai Lueke
570eae5007 sysext: Check for /etc/initrd-release in given --root= tree
Both sysext and confext used the host's /etc/initrd-release file even
when --root=/somewhere was specified. A workaround was the
SYSTEMD_IN_INITRD= env var but without knowing this it was quite
confusing. Aside from users validating their extensions, the primary
use case for this to matter is when the extensions are set up from the
initrd where the initrd-release file is present when running but we want
to prepare the extensions for the final system and thus should match
for the right scope.
Make systemd-sysext check for /etc/initrd-release inside the given
--root= tree. An alternative would be to always ignore the
initrd-release check when --root= is passed but this way it is more
consistent. The image policy logic for EFI-loader-passed extensions
won't take effect when --root= is used, though.
2025-11-04 22:10:50 +09:00
Kai Lueke
6649562924 test: Add missing test cleanup for the last sysext test
The last sysext test leaked things into new tests added later,
uncovered by any new tests leftover check.
Remove the mutable folder state through a trap as done in other tests.
2025-11-04 22:10:18 +09:00
Mike Yuan
ca8658120e
TEST-80-NOTIFYACCESS: add test case for #37515 2025-11-04 12:18:34 +01:00
Mike Yuan
b03e1b09af
core/service: rework ExecReload= + Type=notify-reload interaction, add ExecReloadPost=
When Type=notify-reload got introduced, it wasn't intended to be
mutually exclusive with ExecReload=. However, currently ExecReload=
is immediately forked off after the service main process is signaled,
leaving states in between essentially undefined. Given so broken
it is I doubt any sane user is using this setup, hence I took a stab
to rework everything:

1.  Extensions are refreshed (unchanged)
2.  ExecReload= is forked off without signaling the process
3a. If RELOADING=1 is sent during the ExecReload= invocation,
    we'd refrain from signaling the process again, instead
    just transition to SERVICE_RELOAD_NOTIFY directly and
    wait for READY=1
3b. If not, signal the process after ExecReload= finishes
    (from now on the same as Type=notify-reload w/o ExecReload=)
4.  To accomodate the use case of performing post-reload tasks,
    ExecReloadPost= is introduced which executes after READY=1

The new model greatly simplifies things, as no control processes
will be around in SERVICE_RELOAD_SIGNAL and SERVICE_RELOAD_NOTIFY
states.

See also: https://github.com/systemd/systemd/issues/37515#issuecomment-2891229652
2025-11-04 12:18:33 +01:00
Mike Yuan
b3c6709fde
macro: add 21st case for IN_SET 2025-11-04 12:17:33 +01:00
Mike Yuan
48632305c7
man/org.freedesktop.systemd1: fix typo (ExecStop -> -Post) 2025-11-04 12:17:33 +01:00
Mike Yuan
1b4cf02ada
core/service: fix missing error handling for refresh-extensions control process 2025-11-04 12:17:33 +01:00
Mike Yuan
5fb8387c67
core/service: reset all reload-related states once a cycle completes
Fixes #37515
2025-11-04 12:17:33 +01:00
Mike Yuan
584e89f26e
core/service: consolidate where to initialize reload_result 2025-11-04 12:17:32 +01:00
Mike Yuan
98734ac74d
core/service: forbid reverting STOPPING=1
We don't permit state transition from STOPPING back to RUNNING,
hence refrain from resetting notify_state too.
2025-11-04 12:17:32 +01:00
Mike Yuan
ed834f11cb
core/service: split out service_notify_message_process_state()
No functional change, preparation for later changes.
2025-11-04 12:17:32 +01:00
Mike Yuan
3d888e2dad
core/service: add missing serialization for notify_state
This really should be persisted across daemon-reload since
it might contain deferred state transitions.
2025-11-04 12:17:32 +01:00
Mike Yuan
38ea58a907
core/service: remove effectively unused NOTIFY_UNKNOWN state
We usually use _INVALID enum value as placeholder.

While at it, reset notify_state in service_enter_dead() for consistency.
2025-11-04 12:17:31 +01:00
Mike Yuan
1b5a9a7d56
core/service: avoid duplicate unit_add_to_dbus_queue() call
If we're changing state anyways, service_set_state() -> unit_notify()
will take care of dbus signaling for us.
2025-11-04 12:17:31 +01:00
Mike Yuan
1b272d778a
core/service: introduce SERVICE_STATE_WITH_WATCHDOG 2025-11-04 12:17:31 +01:00
Mike Yuan
1773d2a207
core: drop redundant pidref_done() calls
{service,socket}_unwatch_control_pid() -> unit_unwatch_pidref_done()
is unconditionally called everywhere.
2025-11-04 12:17:31 +01:00
Mike Yuan
16a0cbe915
core/service: merge service_enter_reload_mounting() into _refresh_extensions() 2025-11-04 12:17:31 +01:00
Mike Yuan
2e43eee666
core/service: restore spuriously changed comment
Not sure why dfdeb0b1cbb05a213f0965eedfe0e7ef06cd39d3
touched it at all.
2025-11-04 12:17:30 +01:00
Mike Yuan
aebe553db3
core/service: do not set reload_begin_usec when refreshing confexts
reload_begin_usec is a barrier for determining whether RELOADING=1
came after the main process got signaled, unrelated to refreshing.
2025-11-04 12:17:30 +01:00
Mike Yuan
af3a197515
core/service: no need for STATUS_TEXT_MAX to reside in header 2025-11-04 12:17:30 +01:00
Mike Yuan
969c8050d8
core/job: use UNIT_IS_* helpers 2025-11-04 12:17:30 +01:00
Mike Yuan
b1947bf6e3
core/job: drop pure qualifier for static inline functions
The impl is directly visible to the compiler so it can apply
all sorts of optimizations wherever it sees fit. And with
the previous commit they are actually "const".
2025-11-04 12:17:29 +01:00
Mike Yuan
1fa9473b07
core/job: mark job_type_lookup_merge() and _is_redundant() as const
They don't take pointers, hence are eligible for stronger guarantees.
2025-11-04 12:17:29 +01:00
Mike Yuan
d98c783516
sysupdated: port to sd_json_parse_file_at() 2025-11-04 10:46:01 +01:00
Mike Yuan
89b9a68e5f
sd-json: accept NULL path in sd_json_parse_file_at() too 2025-11-03 22:32:57 +01:00
Mike Yuan
eaebc33220
fileio: mask off O_NOFOLLOW in xfopenat_regular() for fd_reopen()
in a similar fashion as xopenat_full()
2025-11-03 22:32:25 +01:00
Mike Yuan
7400e37578
TEST-74-AUX-UTILS.run: add test case for #39038 2025-10-25 21:26:54 +02:00
Mike Yuan
c86914667a
core/exec-invoke: gracefully handle lack of privilege for initgroups() in user mode
Otherwise specifying User=SELF also fails because we got
no privilege to call setgroups().

Fixes #39038
2025-10-25 21:18:11 +02:00
29 changed files with 547 additions and 299 deletions

View File

@ -2856,6 +2856,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasasttttuii) ExecReloadEx = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasbttttuii) ExecReloadPost = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasasttttuii) ExecReloadPostEx = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasbttttuii) ExecStop = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
readonly a(sasasttttuii) ExecStopEx = [...];
@ -3537,9 +3541,9 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property ExecReloadEx is not documented!-->
<!--property ExecStopEx is not documented!-->
<!--property ExecReloadPostEx is not documented!-->
<!--property ExecStopPost is not documented!-->
<!--property ExecStopEx is not documented!-->
<!--property ExecStopPostEx is not documented!-->
@ -4211,6 +4215,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="ExecReloadEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecReloadPost"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecReloadPostEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecStop"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecStopEx"/>
@ -4841,10 +4849,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
last watchdog ping received from the service, or 0 if none was ever received.</para>
<para><varname>ExecStartPre</varname>, <varname>ExecStart</varname>, <varname>ExecStartPost</varname>,
<varname>ExecReload</varname>, <varname>ExecStop</varname>, and <varname>ExecStop</varname> are arrays
of structures where each struct contains: the binary path to execute; an array with all arguments to
pass to the executed command, starting with argument 0; a boolean whether it should be considered a
failure if the process exits uncleanly; two pairs of
<varname>ExecReload</varname>, <varname>ExecReloadPost</varname>, <varname>ExecStop</varname>, and
<varname>ExecStopPost</varname> are arrays of structures where each struct contains: the binary path
to execute; an array with all arguments to pass to the executed command, starting with argument 0;
a boolean whether it should be considered a failure if the process exits uncleanly; two pairs of
<constant>CLOCK_REALTIME</constant>/<constant>CLOCK_MONOTONIC</constant> microsecond timestamps when
the process began and finished running the last time, or 0 if it never ran or never finished running;
the PID of the process, or 0 if it has not run yet; the exit code and status of the last run. This
@ -5814,8 +5822,6 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property ExecStopPre is not documented!-->
<!--property ExecStopPost is not documented!-->
<!--property Slice is not documented!-->
<!--property ControlGroupId is not documented!-->
@ -12544,8 +12550,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
<para><varname>UserNamespacePath</varname>,
<varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
<varname>OOMKills</varname>,
<varname>ManagedOOMKills</varname>,
<varname>ExecReloadPost</varname>, and
<varname>ExecReloadPostEx</varname> were added in version 259.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>

View File

@ -475,8 +475,8 @@
<varlistentry>
<term><varname>ExecReload=</varname></term>
<listitem><para>Commands to execute to trigger a configuration reload in the service. This argument
takes multiple command lines, following the same scheme as described for
<listitem><para>Commands to execute to trigger a configuration reload in the service. This setting
may take multiple command lines, following the same scheme as described for
<varname>ExecStart=</varname> above. Use of this setting is optional. Specifier and environment
variable substitution is supported here following the same scheme as for
<varname>ExecStart=</varname>.</para>
@ -486,13 +486,12 @@
<programlisting>ExecReload=kill -HUP $MAINPID</programlisting>
<para>Note however that reloading a daemon by enqueuing a signal (as with the example line above) is
usually not a good choice, because this is an asynchronous operation and hence not suitable when
ordering reloads of multiple services against each other. It is thus strongly recommended to either
use <varname>Type=</varname><option>notify-reload</option> in place of
<varname>ExecReload=</varname>, or to set <varname>ExecReload=</varname> to a command that not only
triggers a configuration reload of the daemon, but also synchronously waits for it to complete. For
example, <citerefentry
<para>Note however that reloading a daemon by enqueuing a signal without completion notification
(as is the case with the example line above) is usually not a good choice, because this is an
asynchronous operation and hence not suitable when ordering reloads of multiple services against
each other. It is thus strongly recommended to either use <varname>Type=notify-reload</varname>,
or to set <varname>ExecReload=</varname> to a command that not only triggers a configuration reload
of the daemon, but also synchronously waits for it to complete. For example, <citerefentry
project='mankier'><refentrytitle>dbus-broker</refentrytitle><manvolnum>1</manvolnum></citerefentry>
uses the following:</para>
@ -500,6 +499,22 @@
/org/freedesktop/DBus org.freedesktop.DBus \
ReloadConfig
</programlisting>
<para>This setting can be combined with <varname>Type=notify-reload</varname>, in which case
the service main process is signaled after all specified command lines finish execution. Specially,
if <literal>RELOADING=1</literal> notification is received before <varname>ExecReload=</varname>
completes, the signaling is skipped and the service manager immediately starts listening for
<literal>READY=1</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ExecReloadPost=</varname></term>
<listitem><para>Commands to execute after a successful reload operation. Syntax for this setting
is exactly the same as <varname>ExecReload=</varname>.</para>
<xi:include href="version-info.xml" xpointer="v259"/>
</listitem>
</varlistentry>
@ -1072,18 +1087,14 @@
<varlistentry>
<term><varname>RootDirectoryStartOnly=</varname></term>
<listitem><para>Takes a boolean argument. If true, the root
directory, as configured with the
<listitem><para>Takes a boolean argument. If true, the root directory, as configured with the
<varname>RootDirectory=</varname> option (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information), is only applied to the process started
with <varname>ExecStart=</varname>, and not to the various
other <varname>ExecStartPre=</varname>,
<varname>ExecStartPost=</varname>,
<varname>ExecReload=</varname>, <varname>ExecStop=</varname>,
and <varname>ExecStopPost=</varname> commands. If false, the
setting is applied to all configured commands the same way.
Defaults to false.</para></listitem>
for more information), is only applied to the process started with <varname>ExecStart=</varname>,
and not to the various other <varname>ExecStartPre=</varname>, <varname>ExecStartPost=</varname>,
<varname>ExecReload=</varname>, <varname>ExecReloadPost=</varname>, <varname>ExecStop=</varname>,
and <varname>ExecStopPost=</varname> commands. If false, the setting is applied to all
configured commands the same way. Defaults to false.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -1011,7 +1011,7 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int
assert(mode);
assert(ret);
if (dir_fd == AT_FDCWD && open_flags == 0 && path)
if (dir_fd == AT_FDCWD && path && open_flags == 0)
f = fopen(path, mode);
else {
_cleanup_close_ int fd = -EBADF;
@ -1029,7 +1029,7 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int
if (dir_fd == AT_FDCWD)
return -EBADF;
fd = fd_reopen(dir_fd, mode_flags | open_flags);
fd = fd_reopen(dir_fd, (mode_flags | open_flags) & ~O_NOFOLLOW);
if (fd < 0)
return fd;
}

View File

@ -224,10 +224,11 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_START_POST] = "start-post",
[SERVICE_RUNNING] = "running",
[SERVICE_EXITED] = "exited",
[SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
[SERVICE_RELOAD] = "reload",
[SERVICE_RELOAD_SIGNAL] = "reload-signal",
[SERVICE_RELOAD_NOTIFY] = "reload-notify",
[SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
[SERVICE_RELOAD_POST] = "reload-post",
[SERVICE_STOP] = "stop",
[SERVICE_STOP_WATCHDOG] = "stop-watchdog",
[SERVICE_STOP_SIGTERM] = "stop-sigterm",

View File

@ -131,10 +131,11 @@ typedef enum ServiceState {
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
SERVICE_RELOAD, /* Reloading via ExecReload= */
SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */
SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */
SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
SERVICE_RELOAD_POST,
SERVICE_MOUNTING, /* Performing a live mount into the namespace of the service */
SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */
SERVICE_STOP_WATCHDOG,

View File

@ -114,12 +114,20 @@ static int parse_argv(int argc, char *argv[]) {
break;
case 'u':
if (arg_show_unit == SHOW_UNIT_USER)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot combine --unit with --user-unit.");
arg_show_unit = SHOW_UNIT_SYSTEM;
if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */
return log_oom();
break;
case ARG_USER_UNIT:
if (arg_show_unit == SHOW_UNIT_SYSTEM)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot combine --user-unit with --unit.");
arg_show_unit = SHOW_UNIT_USER;
if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */
return log_oom();

View File

@ -383,6 +383,8 @@ const sd_bus_vtable bus_service_vtable[] = {
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStartPostEx", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecReloadEx", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecReloadPost", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecReloadPostEx", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStopEx", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),

View File

@ -904,8 +904,16 @@ static int get_supplementary_groups(
bool keep_groups = false;
if (user && gid_is_valid(gid) && gid != 0) {
/* First step, initialize groups from /etc/groups */
if (initgroups(user, gid) < 0)
if (initgroups(user, gid) < 0) {
/* If our primary gid is already the one specified in Group= (i.e. we're running in
* user mode), gracefully handle the case where we have no privilege to re-initgroups().
*
* Note that group memberships of the current user might have been modified, but
* the change will only take effect after re-login. It's better to continue on with
* existing credentials rather than erroring out. */
if (!ERRNO_IS_PRIVILEGE(errno) || gid != getgid())
return -errno;
}
keep_groups = true;
}

View File

@ -436,13 +436,11 @@ bool job_type_is_redundant(JobType a, UnitActiveState b) {
switch (a) {
case JOB_START:
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
case JOB_VERIFY_ACTIVE:
return UNIT_IS_ACTIVE_OR_RELOADING(b);
case JOB_STOP:
return IN_SET(b, UNIT_INACTIVE, UNIT_FAILED);
case JOB_VERIFY_ACTIVE:
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING);
return UNIT_IS_INACTIVE_OR_FAILED(b);
case JOB_RELOAD:
/* Reload jobs are never considered redundant/duplicate. Refer to jobs_may_late_merge() for

View File

@ -162,17 +162,17 @@ int job_coldplug(Job *j);
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
void job_dependency_free(JobDependency *l);
JobType job_type_lookup_merge(JobType a, JobType b) _pure_;
JobType job_type_lookup_merge(JobType a, JobType b) _const_;
_pure_ static inline bool job_type_is_mergeable(JobType a, JobType b) {
static inline bool job_type_is_mergeable(JobType a, JobType b) {
return job_type_lookup_merge(a, b) >= 0;
}
_pure_ static inline bool job_type_is_conflicting(JobType a, JobType b) {
static inline bool job_type_is_conflicting(JobType a, JobType b) {
return a != JOB_NOP && b != JOB_NOP && !job_type_is_mergeable(a, b);
}
_pure_ static inline bool job_type_is_superset(JobType a, JobType b) {
static inline bool job_type_is_superset(JobType a, JobType b) {
/* Checks whether operation a is a "superset" of b in its actions */
if (b == JOB_NOP)
return true;
@ -181,7 +181,7 @@ _pure_ static inline bool job_type_is_superset(JobType a, JobType b) {
return a == job_type_lookup_merge(a, b);
}
bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_;
bool job_type_is_redundant(JobType a, UnitActiveState b) _const_;
/* Collapses a state-dependent job type into a simpler type by observing
* the state of the unit which it is going to be applied to. */

View File

@ -430,8 +430,9 @@ Service.PIDFile, config_parse_pid_file,
Service.ExecCondition, config_parse_exec, SERVICE_EXEC_CONDITION, offsetof(Service, exec_command)
Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command)
Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command)
Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command)
Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command)
Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command)
Service.ExecReloadPost, config_parse_exec, SERVICE_EXEC_RELOAD_POST, offsetof(Service, exec_command)
Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command)
Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command)
Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec)

View File

@ -56,6 +56,8 @@
#include "unit-name.h"
#include "utf8.h"
#define STATUS_TEXT_MAX (16U*1024U)
#define service_spawn(...) service_spawn_internal(__func__, __VA_ARGS__)
static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
@ -66,10 +68,11 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_START_POST] = UNIT_ACTIVATING,
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_RELOAD_POST] = UNIT_RELOADING,
[SERVICE_MOUNTING] = UNIT_REFRESHING,
[SERVICE_STOP] = UNIT_DEACTIVATING,
[SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING,
@ -98,10 +101,11 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_START_POST] = UNIT_ACTIVE,
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_RELOAD_POST] = UNIT_RELOADING,
[SERVICE_MOUNTING] = UNIT_REFRESHING,
[SERVICE_STOP] = UNIT_DEACTIVATING,
[SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING,
@ -126,13 +130,15 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void
static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
static void service_enter_signal(Service *s, ServiceState state, ServiceResult f);
static void service_reload_finish(Service *s, ServiceResult f);
static void service_enter_reload_by_notify(Service *s);
static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL);
@ -142,13 +148,21 @@ static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_CONDITION,
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_CLEANING);
}
static bool SERVICE_STATE_WITH_WATCHDOG(ServiceState state) {
return IN_SET(state,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING);
}
static void service_init(Unit *u) {
Service *s = SERVICE(u);
@ -175,6 +189,7 @@ static void service_init(Unit *u) {
EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT;
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
s->notify_state = _NOTIFY_STATE_INVALID;
s->watchdog_original_usec = USEC_INFINITY;
@ -1289,7 +1304,7 @@ static void service_set_state(Service *s, ServiceState state) {
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_REFRESH_EXTENSIONS,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@ -1317,7 +1332,7 @@ static void service_set_state(Service *s, ServiceState state) {
if (state != SERVICE_START)
s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS, SERVICE_MOUNTING))
if (!SERVICE_STATE_WITH_WATCHDOG(state))
service_stop_watchdog(s);
if (state != SERVICE_MOUNTING) /* Just in case */
@ -1359,10 +1374,11 @@ static usec_t service_coldplug_timeout(Service *s) {
case SERVICE_START_PRE:
case SERVICE_START:
case SERVICE_START_POST:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD_POST:
case SERVICE_MOUNTING:
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec);
@ -1429,7 +1445,7 @@ static int service_coldplug(Unit *u) {
SERVICE_DEAD_RESOURCES_PINNED))
(void) unit_setup_exec_runtime(u);
if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS, SERVICE_MOUNTING) &&
if (SERVICE_STATE_WITH_WATCHDOG(s->deserialized_state) &&
freezer_state_objective(u->freezer_state) == FREEZER_RUNNING)
service_start_watchdog(s);
@ -2167,8 +2183,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
/* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
s->forbid_restart = false;
/* Reset NotifyAccess override */
/* Reset notify states */
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
s->notify_state = _NOTIFY_STATE_INVALID;
/* We want fresh tmpdirs and ephemeral snapshots in case the service is started again immediately. */
s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
@ -2205,7 +2222,6 @@ static void service_enter_stop_post(Service *s, ServiceResult f) {
s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_STOP_POST;
pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@ -2318,7 +2334,6 @@ static void service_enter_stop(Service *s, ServiceResult f) {
s->control_command = s->exec_command[SERVICE_EXEC_STOP];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_STOP;
pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@ -2404,7 +2419,6 @@ static void service_enter_start_post(Service *s) {
s->control_command = s->exec_command[SERVICE_EXEC_START_POST];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_START_POST;
pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@ -2538,7 +2552,6 @@ static void service_enter_start(Service *s) {
case SERVICE_FORKING:
/* For forking services we wait until the start process exited. */
pidref_done(&s->control_pid);
s->control_pid = TAKE_PIDREF(pidref);
return service_set_state(s, SERVICE_START);
@ -2612,7 +2625,6 @@ static void service_enter_condition(Service *s) {
goto fail;
s->control_command_id = SERVICE_EXEC_CONDITION;
pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@ -2696,9 +2708,7 @@ static void service_enter_reload_by_notify(Service *s) {
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
s->reload_result = SERVICE_FAILURE_RESOURCES;
service_enter_running(s, SERVICE_SUCCESS);
return;
return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
service_set_state(s, SERVICE_RELOAD_NOTIFY);
@ -2709,31 +2719,91 @@ static void service_enter_reload_by_notify(Service *s) {
log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r));
}
static void service_enter_reload_signal_exec(Service *s) {
bool killed = false;
static void service_enter_reload_post(Service *s) {
int r;
assert(s);
service_unwatch_control_pid(s);
s->reload_result = SERVICE_SUCCESS;
usec_t ts = now(CLOCK_MONOTONIC);
s->control_command = s->exec_command[SERVICE_EXEC_RELOAD_POST];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_RELOAD_POST;
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec,
&s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'reload-post' task: %m");
return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
service_set_state(s, SERVICE_RELOAD_POST);
} else
service_reload_finish(s, SERVICE_SUCCESS);
}
static void service_enter_reload_signal(Service *s) {
int r;
assert(s);
if (s->type != SERVICE_NOTIFY_RELOAD)
return service_enter_reload_post(s);
if (s->state == SERVICE_RELOAD) {
/* We executed ExecReload=, and the service has already notified us the result?
* Directly transition to next state. */
if (s->notify_state == NOTIFY_RELOADING)
return service_set_state(s, SERVICE_RELOAD_NOTIFY);
if (s->notify_state == NOTIFY_RELOAD_READY)
return service_enter_reload_post(s);
}
if (pidref_is_set(&s->main_pid)) {
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
goto fail;
}
if (s->type == SERVICE_NOTIFY_RELOAD && pidref_is_set(&s->main_pid)) {
r = pidref_kill_and_sigcont(&s->main_pid, s->reload_signal);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to send reload signal: %m");
goto fail;
}
killed = true;
service_set_state(s, SERVICE_RELOAD_SIGNAL);
} else
service_enter_reload_post(s);
return;
fail:
service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
static void service_enter_reload(Service *s) {
int r;
assert(s);
service_unwatch_control_pid(s);
if (IN_SET(s->notify_state, NOTIFY_RELOADING, NOTIFY_RELOAD_READY))
s->notify_state = _NOTIFY_STATE_INVALID;
/* Store the timestamp when we started reloading: when reloading via SIGHUP we won't leave the reload
* state until we received both RELOADING=1 and READY=1 with MONOTONIC_USEC= set to a value above
* this. Thus we know for sure the reload cycle was executed *after* we requested it, and is not one
* that was already in progress before. */
s->reload_begin_usec = now(CLOCK_MONOTONIC);
s->control_command = s->exec_command[SERVICE_EXEC_RELOAD];
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_RELOAD;
pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
@ -2742,33 +2812,12 @@ static void service_enter_reload_signal_exec(Service *s) {
&s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'reload' task: %m");
goto fail;
return service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
service_set_state(s, SERVICE_RELOAD);
} else if (killed) {
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
goto fail;
}
service_set_state(s, SERVICE_RELOAD_SIGNAL);
} else {
service_enter_running(s, SERVICE_SUCCESS);
return;
}
/* Store the timestamp when we started reloading: when reloading via SIGHUP we won't leave the reload
* state until we received both RELOADING=1 and READY=1 with MONOTONIC_USEC= set to a value above
* this. Thus we know for sure the reload cycle was executed *after* we requested it, and is not one
* that was already in progress before. */
s->reload_begin_usec = ts;
return;
fail:
s->reload_result = SERVICE_FAILURE_RESOURCES;
service_enter_running(s, SERVICE_SUCCESS);
} else
service_enter_reload_signal(s);
}
static bool service_should_reload_extensions(Service *s) {
@ -2805,15 +2854,20 @@ static void service_enter_refresh_extensions(Service *s) {
assert(s);
/* If we don't have extensions to reload, immediately go to the signal step */
/* If we don't have extensions to refresh, immediately transition to reload state */
if (!service_should_reload_extensions(s))
return (void) service_enter_reload_signal_exec(s);
return service_enter_reload(s);
service_unwatch_control_pid(s);
s->reload_result = SERVICE_SUCCESS;
s->control_command = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
goto fail;
}
/* Given we are running from PID1, avoid doing potentially heavy I/O operations like opening images
* directly, and instead fork a worker process. */
r = unit_fork_helper_process(UNIT(s), "(sd-refresh-extensions)", /* into_cgroup= */ false, &worker);
@ -2868,28 +2922,7 @@ static void service_enter_refresh_extensions(Service *s) {
return;
fail:
s->reload_result = SERVICE_FAILURE_RESOURCES;
service_enter_running(s, SERVICE_SUCCESS);
}
static void service_enter_reload_mounting(Service *s) {
int r;
assert(s);
usec_t ts = now(CLOCK_MONOTONIC);
r = service_arm_timer(s, /* relative= */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
s->reload_result = SERVICE_FAILURE_RESOURCES;
service_enter_running(s, SERVICE_SUCCESS);
return;
}
s->reload_begin_usec = ts;
service_enter_refresh_extensions(s);
service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
static void service_run_next_control(Service *s) {
@ -2905,13 +2938,15 @@ static void service_run_next_control(Service *s) {
s->control_command = s->control_command->command_next;
service_unwatch_control_pid(s);
if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
if (IN_SET(s->state,
SERVICE_CONDITION,
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_POST))
timeout = s->timeout_start_usec;
else
timeout = s->timeout_stop_usec;
pidref_done(&s->control_pid);
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag = */ 0),
@ -2924,10 +2959,9 @@ static void service_run_next_control(Service *s) {
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
else if (s->state == SERVICE_STOP_POST)
service_enter_dead(s, SERVICE_FAILURE_RESOURCES, /* allow_restart= */ true);
else if (s->state == SERVICE_RELOAD) {
s->reload_result = SERVICE_FAILURE_RESOURCES;
service_enter_running(s, SERVICE_SUCCESS);
} else
else if (IN_SET(s->state, SERVICE_RELOAD, SERVICE_RELOAD_POST))
service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
else
service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
}
}
@ -2979,8 +3013,6 @@ static int service_start(Unit *u) {
if (r < 0)
return r;
s->result = SERVICE_SUCCESS;
s->reload_result = SERVICE_SUCCESS;
s->main_pid_known = false;
s->main_pid_alien = false;
s->forbid_restart = false;
@ -2989,13 +3021,17 @@ static int service_start(Unit *u) {
if (s->state != SERVICE_AUTO_RESTART_QUEUED)
s->n_restarts = 0;
s->result = SERVICE_SUCCESS;
s->reload_result = SERVICE_SUCCESS;
s->reload_begin_usec = USEC_INFINITY;
s->status_text = mfree(s->status_text);
s->status_errno = 0;
s->status_bus_error = mfree(s->status_bus_error);
s->status_varlink_error = mfree(s->status_varlink_error);
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
s->notify_state = NOTIFY_UNKNOWN;
s->notify_state = _NOTIFY_STATE_INVALID;
s->watchdog_original_usec = s->watchdog_usec;
s->watchdog_override_enable = false;
@ -3040,6 +3076,20 @@ static void service_live_mount_finish(Service *s, ServiceResult f, const char *e
s->mount_request = sd_bus_message_unref(s->mount_request);
}
static void service_reload_finish(Service *s, ServiceResult f) {
assert(s);
s->reload_result = f;
s->reload_begin_usec = USEC_INFINITY;
/* If notify state is still in dangling NOTIFY_RELOADING, reset it so service_enter_running()
* won't get confused (see #37515) */
if (s->notify_state == NOTIFY_RELOADING)
s->notify_state = _NOTIFY_STATE_INVALID;
service_enter_running(s, SERVICE_SUCCESS);
}
static int service_stop(Unit *u) {
Service *s = ASSERT_PTR(SERVICE(u));
@ -3077,6 +3127,7 @@ static int service_stop(Unit *u) {
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
case SERVICE_RELOAD_POST:
case SERVICE_STOP_WATCHDOG:
/* If there's already something running we go directly into kill mode. */
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
@ -3108,7 +3159,9 @@ static int service_reload(Unit *u) {
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
service_enter_reload_mounting(s);
s->reload_result = SERVICE_SUCCESS;
service_enter_refresh_extensions(s);
return 1;
}
@ -3308,6 +3361,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
if (s->notify_access_override >= 0)
(void) serialize_item(f, "notify-access-override", notify_access_to_string(s->notify_access_override));
if (s->notify_state >= 0)
(void) serialize_item(f, "notify-state", notify_state_to_string(s->notify_state));
r = serialize_item_escaped(f, "status-text", s->status_text);
if (r < 0)
@ -3618,6 +3673,16 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
log_unit_debug(u, "Failed to parse notify-access-override value: %s", value);
else
s->notify_access_override = notify_access;
} else if (streq(key, "notify-state")) {
NotifyState notify_state;
notify_state = notify_state_from_string(value);
if (notify_state < 0)
log_unit_debug(u, "Failed to parse notify-state value: %s", value);
else
s->notify_state = notify_state;
} else if (streq(key, "n-restarts")) {
r = safe_atou(value, &s->n_restarts);
if (r < 0)
@ -4115,10 +4180,11 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
switch (s->state) {
case SERVICE_START_POST:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD_POST:
case SERVICE_MOUNTING:
/* If neither main nor control processes are running then the current
* state can never exit cleanly, hence immediately terminate the
@ -4234,7 +4300,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
success,
code, status);
if (!IN_SET(s->state, SERVICE_RELOAD, SERVICE_MOUNTING) && s->result == SERVICE_SUCCESS)
if (!IN_SET(s->state, SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_MOUNTING) &&
s->result == SERVICE_SUCCESS)
s->result = f;
if (s->control_command &&
@ -4321,28 +4388,28 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
service_enter_running(s, SERVICE_SUCCESS);
break;
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
case SERVICE_REFRESH_EXTENSIONS:
if (f == SERVICE_SUCCESS)
/* Remounting extensions asynchronously done, proceed to reload */
service_enter_reload(s);
else
service_reload_finish(s, f);
break;
case SERVICE_RELOAD:
if (f != SERVICE_SUCCESS) {
service_reload_finish(s, f);
break;
}
if (service_load_pid_file(s, true) < 0)
service_search_main_pid(s);
s->reload_result = f;
/* If the last notification we received from the service process indicates
* we are still reloading, then don't leave reloading state just yet, just
* transition into SERVICE_RELOAD_NOTIFY, to wait for the READY=1 coming,
* too. */
if (s->notify_state == NOTIFY_RELOADING)
service_set_state(s, SERVICE_RELOAD_NOTIFY);
else
service_enter_running(s, SERVICE_SUCCESS);
service_enter_reload_signal(s);
break;
case SERVICE_REFRESH_EXTENSIONS:
/* Remounting extensions asynchronously done, proceed to signal */
service_enter_reload_signal_exec(s);
case SERVICE_RELOAD_POST:
service_reload_finish(s, f);
break;
case SERVICE_MOUNTING:
@ -4440,14 +4507,14 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
service_enter_stop(s, SERVICE_FAILURE_TIMEOUT);
break;
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_RELOAD_POST:
log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process.");
service_kill_control_process(s);
s->reload_result = SERVICE_FAILURE_TIMEOUT;
service_enter_running(s, SERVICE_SUCCESS);
service_reload_finish(s, SERVICE_FAILURE_TIMEOUT);
break;
case SERVICE_MOUNTING:
@ -4747,6 +4814,90 @@ finish:
return 1;
}
static void service_notify_message_process_state(Service *s, char * const *tags) {
usec_t monotonic_usec = USEC_INFINITY;
int r;
assert(s);
const char *e = strv_find_startswith(tags, "MONOTONIC_USEC=");
if (e) {
r = safe_atou64(e, &monotonic_usec);
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to parse MONOTONIC_USEC= field in notification message, ignoring: %s", e);
}
/* Interpret READY=/STOPPING=/RELOADING=. STOPPING= wins over the others, and READY= over RELOADING= */
if (strv_contains(tags, "STOPPING=1")) {
s->notify_state = NOTIFY_STOPPING;
if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS))
service_enter_stop_by_notify(s);
return;
}
/* Disallow resurrecting a dying service */
if (s->notify_state == NOTIFY_STOPPING)
return;
if (strv_contains(tags, "READY=1")) {
if (s->notify_state == NOTIFY_RELOADING)
s->notify_state = NOTIFY_RELOAD_READY;
else
s->notify_state = NOTIFY_READY;
/* Combined RELOADING=1 and READY=1? Then this is indication that the service started and
* immediately finished reloading. */
if (strv_contains(tags, "RELOADING=1")) {
if (s->state == SERVICE_RELOAD_SIGNAL &&
monotonic_usec != USEC_INFINITY &&
monotonic_usec >= s->reload_begin_usec)
/* Valid Type=notify-reload protocol? Then we're all good. */
service_enter_reload_post(s);
else if (s->state == SERVICE_RUNNING) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
/* Propagate a reload explicitly for plain RELOADING=1 (semantically equivalent to
* service_enter_reload_by_notify() call in below) */
r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error);
if (r < 0)
log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s",
bus_error_message(&error, r));
}
}
/* Type=notify(-reload) services inform us about completed initialization with READY=1 */
if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
s->state == SERVICE_START)
service_enter_start_post(s);
/* Sending READY=1 while we are reloading informs us that the reloading is complete. */
if (s->state == SERVICE_RELOAD_NOTIFY)
service_enter_reload_post(s);
} else if (strv_contains(tags, "RELOADING=1")) {
s->notify_state = NOTIFY_RELOADING;
/* Sending RELOADING=1 after we send SIGHUP to request a reload will transition
* things to "reload-notify" state, where we'll wait for READY=1 to let us know the
* reload is done. Note that we insist on a timestamp being sent along here, so that
* we know for sure this is a reload cycle initiated *after* we sent the signal */
if (s->state == SERVICE_RELOAD_SIGNAL &&
monotonic_usec != USEC_INFINITY &&
monotonic_usec >= s->reload_begin_usec)
/* Note, we don't call service_enter_reload_by_notify() here, because we
* don't need reload propagation nor do we want to restart the timeout. */
service_set_state(s, SERVICE_RELOAD_NOTIFY);
if (s->state == SERVICE_RUNNING)
service_enter_reload_by_notify(s);
}
}
static void service_notify_message(
Unit *u,
PidRef *pidref,
@ -4768,7 +4919,6 @@ static void service_notify_message(
log_unit_debug(u, "Got notification message from PID "PID_FMT": %s", pidref->pid, empty_to_na(cc));
}
usec_t monotonic_usec = USEC_INFINITY;
bool notify_dbus = false;
const char *e;
@ -4778,7 +4928,7 @@ static void service_notify_message(
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
(!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) {
@ -4803,79 +4953,7 @@ static void service_notify_message(
}
}
/* Parse MONOTONIC_USEC= */
e = strv_find_startswith(tags, "MONOTONIC_USEC=");
if (e) {
r = safe_atou64(e, &monotonic_usec);
if (r < 0)
log_unit_warning_errno(u, r, "Failed to parse MONOTONIC_USEC= field in notification message, ignoring: %s", e);
}
/* Interpret READY=/STOPPING=/RELOADING=. STOPPING= wins over the others, and READY= over RELOADING= */
if (strv_contains(tags, "STOPPING=1")) {
s->notify_state = NOTIFY_STOPPING;
if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS))
service_enter_stop_by_notify(s);
notify_dbus = true;
} else if (strv_contains(tags, "READY=1")) {
s->notify_state = NOTIFY_READY;
/* Combined RELOADING=1 and READY=1? Then this is indication that the service started and
* immediately finished reloading. */
if (strv_contains(tags, "RELOADING=1")) {
if (s->state == SERVICE_RELOAD_SIGNAL &&
monotonic_usec != USEC_INFINITY &&
monotonic_usec >= s->reload_begin_usec)
/* Valid Type=notify-reload protocol? Then we're all good. */
service_enter_running(s, SERVICE_SUCCESS);
else if (s->state == SERVICE_RUNNING) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
/* Propagate a reload explicitly for plain RELOADING=1 (semantically equivalent to
* service_enter_reload_mounting() call in below) */
r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error);
if (r < 0)
log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s",
bus_error_message(&error, r));
}
}
/* Type=notify(-reload) services inform us about completed initialization with READY=1 */
if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
s->state == SERVICE_START)
service_enter_start_post(s);
/* Sending READY=1 while we are reloading informs us that the reloading is complete. */
if (s->state == SERVICE_RELOAD_NOTIFY)
service_enter_running(s, SERVICE_SUCCESS);
notify_dbus = true;
} else if (strv_contains(tags, "RELOADING=1")) {
s->notify_state = NOTIFY_RELOADING;
/* Sending RELOADING=1 after we send SIGHUP to request a reload will transition
* things to "reload-notify" state, where we'll wait for READY=1 to let us know the
* reload is done. Note that we insist on a timestamp being sent along here, so that
* we know for sure this is a reload cycle initiated *after* we sent the signal */
if (s->state == SERVICE_RELOAD_SIGNAL &&
monotonic_usec != USEC_INFINITY &&
monotonic_usec >= s->reload_begin_usec)
/* Note, we don't call service_enter_reload_by_notify() here, because we
* don't need reload propagation nor do we want to restart the timeout. */
service_set_state(s, SERVICE_RELOAD_NOTIFY);
if (s->state == SERVICE_RUNNING)
service_enter_reload_by_notify(s);
notify_dbus = true;
}
service_notify_message_process_state(s, tags);
/* Interpret STATUS= */
e = strv_find_startswith(tags, "STATUS=");
@ -5118,10 +5196,11 @@ static bool pick_up_pid_from_bus_name(Service *s) {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD_POST,
SERVICE_MOUNTING);
}
@ -5303,10 +5382,11 @@ static bool service_needs_console(Unit *u) {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP,
SERVICE_STOP_WATCHDOG,
@ -5744,6 +5824,7 @@ static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] =
[SERVICE_EXEC_START] = "ExecStart",
[SERVICE_EXEC_START_POST] = "ExecStartPost",
[SERVICE_EXEC_RELOAD] = "ExecReload",
[SERVICE_EXEC_RELOAD_POST] = "ExecReloadPost",
[SERVICE_EXEC_STOP] = "ExecStop",
[SERVICE_EXEC_STOP_POST] = "ExecStopPost",
};
@ -5756,6 +5837,7 @@ static const char* const service_exec_ex_command_table[_SERVICE_EXEC_COMMAND_MAX
[SERVICE_EXEC_START] = "ExecStartEx",
[SERVICE_EXEC_START_POST] = "ExecStartPostEx",
[SERVICE_EXEC_RELOAD] = "ExecReloadEx",
[SERVICE_EXEC_RELOAD_POST] = "ExecReloadPostEx",
[SERVICE_EXEC_STOP] = "ExecStopEx",
[SERVICE_EXEC_STOP_POST] = "ExecStopPostEx",
};
@ -5763,9 +5845,9 @@ static const char* const service_exec_ex_command_table[_SERVICE_EXEC_COMMAND_MAX
DEFINE_STRING_TABLE_LOOKUP(service_exec_ex_command, ServiceExecCommand);
static const char* const notify_state_table[_NOTIFY_STATE_MAX] = {
[NOTIFY_UNKNOWN] = "unknown",
[NOTIFY_READY] = "ready",
[NOTIFY_RELOADING] = "reloading",
[NOTIFY_RELOAD_READY] = "reload-ready",
[NOTIFY_STOPPING] = "stopping",
};

View File

@ -46,6 +46,7 @@ typedef enum ServiceExecCommand {
SERVICE_EXEC_START,
SERVICE_EXEC_START_POST,
SERVICE_EXEC_RELOAD,
SERVICE_EXEC_RELOAD_POST,
SERVICE_EXEC_STOP,
SERVICE_EXEC_STOP_POST,
_SERVICE_EXEC_COMMAND_MAX,
@ -53,9 +54,9 @@ typedef enum ServiceExecCommand {
} ServiceExecCommand;
typedef enum NotifyState {
NOTIFY_UNKNOWN,
NOTIFY_READY,
NOTIFY_RELOADING,
NOTIFY_RELOAD_READY,
NOTIFY_STOPPING,
_NOTIFY_STATE_MAX,
_NOTIFY_STATE_INVALID = -EINVAL,
@ -298,7 +299,5 @@ ServiceTimeoutFailureMode service_timeout_failure_mode_from_string(const char *s
DEFINE_CAST(SERVICE, Service);
#define STATUS_TEXT_MAX (16U*1024U)
/* Only exported for unit tests */
int service_deserialize_exec_command(Unit *u, const char *key, const char *value);

View File

@ -2148,8 +2148,6 @@ static void socket_enter_stop_post(Socket *s, SocketResult f) {
s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST];
if (s->control_command) {
pidref_done(&s->control_pid);
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'stop-post' task: %m");
@ -2226,8 +2224,6 @@ static void socket_enter_stop_pre(Socket *s, SocketResult f) {
s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE];
if (s->control_command) {
pidref_done(&s->control_pid);
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'stop-pre' task: %m");
@ -2289,8 +2285,6 @@ static void socket_enter_start_post(Socket *s) {
s->control_command = s->exec_command[SOCKET_EXEC_START_POST];
if (s->control_command) {
pidref_done(&s->control_pid);
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start-post' task: %m");
@ -2363,8 +2357,6 @@ static void socket_enter_start_pre(Socket *s) {
s->control_command = s->exec_command[SOCKET_EXEC_START_PRE];
if (s->control_command) {
pidref_done(&s->control_pid);
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start-pre' task: %m");
@ -2612,8 +2604,6 @@ static void socket_run_next(Socket *s) {
s->control_command = s->control_command->command_next;
pidref_done(&s->control_pid);
r = socket_spawn(s, s->control_command, &s->control_pid);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to spawn next task: %m");

View File

@ -350,11 +350,12 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
#define CASE_F_18(X, ...) case X: CASE_F_17( __VA_ARGS__)
#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__)
#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
#define CASE_F_21(X, ...) case X: CASE_F_20( __VA_ARGS__)
#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,NAME,...) NAME
#define FOR_EACH_MAKE_CASE(...) \
GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
GET_CASE_F(__VA_ARGS__,CASE_F_21,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12, \
CASE_F_11,CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
(__VA_ARGS__)
#define IN_SET(x, first, ...) \
@ -363,7 +364,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
/* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \
* here to widen the type of x if it is a bit-field as this would otherwise be illegal. */ \
static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \
assert_cc(ELEMENTSOF(__assert_in_set) <= 21); \
switch (x) { \
FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \
_found = true; \

View File

@ -3453,10 +3453,8 @@ _public_ int sd_json_parse_file_at(
if (f)
r = read_full_stream(f, &text, NULL);
else if (path)
r = read_full_file_full(dir_fd, path, UINT64_MAX, SIZE_MAX, 0, NULL, &text, NULL);
else
return -EINVAL;
r = read_full_file_full(dir_fd, path, UINT64_MAX, SIZE_MAX, 0, NULL, &text, NULL);
if (r < 0)
return r;

View File

@ -2649,6 +2649,8 @@ static const BusProperty service_properties[] = {
{ "ExecStartPostEx", bus_append_exec_command }, /* compat */
{ "ExecReload", bus_append_exec_command },
{ "ExecReloadEx", bus_append_exec_command }, /* compat */
{ "ExecReloadPost", bus_append_exec_command },
{ "ExecReloadPostEx", bus_append_exec_command }, /* compat */
{ "ExecStop", bus_append_exec_command },
{ "ExecStopEx", bus_append_exec_command }, /* compat */
{ "ExecStopPost", bus_append_exec_command },

View File

@ -619,6 +619,7 @@ static int image_make(
static int pick_image_search_path(
RuntimeScope scope,
ImageClass class,
const char *root,
char ***ret) {
int r;
@ -635,11 +636,11 @@ static int pick_image_search_path(
if (scope < 0) {
_cleanup_strv_free_ char **a = NULL, **b = NULL;
r = pick_image_search_path(RUNTIME_SCOPE_USER, class, &a);
r = pick_image_search_path(RUNTIME_SCOPE_USER, class, root, &a);
if (r < 0)
return r;
r = pick_image_search_path(RUNTIME_SCOPE_SYSTEM, class, &b);
r = pick_image_search_path(RUNTIME_SCOPE_SYSTEM, class, root, &b);
if (r < 0)
return r;
@ -655,8 +656,15 @@ static int pick_image_search_path(
case RUNTIME_SCOPE_SYSTEM: {
const char *ns;
bool is_initrd;
r = chase_and_access("/etc/initrd-release", root, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
if (r < 0 && r != -ENOENT)
return r;
is_initrd = r >= 0;
/* Use the initrd search path if there is one, otherwise use the common one */
ns = in_initrd() && image_search_path_initrd[class] ?
ns = is_initrd && image_search_path_initrd[class] ?
image_search_path_initrd[class] :
image_search_path[class];
if (!ns)
@ -763,7 +771,7 @@ int image_find(RuntimeScope scope,
return -ENOMEM;
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, &search);
r = pick_image_search_path(scope, class, root, &search);
if (r < 0)
return r;
@ -954,7 +962,7 @@ int image_discover(
assert(images);
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, &search);
r = pick_image_search_path(scope, class, root, &search);
if (r < 0)
return r;
@ -2101,7 +2109,7 @@ bool image_in_search_path(
assert(image);
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, &search);
r = pick_image_search_path(scope, class, root, &search);
if (r < 0)
return r;

View File

@ -1707,9 +1707,11 @@ static const ImagePolicy *pick_image_policy(const Image *img) {
/* If located in /.extra/ in the initrd, then it was placed there by systemd-stub, and was
* picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
* other directories we assume the appropriate level of trust was already established already. */
* other directories we assume the appropriate level of trust was already established.)
* With --root= we default to the regular policy, though. (To change that, the check would need
* to prepend (or cut away) arg_root.) */
if (in_initrd()) {
if (in_initrd() && !arg_root) {
if (path_startswith(img->path, "/.extra/sysext/"))
return &image_policy_sysext_strict;
if (path_startswith(img->path, "/.extra/global_sysext/"))
@ -1905,13 +1907,19 @@ static int merge_subprocess(
if (force)
log_debug("Force mode enabled, skipping version validation.");
else {
bool is_initrd;
r = chase_and_access("/etc/initrd-release", arg_root, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to check for /etc/initrd-release: %m");
is_initrd = r >= 0;
r = extension_release_validate(
img->name,
host_os_release_id,
host_os_release_id_like,
host_os_release_version_id,
host_os_release_api_level,
in_initrd() ? "initrd" : "system",
is_initrd ? "initrd" : "system",
image_extension_release(img, image_class),
image_class);
if (r < 0)

View File

@ -95,8 +95,28 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) {
if (r < 0)
return r;
while (s <= n)
s += USEC_PER_DAY;
if (s <= n) {
/* The specified time is today, but in the past. We need to schedule it for tomorrow
* at the same time. Adding USEC_PER_DAY would be wrong across DST changes, so just
* let mktime() normalise it. */
int requested_hour = tm.tm_hour;
int requested_min = tm.tm_min;
tm.tm_mday++;
tm.tm_isdst = -1;
r = mktime_or_timegm_usec(&tm, /* utc= */ false, &s);
if (r < 0)
return r;
if (tm.tm_hour != requested_hour || tm.tm_min != requested_min) {
log_warning("Requested shutdown time %02d:%02d does not exist. "
"Rescheduling to %02d:%02d.",
requested_hour,
requested_min,
tm.tm_hour,
tm.tm_min);
}
}
*ret = s;
}

View File

@ -2284,6 +2284,8 @@ static int show_one(
{ "ExecStartPostEx", "a(sasasttttuii)", map_exec, 0 },
{ "ExecReload", "a(sasbttttuii)", map_exec, 0 },
{ "ExecReloadEx", "a(sasasttttuii)", map_exec, 0 },
{ "ExecReloadPost", "a(sasbttttuii)", map_exec, 0 },
{ "ExecReloadPostEx", "a(sasasttttuii)", map_exec, 0 },
{ "ExecStopPre", "a(sasbttttuii)", map_exec, 0 },
{ "ExecStop", "a(sasbttttuii)", map_exec, 0 },
{ "ExecStopEx", "a(sasasttttuii)", map_exec, 0 },

View File

@ -221,10 +221,8 @@ static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete com
}
static int job_parse_child_output(int _fd, sd_json_variant **ret) {
_cleanup_close_ int fd = ASSERT_FD(_fd); /* Take ownership of the passed fd */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
/* Take ownership of the passed fd */
_cleanup_close_ int fd = _fd;
_cleanup_fclose_ FILE *f = NULL;
struct stat st;
int r;
@ -240,16 +238,10 @@ static int job_parse_child_output(int _fd, sd_json_variant **ret) {
return 0;
}
if (lseek(fd, SEEK_SET, 0) == (off_t) -1)
return log_debug_errno(errno, "Failed to seek to beginning of memfd: %m");
f = take_fdopen(&fd, "r");
if (!f)
return log_debug_errno(errno, "Failed to reopen memfd: %m");
r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL);
r = sd_json_parse_file_at(/* f = */ NULL, fd, /* path = */ NULL, /* flags = */ 0,
&v, /* reterr_line = */ NULL, /* reterr_column = */ NULL);
if (r < 0)
return log_debug_errno(r, "Failed to parse JSON: %m");
return log_debug_errno(r, "Failed to parse child output as JSON: %m");
*ret = TAKE_PTR(v);
return 0;

View File

@ -0,0 +1,4 @@
[Service]
Type=notify-reload
TimeoutStartSec=40
ExecStart=/usr/lib/systemd/tests/testdata/TEST-80-NOTIFYACCESS.units/reload-timeout.sh

View File

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
COUNTER=0
sync_in() {
read -r x < /tmp/syncfifo2
test "$x" = "$1"
}
wait_for_signal() {
sleep infinity &
wait "$!" || :
}
sighup_handler() {
echo "hup$(( ++COUNTER ))" > /tmp/syncfifo1
}
trap sighup_handler SIGHUP
export SYSTEMD_LOG_LEVEL=debug
systemd-notify --ready
wait_for_signal
systemd-notify --reloading
wait_for_signal
systemd-notify --reloading
sync_in ready
systemd-notify --ready
wait_for_signal
systemd-notify --reloading --ready
exec sleep infinity

View File

@ -80,14 +80,20 @@ systemd-dissect --umount "$IMAGE_DIR/mount2"
# Ensure the deferred close flag is set up correctly and we don't leak verity devices
# when sharing is disabled
set +o pipefail
# The devices are named 'loopXYZ-verity' when sharing is disabled
n_before=$(dmsetup ls | grep loop | grep -c verity || true)
SYSTEMD_VERITY_SHARING=0 systemd-dissect --mount "$MINIMAL_IMAGE.raw" "$IMAGE_DIR/mount"
test $((n_before + 1)) -eq "$(dmsetup ls | grep loop | grep -c verity || true)"
d=""
for f in /dev/mapper/*; do
if [[ "$(basename "$f")" =~ ^loop.*-verity ]] && veritysetup status "$(basename "$f")" | grep -q "$MINIMAL_IMAGE.raw"; then
d="$f"
break
fi
done
test -n "$d"
dmsetup ls | grep -q "$(basename "$d")"
umount -R "$IMAGE_DIR/mount"
test "$n_before" -eq "$(dmsetup ls | grep loop | grep -c verity || true)"
set -o pipefail
timeout 60 bash -c "while test -e $d; do sleep 0.1; done"
( ! dmsetup ls | grep -q "$(basename "$d")")
# Test BindLogSockets=
systemd-run --wait -p RootImage="$MINIMAL_IMAGE.raw" mountpoint /run/systemd/journal/socket

View File

@ -1130,10 +1130,13 @@ extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
fake_root=${roots_dir:+"$roots_dir/mutable-directory-with-invalid-permissions"}
hierarchy=/opt
extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr"
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
prepare_extension_mutable_dir "$extension_data_dir"
prepend_trap "rm -rf ${extension_data_dir@Q}"
prepend_trap "rm -rf ${extension_data_dir_usr@Q}"
prepare_hierarchy "$fake_root" "$hierarchy"
old_mode=$(stat --format '%#a' "$fake_root$hierarchy")
@ -1144,6 +1147,29 @@ chmod 0700 "$extension_data_dir"
(! run_systemd_sysext "$fake_root" --mutable=yes merge)
)
( init_trap
: "Check if merging fails in case of --root= being an initrd but the extension is not for it"
# Since this is really about whether --root= gets prepended for the /etc/initrd-release check,
# this also tests the more interesting reverse case that we are in the initrd and prepare
# the mounts for the final system with --root=/sysroot
fake_root=${roots_dir:+"$roots_dir/initrd-env-with-non-initrd-extension"}
hierarchy=/opt
prepare_root "$fake_root" "$hierarchy"
prepare_extension_image "$fake_root" "$hierarchy"
mkdir -p "${fake_root}/etc"
touch "${fake_root}/etc/initrd-release"
prepare_read_only_hierarchy "$fake_root" "$hierarchy"
# Should be a no-op, thus we also don't run unmerge afterwards (otherwise the test is broken)
run_systemd_sysext "$fake_root" merge
if run_systemd_sysext "$fake_root" status --json=pretty | jq -r '.[].extensions' | grep -v '^none$' ; then
echo >&2 "Extension got loaded for an initrd structure passed as --root= while the extension does not declare itself compatible with the initrd scope"
exit 1
fi
rm "${fake_root}/etc/initrd-release"
)
} # End of run_sysext_tests

View File

@ -868,8 +868,8 @@ EOF
for ((i = 0; i < ${#devices[@]}; i++)); do
# Intentionally use weaker cipher-related settings, since we don't care
# about security here as it's a throwaway LUKS partition
udevadm lock --timeout=30 --device="${devices[$i]}" \
cryptsetup luksFormat -q \
SYSTEMD_LOG_LEVEL=debug udevadm lock --timeout=30 --device="${devices[$i]}" \
cryptsetup luksFormat -q --debug \
--use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \
--uuid "deadbeef-dead-dead-beef-11111111111$i" --label "encdisk$i" "${devices[$i]}" /etc/btrfs_keyfile
udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/deadbeef-dead-dead-beef-11111111111$i" "/dev/disk/by-label/encdisk$i"

View File

@ -81,6 +81,11 @@ systemd-run --wait --pipe --user --machine=testuser@ \
systemd-run --wait --pipe --user --machine=testuser@ \
bash -xec '[[ "$PWD" == /home/testuser && -n "$INVOCATION_ID" ]]'
# https://github.com/systemd/systemd/issues/39038
systemd-run --wait --machine=testuser@ --user -p User=testuser true
systemd-run --wait --machine=testuser@ --user -p Group=testuser true
(! systemd-run --wait --machine=testuser@ --user -p Group=testuser -p SupplementaryGroups=root true)
# PrivateTmp=yes implies PrivateUsers=yes for user manager, so skip this if we
# don't have unprivileged user namespaces.
if [[ "$(sysctl -ne kernel.apparmor_restrict_unprivileged_userns)" -ne 1 ]]; then

View File

@ -62,6 +62,33 @@ assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none"
systemctl stop notify.service
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
# Timeout of READY=1 for Type=notify-reload services (issue #37515)
systemctl start reload-timeout.service
systemctl reload --no-block reload-timeout.service
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
sync_in hup1
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-notify" ]]; do sleep .5; done'
timeout 80 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 5; done'
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "timeout"
systemctl reload --no-block reload-timeout.service
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-signal" ]]; do sleep .5; done'
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
sync_in hup2
timeout 10 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "reload-notify" ]]; do sleep .5; done'
sync_out ready
timeout 40 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 1; done'
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
systemctl reload --no-block reload-timeout.service
sync_in hup3
timeout 40 bash -c 'until [[ $(systemctl show reload-timeout.service -P SubState) == "running" ]]; do sleep 1; done'
assert_eq "$(systemctl show reload-timeout.service -P ReloadResult)" "success"
systemctl stop reload-timeout.service
rm /tmp/syncfifo1 /tmp/syncfifo2
# Explicitly test busctl's BUSERROR= reporting and systemctl status should show it