1
0
mirror of https://github.com/systemd/systemd synced 2026-03-11 07:34:58 +01:00

Compare commits

..

27 Commits

Author SHA1 Message Date
Daan De Meyer
71de0a22ff
core/service: support credentials refreshing on reload (#40093)
Closes https://github.com/systemd/systemd/issues/21099
Closes https://github.com/systemd/systemd/issues/35227
2026-02-13 10:21:44 +01:00
Yaping Li
fffba61b78 metrics: fixing casing 2026-02-13 16:55:25 +09:00
Yu Watanabe
559dabbbde
Two portabled fixes (#40664) 2026-02-13 16:53:29 +09:00
Luca Boccassi
421821d1e3 repart: set r/o GPT flag on verity sig partition too
The default image policy for the verity sig partition expects
the r/o flag to be set, but repart so far did not add it by
default if unset, like it does for the verity partition
2026-02-13 16:50:45 +09:00
Baurzhan Muftakhidinov
2cef4057f5 po: Added translation using Weblate (Kazakh)
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
2026-02-13 15:49:34 +09:00
Daan De Meyer
48f0f1fd81 TEST-72-SYSUPDATE: Use some very long partition names
To catch issues like https://github.com/systemd/systemd/issues/40658.
The commit that fixes that issue can make the name even longer to ensure
we don't regress again in this regard.
2026-02-13 15:46:52 +09:00
Daan De Meyer
7b9600a6b2 sysupdate: Compute temporary paths before vacuuming
We don't want to vacuum anything if we're just going to fail just
afterwards because a GPT partition label exceeds the maximum size
so let's compute the temporary paths for all transfers before we do
any vacuuming or acquiring.
2026-02-13 15:46:52 +09:00
Han Sol Jin
09b9466e8c Revert "hwdb: fix arrow keys on HP Elite Dragonfly G3"
Prior to this commit, the behaviour looked like this:

| Keypress | Result       |
| -------- | ------------ |
| Up       | KEY_PAGEUP   |
| Down     | KEY_PAGEDOWN |
| Left     | KEY_LEFT     |
| Right    | KEY_RIGHT    |
| Fn+Up    | KEY_UP       |
| Fn+Down  | KEY_DOWN     |
| Fn+Left  | KEY_HOME     |
| Fn+Right | KEY_END      |

This commit would fix it so that PGUP/PGDN would also require the Fn
key so that the arrow keys behave identically depending on whether Fn
was pressed.

Presumably after a BIOS update, HP seems to have fixed the order. This
now means this commit is now behaving exactly as the table above.

Revert the commit to restore the intended behaviour:

| Keypress | Result       |
| -------- | ------------ |
| Up       | KEY_UP       |
| Down     | KEY_DOWN     |
| Left     | KEY_LEFT     |
| Right    | KEY_RIGHT    |
| Fn+Up    | KEY_PAGEUP   |
| Fn+Down  | KEY_PAGEDOWN |
| Fn+Left  | KEY_HOME     |
| Fn+Right | KEY_END      |

This reverts commit 4fd7c712dcba3c4ed7183ba327d0b88d9b0be9bb.

Signed-off-by: Han Sol Jin <hansol@hansol.ca>
2026-02-13 14:21:16 +09:00
Luca Boccassi
03f6a8b9d9 portable: do not apply extension image policy if not attaching image
Image policy is only for images, so skip for other types

Follow-up for d05961549277f15399a45cdf42d4d5f3e5ed8097
2026-02-12 23:20:47 +00:00
Luca Boccassi
d07aa23c87 portable: fix --force flag combination with directory extension
The check for image type uses the wrong variable, so it's applied
when it shouldn't.

Follow-up for 06768b90a32ac0d36252ebc5f426ad471bf29fce
2026-02-12 23:20:47 +00:00
Mike Yuan
7d551b00e3
TEST-54-CREDS: add test cases for credential refreshing 2026-02-10 23:21:11 +01:00
Mike Yuan
d83f9cc112
test-load-fragment: add unit test for RefreshOnReload= parser 2026-02-10 21:54:13 +01:00
Mike Yuan
24f458da81
man: document RefreshOnReload= 2026-02-10 21:54:13 +01:00
Mike Yuan
21292677f6
core/service: if RefreshOnReload= is explicitly enabled, allow reload even without exec/notify-reload
This was originally brought up by @poettering. If the process
loads stuff on demand and flushes them out after each use,
or actively monitors file changes, they can be reloaded
by merely refreshing the resources.
2026-02-10 21:54:13 +01:00
Mike Yuan
f385b3f3dd
core/service: pass credentials to ExecReload*= if refreshed
Closes #35227
2026-02-10 21:54:13 +01:00
Mike Yuan
fff07be411
core/service: support credential refreshing on reload
Closes #21099
2026-02-10 21:54:12 +01:00
Mike Yuan
cb5c6dc599
macro: add 22nd case for IN_SET
Dejavu moment ;)
2026-02-10 21:54:12 +01:00
Mike Yuan
a43bc1aed8
core/service: introduce RefreshOnReload= setting
This allows controlling resources to be refreshed before performing
reload, with one extra benefit that in the future we can permit
"seemless reload"s, i.e. no active signaling is done to the main process
after refreshing get updated. This could come in handy for programs
that loads stuff on demand or watches changes via inotify.
2026-02-10 21:54:12 +01:00
Mike Yuan
65ea71ead7
core/exec-credential: introduce unit_refresh_credentials() 2026-02-10 21:54:12 +01:00
Mike Yuan
8f89ed439d
process-util: teach namespace_fork() to optionally use namespace_enter_delegated() 2026-02-10 21:54:12 +01:00
Mike Yuan
1a1c5ab758
namespace-util: introduce namespace_enter_delegated()
Typically when entering a namespace the userns is handled last,
because we assume our process is more privileged than the userns.
However, that assumption no longer holds for user managers, which
have no privilege over initial userns and all other namespaces
are actually owned by the userns unshared first (in executor).
Hence, let's add another flavor namespace_enter_delegated() to
accommodate that use case.
2026-02-10 21:54:11 +01:00
Mike Yuan
bae58ceff7
core/exec-credential: only pass needed bits of ExecParameters down in exec_setup_credentials()
No functional change, preparation for credential reloading support,
where we'd operate on Unit rather than ExecParameters.
2026-02-10 21:54:11 +01:00
Mike Yuan
7c358751a4
core/exec-credential: avoid duplicate call to hashmap_contains() 2026-02-10 21:54:11 +01:00
Mike Yuan
482fb9ae40
core/exec-credential: stop removing empty credentials dir
Starting from cfbf7538d87023840c5574fa5b0452e5b0f42149 we'd always
install the credentials dir regardless of whether it's empty,
with the correct permissions. Hence the problem stated in the comment
should no longer be a concern. Moreover, this ensures later in
setup_namespace() the mountpoint would be in-place. This is important
for credential reloading as it saves the trouble of remounting
the upper tmpfs as rw again and create the mountpoint.
2026-02-10 21:54:11 +01:00
Mike Yuan
de65956bdb
core/exec-credential: treat credentials dir as populated if it's mounted
We should only fall back to the dir_is_empty() check if
it's a plain dir, where we can't reasonably differentiate
populated yet empty vs not set up. Otherwise let's stick
to the existing mount if we're told to reuse it.

Yes, this is a minor compat break, but with the to-be-introduced
credential reloading support it should fulfill the goal of
keeping the passed set of credentials stable better, while
still allowing things to be refreshed when requested.
2026-02-10 21:54:11 +01:00
Mike Yuan
f9e0a62392
sd-daemon: escape special characters in notification logging text
Follow-up for 21eb636aaad28707bd371bdfd721dea66277e1f5

Our sd_notify() protocol uses newline as separator, which
is not suitable for direct logging.
2026-02-10 21:53:39 +01:00
Mike Yuan
14af94428a
shared-forward: sort definitions 2026-02-10 21:53:39 +01:00
43 changed files with 2394 additions and 227 deletions

1
TODO
View File

@ -124,7 +124,6 @@ Features:
* report: * report:
- should the list of metrics use JSON-SEQ? or maybe be wrapped in a json - should the list of metrics use JSON-SEQ? or maybe be wrapped in a json
array (the latter might be necessary, once we sign the combination) array (the latter might be necessary, once we sign the combination)
- "io.systemd.Manager.unit_active_state" is a weird mix of CamelCase and snake_case
- metrics from pid1: suppress metrics form units that are inactive and have nothing to report - metrics from pid1: suppress metrics form units that are inactive and have nothing to report
- how to plug facts into this? i.e. hostname, ssh keys, and so on - how to plug facts into this? i.e. hostname, ssh keys, and so on
- switch to daan's suggested hierarchy? - switch to daan's suggested hierarchy?

View File

@ -931,13 +931,6 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHP*:pnHPEliteDragonflyG2*:pvr*
evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPEliteDragonflyG2*:pvr* evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPEliteDragonflyG2*:pvr*
KEYBOARD_KEY_08=unknown # rfkill is also reported by HP Wireless hotkeys KEYBOARD_KEY_08=unknown # rfkill is also reported by HP Wireless hotkeys
# HP Elite Dragonfly G3
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHP:pnHPEliteDragonfly13.5inchG3NotebookPC:pvr*
KEYBOARD_KEY_c9=up
KEYBOARD_KEY_d1=down
KEYBOARD_KEY_c8=pageup
KEYBOARD_KEY_d0=pagedown
# HP 430 Programmable Wireless Keypad # HP 430 Programmable Wireless Keypad
evdev:input:b0005v03F0p854Ae044C* evdev:input:b0005v03F0p854Ae044C*
KEYBOARD_KEY_700f3=macro1 KEYBOARD_KEY_700f3=macro1

View File

@ -2833,6 +2833,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly as ExtraFileDescriptorNames = ['...', ...]; readonly as ExtraFileDescriptorNames = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const") @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i ReloadSignal = ...; readonly i ReloadSignal = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as RefreshOnReload = ['...', ...];
readonly t ExecMainStartTimestamp = ...; readonly t ExecMainStartTimestamp = ...;
readonly t ExecMainStartTimestampMonotonic = ...; readonly t ExecMainStartTimestampMonotonic = ...;
readonly t ExecMainExitTimestamp = ...; readonly t ExecMainExitTimestamp = ...;
@ -3534,6 +3536,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property ReloadSignal is not documented!--> <!--property ReloadSignal is not documented!-->
<!--property RefreshOnReload is not documented!-->
<!--property ExecCondition is not documented!--> <!--property ExecCondition is not documented!-->
<!--property ExecConditionEx is not documented!--> <!--property ExecConditionEx is not documented!-->
@ -4180,6 +4184,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="ReloadSignal"/> <variablelist class="dbus-property" generated="True" extra-ref="ReloadSignal"/>
<variablelist class="dbus-property" generated="True" extra-ref="RefreshOnReload"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestamp"/> <variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestamp"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestampMonotonic"/> <variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestampMonotonic"/>
@ -12514,8 +12520,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>ManagedOOMKills</varname>, <varname>ManagedOOMKills</varname>,
<varname>ExecReloadPost</varname>, and <varname>ExecReloadPost</varname>, and
<varname>ExecReloadPostEx</varname> were added in version 259.</para> <varname>ExecReloadPostEx</varname> were added in version 259.</para>
<para><varname>BindNetworkInterface</varname>, and <para><varname>BindNetworkInterface</varname>,
<varname>MemoryTHP</varname> were added in version 260.</para> <varname>MemoryTHP</varname>, and
<varname>RefreshOnReload</varname> were added in version 260.</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
<title>Socket Unit Objects</title> <title>Socket Unit Objects</title>

View File

@ -572,14 +572,13 @@
To disable the safety check that the extension-release file name matches the image file name, the To disable the safety check that the extension-release file name matches the image file name, the
<varname>x-systemd.relax-extension-release-check</varname> mount option may be appended.</para> <varname>x-systemd.relax-extension-release-check</varname> mount option may be appended.</para>
<para>This option can be used together with a <option>notify-reload</option> service type and <para>If a service employs this option with
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
to manage configuration updates. When such a service carrying confext images is reloaded via and has <option>RefreshOnReload=extensions</option> enabled (the default), the confexts will
<command>systemctl reload foo.service</command> or equivalent D-Bus method, the confext itself will be refreshed to pick up any changes on service reload. This only applies to confext extensions.
be reloaded to pick up any changes. This only applies to confext extensions. Note that in case a Note that in case a service has this configuration enabled at first, and then it is subsequently
service has this configuration enabled at first, and then it is subsequently removed in an update removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op,
followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service and a full service restart is required instead. See
restart is required instead. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
also for details.</para> also for details.</para>
@ -630,14 +629,13 @@
or the host. See: or the host. See:
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
<para>This option can be used together with a <option>notify-reload</option> service type and <para>If a service employs this option with
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
to manage configuration updates. When such a service carrying confext directories is reloaded via and has <option>RefreshOnReload=extensions</option> enabled (the default), the confexts will
<command>systemctl reload foo.service</command> or equivalent D-Bus method, the confext itself will be refreshed to pick up any changes on service reload. This only applies to confext extensions.
be reloaded to pick up any changes. This only applies to confext extensions. Note that in case a Note that in case a service has this configuration enabled at first, and then it is subsequently
service has this configuration enabled at first, and then it is subsequently removed in an update removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op,
followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service and a full service restart is required instead. See
restart is required instead. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
also for details.</para> also for details.</para>

View File

@ -1349,6 +1349,28 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem> <xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>RefreshOnReload=</varname></term>
<listitem><para>Takes a boolean argument, or a list of resources defined in
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Possible values are <option>extensions</option> and <option>credentials</option>, separated by space.
Prepending the list with a single tilde character (<literal>~</literal>) inverts the effect.
Defaults to <option>extensions</option>. An empty assignment resets the list to default. If enabled,
the corresponding resources (<option>ExtensionImages=</option>/<option>ExtensionDirectories=</option>
for <option>extensions</option> and <option>LoadCredential=</option>/<option>ImportCredential=</option>/
<option>SetCredential=</option> (along with their <option>Encrypted</option> counterparts)
for <option>credentials</option>) will be refreshed on service reload. If <option>yes</option>,
all resources listed above that are used by the service shall be refreshed.</para>
<para>Specially, if this option is set explicitly, and the respective resources are in use,
the service may be reloaded without any actual reload mechanism (<option>ExecReload=</option>
or <option>Type=notify-reload</option>) for notifying the main process, in which case the reload
is considered complete immediately after refreshing.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</variablelist> </variablelist>
<para id='shared-unit-options'>Check <para id='shared-unit-options'>Check

View File

@ -44,3 +44,4 @@ kn
ar ar
km km
kw kw
kk

1185
po/kk.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -262,6 +262,71 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
return 0; return 0;
} }
static int namespace_enter_one_idempotent(int nsfd, NamespaceType type) {
int r;
/* Join a namespace, but only if we're not part of it already. This is important if we don't necessarily
* own the namespace in question, as kernel would unconditionally return EPERM otherwise. */
assert(nsfd >= 0);
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
r = is_our_namespace(nsfd, type);
if (r < 0)
return r;
if (r > 0)
return 0;
if (setns(nsfd, namespace_info[type].clone_flag) < 0)
return -errno;
return 1;
}
int namespace_enter_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_fd, int root_fd) {
int r;
/* Similar to namespace_enter(), but operates on a set of namespaces that are potentially owned
* by the userns ("delegated"), in which case we'll need to gain CAP_SYS_ADMIN by joining
* the userns first, and the rest later. */
assert(userns_fd >= 0);
/* Block dlopen() now, to avoid us inadvertently loading shared library from another namespace */
block_dlopen();
if (setns(userns_fd, CLONE_NEWUSER) < 0)
return -errno;
if (pidns_fd >= 0) {
r = namespace_enter_one_idempotent(pidns_fd, NAMESPACE_PID);
if (r < 0)
return r;
}
if (mntns_fd >= 0) {
r = namespace_enter_one_idempotent(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return r;
}
if (netns_fd >= 0) {
r = namespace_enter_one_idempotent(netns_fd, NAMESPACE_NET);
if (r < 0)
return r;
}
if (root_fd >= 0) {
if (fchdir(root_fd) < 0)
return -errno;
if (chroot(".") < 0)
return -errno;
}
return maybe_setgroups(/* size = */ 0, NULL);
}
int fd_is_namespace(int fd, NamespaceType type) { int fd_is_namespace(int fd, NamespaceType type) {
int r; int r;

View File

@ -47,6 +47,7 @@ int namespace_open(
int *ret_root_fd); int *ret_root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
int namespace_enter_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_fd, int root_fd);
int fd_is_namespace(int fd, NamespaceType type); int fd_is_namespace(int fd, NamespaceType type);
int is_our_namespace(int fd, NamespaceType type); int is_our_namespace(int fd, NamespaceType type);

View File

@ -1778,6 +1778,7 @@ int namespace_fork_full(
int netns_fd, int netns_fd,
int userns_fd, int userns_fd,
int root_fd, int root_fd,
bool delegated,
PidRef *ret) { PidRef *ret) {
_cleanup_(pidref_done_sigkill_wait) PidRef pidref_outer = PIDREF_NULL; _cleanup_(pidref_done_sigkill_wait) PidRef pidref_outer = PIDREF_NULL;
@ -1823,7 +1824,10 @@ int namespace_fork_full(
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd); if (delegated)
r = namespace_enter_delegated(userns_fd, pidns_fd, mntns_fd, netns_fd, root_fd);
else
r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
if (r < 0) { if (r < 0) {
log_full_errno(prio, r, "Failed to join namespace: %m"); log_full_errno(prio, r, "Failed to join namespace: %m");
report_errno_and_exit(errno_pipe_fd[1], r); report_errno_and_exit(errno_pipe_fd[1], r);

View File

@ -201,6 +201,7 @@ int namespace_fork_full(
int netns_fd, int netns_fd,
int userns_fd, int userns_fd,
int root_fd, int root_fd,
bool delegated,
PidRef *ret); PidRef *ret);
static inline int namespace_fork( static inline int namespace_fork(
@ -215,7 +216,7 @@ static inline int namespace_fork(
PidRef *ret) { PidRef *ret) {
return namespace_fork_full(outer_name, inner_name, NULL, 0, flags, return namespace_fork_full(outer_name, inner_name, NULL, 0, flags,
pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd, pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd, false,
ret); ret);
} }

View File

@ -223,6 +223,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_RUNNING] = "running", [SERVICE_RUNNING] = "running",
[SERVICE_EXITED] = "exited", [SERVICE_EXITED] = "exited",
[SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions", [SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
[SERVICE_REFRESH_CREDENTIALS] = "refresh-credentials",
[SERVICE_RELOAD] = "reload", [SERVICE_RELOAD] = "reload",
[SERVICE_RELOAD_SIGNAL] = "reload-signal", [SERVICE_RELOAD_SIGNAL] = "reload-signal",
[SERVICE_RELOAD_NOTIFY] = "reload-notify", [SERVICE_RELOAD_NOTIFY] = "reload-notify",

View File

@ -132,6 +132,7 @@ typedef enum ServiceState {
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */ SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
SERVICE_REFRESH_CREDENTIALS, /* ditto, but for credentials */
SERVICE_RELOAD, /* Reloading via ExecReload= */ SERVICE_RELOAD, /* Reloading via ExecReload= */
SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */ SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */
SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */ SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */

View File

@ -30,6 +30,7 @@
#include "signal-util.h" #include "signal-util.h"
#include "stat-util.h" #include "stat-util.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h"
#include "unit.h" #include "unit.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType);
@ -100,6 +101,29 @@ static int property_get_extra_file_descriptors(
return sd_bus_message_close_container(reply); return sd_bus_message_close_container(reply);
} }
static int property_get_refresh_on_reload(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *reterr_error) {
Service *s = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **l = NULL;
int r;
assert(bus);
assert(reply);
r = service_refresh_on_reload_to_strv(s->refresh_on_reload_flags, &l);
if (r < 0)
return r;
return sd_bus_message_append_strv(reply, l);
}
static int property_get_exit_status_set( static int property_get_exit_status_set(
sd_bus *bus, sd_bus *bus,
const char *path, const char *path,
@ -373,6 +397,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExtraFileDescriptorNames", "as", property_get_extra_file_descriptors, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ExtraFileDescriptorNames", "as", property_get_extra_file_descriptors, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RefreshOnReload", "as", property_get_refresh_on_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@ -840,6 +865,47 @@ static int bus_service_set_transient_property(
return 1; return 1;
} }
if (streq(name, "RefreshOnReload")) {
const char *t;
int invert;
r = sd_bus_message_enter_container(message, 'a', "(bs)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(bs)", &invert, &t)) > 0) {
ServiceRefreshOnReload f;
f = service_refresh_on_reload_flag_from_string(t);
if (f < 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RefreshOnReload= value: %s", t);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
if (!s->refresh_on_reload_set)
s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f;
else
SET_FLAG(s->refresh_on_reload_flags, f, !invert);
s->refresh_on_reload_set = true;
unit_write_settingf(u, flags, name, "%s=%s%s", name, invert ? "~" : "", t);
}
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags) && !s->refresh_on_reload_set) { /* empty array? */
s->refresh_on_reload_flags = 0;
s->refresh_on_reload_set = true;
unit_write_settingf(u, flags, name, "%s=no", name);
}
return 1;
}
return 0; return 0;
} }

View File

@ -16,19 +16,25 @@
#include "iovec-util.h" #include "iovec-util.h"
#include "label-util.h" #include "label-util.h"
#include "log.h" #include "log.h"
#include "manager.h"
#include "mkdir-label.h" #include "mkdir-label.h"
#include "mount-util.h" #include "mount-util.h"
#include "mountpoint-util.h" #include "mountpoint-util.h"
#include "namespace-util.h"
#include "ordered-set.h" #include "ordered-set.h"
#include "path-lookup.h" #include "path-lookup.h"
#include "path-util.h" #include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "random-util.h" #include "random-util.h"
#include "recurse-dir.h" #include "recurse-dir.h"
#include "rm-rf.h" #include "rm-rf.h"
#include "siphash24.h" #include "siphash24.h"
#include "socket-util.h"
#include "stat-util.h" #include "stat-util.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "unit.h"
#include "user-util.h" #include "user-util.h"
ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) { ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) {
@ -310,6 +316,34 @@ int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_p
return 0; return 0;
} }
typedef struct SetupCredentialsContext {
RuntimeScope scope;
const ExecContext *exec_context;
const char *unit;
const char *runtime_prefix;
const char *received_credentials_directory;
const char *received_encrypted_credentials_directory;
bool always_ipc;
uid_t uid;
gid_t gid;
} SetupCredentialsContext;
typedef struct LoadCredentialArguments {
const SetupCredentialsContext *context;
bool encrypted;
int write_dfd;
bool ownership_ok;
uint64_t left;
} LoadCredentialArguments;
typedef enum CredentialSearchPath { typedef enum CredentialSearchPath {
CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_TRUSTED,
CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ENCRYPTED,
@ -318,11 +352,15 @@ typedef enum CredentialSearchPath {
_CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL, _CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL,
} CredentialSearchPath; } CredentialSearchPath;
static int credential_search_path(const ExecParameters *params, CredentialSearchPath path, char ***ret) { static int credential_search_path(
const SetupCredentialsContext *context,
CredentialSearchPath path,
char ***ret) {
_cleanup_strv_free_ char **l = NULL; _cleanup_strv_free_ char **l = NULL;
int r; int r;
assert(params); assert(context);
assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX); assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX);
assert(ret); assert(ret);
@ -331,12 +369,12 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
* credentials, we'll look in /etc/credstore.encrypted/ (and similar dirs). */ * credentials, we'll look in /etc/credstore.encrypted/ (and similar dirs). */
if (IN_SET(path, CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ALL)) { if (IN_SET(path, CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ALL)) {
r = strv_extend(&l, params->received_encrypted_credentials_directory); r = strv_extend(&l, context->received_encrypted_credentials_directory);
if (r < 0) if (r < 0)
return r; return r;
_cleanup_strv_free_ char **add = NULL; _cleanup_strv_free_ char **add = NULL;
r = credential_store_path_encrypted(params->runtime_scope, &add); r = credential_store_path_encrypted(context->scope, &add);
if (r < 0) if (r < 0)
return r; return r;
@ -346,12 +384,12 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
} }
if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) { if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
r = strv_extend(&l, params->received_credentials_directory); r = strv_extend(&l, context->received_credentials_directory);
if (r < 0) if (r < 0)
return r; return r;
_cleanup_strv_free_ char **add = NULL; _cleanup_strv_free_ char **add = NULL;
r = credential_store_path(params->runtime_scope, &add); r = credential_store_path(context->scope, &add);
if (r < 0) if (r < 0)
return r; return r;
@ -369,23 +407,6 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
return 0; return 0;
} }
struct load_cred_args {
const ExecContext *context;
const ExecParameters *params;
const char *unit;
bool always_ipc;
bool encrypted;
int write_dfd;
uid_t uid;
gid_t gid;
bool ownership_ok;
uint64_t left;
};
static int write_credential( static int write_credential(
int dfd, int dfd,
const char *id, const char *id,
@ -431,7 +452,7 @@ static int write_credential(
} }
static int maybe_decrypt_and_write_credential( static int maybe_decrypt_and_write_credential(
struct load_cred_args *args, LoadCredentialArguments *args,
const char *id, const char *id,
const char *data, const char *data,
size_t size, size_t size,
@ -449,7 +470,7 @@ static int maybe_decrypt_and_write_credential(
if (args->encrypted) { if (args->encrypted) {
CredentialFlags flags = 0; /* only allow user creds in user scope */ CredentialFlags flags = 0; /* only allow user creds in user scope */
switch (args->params->runtime_scope) { switch (args->context->scope) {
case RUNTIME_SCOPE_SYSTEM: case RUNTIME_SCOPE_SYSTEM:
/* In system mode talk directly to the TPM unless we live in a device sandbox /* In system mode talk directly to the TPM unless we live in a device sandbox
@ -457,7 +478,7 @@ static int maybe_decrypt_and_write_credential(
flags |= CREDENTIAL_ANY_SCOPE; flags |= CREDENTIAL_ANY_SCOPE;
if (!args->always_ipc) { if (!args->context->always_ipc) {
r = decrypt_credential_and_warn( r = decrypt_credential_and_warn(
id, id,
now(CLOCK_REALTIME), now(CLOCK_REALTIME),
@ -506,7 +527,7 @@ static int maybe_decrypt_and_write_credential(
if (add > args->left) if (add > args->left)
return -E2BIG; return -E2BIG;
r = write_credential(args->write_dfd, id, data, size, args->uid, args->gid, args->ownership_ok); r = write_credential(args->write_dfd, id, data, size, args->context->uid, args->context->gid, args->ownership_ok);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to write credential '%s': %m", id); return log_debug_errno(r, "Failed to write credential '%s': %m", id);
@ -516,7 +537,7 @@ static int maybe_decrypt_and_write_credential(
} }
static int load_credential_glob( static int load_credential_glob(
struct load_cred_args *args, LoadCredentialArguments *args,
const ExecImportCredential *ic, const ExecImportCredential *ic,
char * const *search_path, char * const *search_path,
ReadFullFileFlags flags) { ReadFullFileFlags flags) {
@ -595,7 +616,7 @@ static int load_credential_glob(
} }
static int load_credential( static int load_credential(
struct load_cred_args *args, LoadCredentialArguments *args,
const char *id, const char *id,
int read_dfd, int read_dfd,
const char *path) { const char *path) {
@ -611,9 +632,8 @@ static int load_credential(
assert(args); assert(args);
assert(args->context); assert(args->context);
assert(args->params); assert(args->context->exec_context);
assert(args->unit); assert(args->context->unit);
assert(args->write_dfd >= 0);
assert(id); assert(id);
assert(read_dfd >= 0 || read_dfd == AT_FDCWD); assert(read_dfd >= 0 || read_dfd == AT_FDCWD);
assert(path); assert(path);
@ -641,7 +661,7 @@ static int load_credential(
/* Pass some minimal info about the unit and the credential name we are looking to acquire /* Pass some minimal info about the unit and the credential name we are looking to acquire
* via the source socket address in case we read off an AF_UNIX socket. */ * via the source socket address in case we read off an AF_UNIX socket. */
if (asprintf(&bindname, "@%" PRIx64 "/unit/%s/%s", random_u64(), args->unit, id) < 0) if (asprintf(&bindname, "@%" PRIx64 "/unit/%s/%s", random_u64(), args->context->unit, id) < 0)
return -ENOMEM; return -ENOMEM;
missing_ok = false; missing_ok = false;
@ -652,7 +672,7 @@ static int load_credential(
* directory we received ourselves. We don't support the AF_UNIX stuff in this mode, since we * directory we received ourselves. We don't support the AF_UNIX stuff in this mode, since we
* are operating on a credential store, i.e. this is guaranteed to be regular files. */ * are operating on a credential store, i.e. this is guaranteed to be regular files. */
r = credential_search_path(args->params, CREDENTIAL_SEARCH_PATH_ALL, &search_path); r = credential_search_path(args->context, CREDENTIAL_SEARCH_PATH_ALL, &search_path);
if (r < 0) if (r < 0)
return r; return r;
@ -695,17 +715,20 @@ static int load_credential(
else else
assert_not_reached(); assert_not_reached();
if (r == -ENOENT && (missing_ok || hashmap_contains(args->context->set_credentials, id))) { if (r == -ENOENT) {
/* Make a missing inherited credential non-fatal, let's just continue. After all apps bool in_set_credentials = hashmap_contains(args->context->exec_context->set_credentials, id);
* will get clear errors if we don't pass such a missing credential on as they if (missing_ok || in_set_credentials) {
* themselves will get ENOENT when trying to read them, which should not be much /* Make a missing inherited credential non-fatal, let's just continue. After all apps
* worse than when we handle the error here and make it fatal. * will get clear errors if we don't pass such a missing credential on as they
* * themselves will get ENOENT when trying to read them, which should not be much
* Also, if the source file doesn't exist, but a fallback is set via SetCredentials= * worse than when we handle the error here and make it fatal.
* we are fine, too. */ *
log_full_errno(hashmap_contains(args->context->set_credentials, id) ? LOG_DEBUG : LOG_INFO, * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
r, "Couldn't read inherited credential '%s', skipping: %m", path); * we are fine, too. */
return 0; log_full_errno(in_set_credentials ? LOG_DEBUG : LOG_INFO,
r, "Couldn't read inherited credential '%s', skipping: %m", path);
return 0;
}
} }
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", path); return log_debug_errno(r, "Failed to read credential '%s': %m", path);
@ -722,7 +745,7 @@ static int load_cred_recurse_dir_cb(
const struct statx *sx, const struct statx *sx,
void *userdata) { void *userdata) {
struct load_cred_args *args = ASSERT_PTR(userdata); LoadCredentialArguments *args = ASSERT_PTR(userdata);
_cleanup_free_ char *sub_id = NULL; _cleanup_free_ char *sub_id = NULL;
int r; int r;
@ -779,38 +802,27 @@ static bool device_nodes_restricted(
} }
static int acquire_credentials( static int acquire_credentials(
const ExecContext *context, const SetupCredentialsContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
int dfd, int dfd,
uid_t uid,
gid_t gid,
bool ownership_ok) { bool ownership_ok) {
int r; int r;
assert(context); assert(context);
assert(cgroup_context); assert(context->exec_context);
assert(params);
assert(unit);
assert(dfd >= 0); assert(dfd >= 0);
struct load_cred_args args = { LoadCredentialArguments args = {
.context = context, .context = context,
.params = params,
.unit = unit,
.always_ipc = device_nodes_restricted(context, cgroup_context),
.write_dfd = dfd, .write_dfd = dfd,
.uid = uid,
.gid = gid,
.ownership_ok = ownership_ok, .ownership_ok = ownership_ok,
.left = CREDENTIALS_TOTAL_SIZE_MAX, .left = CREDENTIALS_TOTAL_SIZE_MAX,
}; };
/* First, load credentials off disk (or acquire via AF_UNIX socket) */ /* First, load credentials off disk (or acquire via AF_UNIX socket) */
ExecLoadCredential *lc; ExecLoadCredential *lc;
HASHMAP_FOREACH(lc, context->load_credentials) { HASHMAP_FOREACH(lc, context->exec_context->load_credentials) {
_cleanup_close_ int sub_fd = -EBADF; _cleanup_close_ int sub_fd = -EBADF;
args.encrypted = lc->encrypted; args.encrypted = lc->encrypted;
@ -849,10 +861,10 @@ static int acquire_credentials(
/* Next, look for system credentials and credentials in the credentials store. Note that these do not /* Next, look for system credentials and credentials in the credentials store. Note that these do not
* override any credentials found earlier. */ * override any credentials found earlier. */
ExecImportCredential *ic; ExecImportCredential *ic;
ORDERED_SET_FOREACH(ic, context->import_credentials) { ORDERED_SET_FOREACH(ic, context->exec_context->import_credentials) {
_cleanup_free_ char **search_path = NULL; _cleanup_free_ char **search_path = NULL;
r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path); r = credential_search_path(context, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path);
if (r < 0) if (r < 0)
return r; return r;
@ -868,7 +880,7 @@ static int acquire_credentials(
search_path = strv_free(search_path); search_path = strv_free(search_path);
r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED, &search_path); r = credential_search_path(context, CREDENTIAL_SEARCH_PATH_ENCRYPTED, &search_path);
if (r < 0) if (r < 0)
return r; return r;
@ -886,7 +898,7 @@ static int acquire_credentials(
/* Finally, we add in literally specified credentials. If the credentials already exist, we'll not /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
* add them, so that they can act as a "default" if the same credential is specified multiple times. */ * add them, so that they can act as a "default" if the same credential is specified multiple times. */
ExecSetCredential *sc; ExecSetCredential *sc;
HASHMAP_FOREACH(sc, context->set_credentials) { HASHMAP_FOREACH(sc, context->exec_context->set_credentials) {
args.encrypted = sc->encrypted; args.encrypted = sc->encrypted;
if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) { if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
@ -934,13 +946,8 @@ static int credentials_dir_finalize_permissions(int dfd, uid_t uid, gid_t gid, b
} }
static int setup_credentials_plain_dir( static int setup_credentials_plain_dir(
const ExecContext *context, const SetupCredentialsContext *context,
const CGroupContext *cgroup_context, const char *cred_dir) {
const ExecParameters *params,
const char *unit,
const char *cred_dir,
uid_t uid,
gid_t gid) {
_cleanup_free_ char *t = NULL, *workspace = NULL; _cleanup_free_ char *t = NULL, *workspace = NULL;
_cleanup_(rm_rf_safep) const char *workspace_rm = NULL; _cleanup_(rm_rf_safep) const char *workspace_rm = NULL;
@ -948,13 +955,13 @@ static int setup_credentials_plain_dir(
int r; int r;
assert(context); assert(context);
assert(params); assert(context->unit);
assert(unit); assert(context->runtime_prefix);
assert(cred_dir); assert(cred_dir);
/* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving /* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
* it into place, so that users can't access half-initialized credential stores. */ * it into place, so that users can't access half-initialized credential stores. */
t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials"); t = path_join(context->runtime_prefix, "systemd/temporary-credentials");
if (!t) if (!t)
return -ENOMEM; return -ENOMEM;
@ -962,7 +969,7 @@ static int setup_credentials_plain_dir(
if (r < 0 && r != -EEXIST) if (r < 0 && r != -EEXIST)
return r; return r;
workspace = path_join(t, unit); workspace = path_join(t, context->unit);
if (!workspace) if (!workspace)
return -ENOMEM; return -ENOMEM;
@ -973,7 +980,7 @@ static int setup_credentials_plain_dir(
(void) label_fix_full(dfd, /* inode_path= */ NULL, cred_dir, /* flags= */ 0); (void) label_fix_full(dfd, /* inode_path= */ NULL, cred_dir, /* flags= */ 0);
r = acquire_credentials(context, cgroup_context, params, unit, dfd, uid, gid, /* ownership_ok= */ false); r = acquire_credentials(context, dfd, /* ownership_ok= */ false);
if (r < 0) if (r < 0)
return r; return r;
@ -995,7 +1002,7 @@ static int setup_credentials_plain_dir(
/* rename() requires both the source and target to be writable, hence lock down write permission /* rename() requires both the source and target to be writable, hence lock down write permission
* as last step. */ * as last step. */
r = credentials_dir_finalize_permissions(dfd, uid, gid, /* ownership_ok= */ false); r = credentials_dir_finalize_permissions(dfd, context->uid, context->gid, /* ownership_ok= */ false);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m"); return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m");
@ -1003,43 +1010,47 @@ static int setup_credentials_plain_dir(
} }
static int setup_credentials_internal( static int setup_credentials_internal(
const ExecContext *context, const SetupCredentialsContext *context,
const CGroupContext *cgroup_context, bool may_reuse,
const ExecParameters *params, const char *cred_dir) {
const char *unit,
const char *cred_dir,
uid_t uid,
gid_t gid) {
_cleanup_close_ int fs_fd = -EBADF, mfd = -EBADF, dfd = -EBADF; _cleanup_close_ int fs_fd = -EBADF, mfd = -EBADF, dfd = -EBADF;
bool dir_mounted; bool dir_mounted;
int r; int r;
assert(context); assert(context);
assert(params);
assert(unit);
assert(cred_dir); assert(cred_dir);
if (!FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
/* We may reuse the previous credential dir */
r = dir_is_empty(cred_dir, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return r;
if (r == 0) {
log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
return 0;
}
}
r = path_is_mount_point(cred_dir); r = path_is_mount_point(cred_dir);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", cred_dir); return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", cred_dir);
dir_mounted = r > 0; dir_mounted = r > 0;
if (may_reuse) {
bool populated;
/* If the cred dir is a mount, let's treat it as populated, and only look at the contents
* if it's a plain dir, where we can't reasonably differentiate populated yet empty vs
* not set up. */
if (dir_mounted)
populated = true;
else {
r = dir_is_empty(cred_dir, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return r;
populated = r == 0;
}
if (populated) {
log_debug("Credential dir for unit '%s' already set up, skipping.", context->unit);
return 0;
}
}
mfd = fsmount_credentials_fs(&fs_fd); mfd = fsmount_credentials_fs(&fs_fd);
if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) { if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) {
log_debug_errno(mfd, "Lacking privilege to mount credentials fs, falling back to plain directory."); log_debug_errno(mfd, "Lacking privilege to mount credentials fs, falling back to plain directory.");
return setup_credentials_plain_dir(context, cgroup_context, params, unit, cred_dir, uid, gid); return setup_credentials_plain_dir(context, cred_dir);
} }
if (mfd < 0) if (mfd < 0)
return log_debug_errno(mfd, "Failed to mount credentials fs: %m"); return log_debug_errno(mfd, "Failed to mount credentials fs: %m");
@ -1050,11 +1061,11 @@ static int setup_credentials_internal(
(void) label_fix_full(dfd, /* inode_path= */ NULL, cred_dir, /* flags= */ 0); (void) label_fix_full(dfd, /* inode_path= */ NULL, cred_dir, /* flags= */ 0);
r = acquire_credentials(context, cgroup_context, params, unit, dfd, uid, gid, /* ownership_ok= */ true); r = acquire_credentials(context, dfd, /* ownership_ok= */ true);
if (r < 0) if (r < 0)
return r; return r;
r = credentials_dir_finalize_permissions(dfd, uid, gid, /* ownership_ok= */ true); r = credentials_dir_finalize_permissions(dfd, context->uid, context->gid, /* ownership_ok= */ true);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m"); return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m");
@ -1099,7 +1110,6 @@ int exec_setup_credentials(
const ExecContext *context, const ExecContext *context,
const CGroupContext *cgroup_context, const CGroupContext *cgroup_context,
const ExecParameters *params, const ExecParameters *params,
const char *unit,
uid_t uid, uid_t uid,
gid_t gid) { gid_t gid) {
@ -1108,7 +1118,6 @@ int exec_setup_credentials(
assert(context); assert(context);
assert(params); assert(params);
assert(unit);
if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context)) if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context))
return 0; return 0;
@ -1126,7 +1135,7 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST) if (r < 0 && r != -EEXIST)
return r; return r;
p = path_join(q, unit); p = path_join(q, params->unit_id);
if (!p) if (!p)
return -ENOMEM; return -ENOMEM;
@ -1134,12 +1143,208 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST) if (r < 0 && r != -EEXIST)
return r; return r;
r = setup_credentials_internal(context, cgroup_context, params, unit, p, uid, gid); SetupCredentialsContext ctx = {
.scope = params->runtime_scope,
.exec_context = context,
.unit = params->unit_id,
.runtime_prefix = params->prefix[EXEC_DIRECTORY_RUNTIME],
.received_credentials_directory = params->received_credentials_directory,
.received_encrypted_credentials_directory = params->received_encrypted_credentials_directory,
.always_ipc = device_nodes_restricted(context, cgroup_context),
.uid = uid,
.gid = gid,
};
r = setup_credentials_internal(&ctx, /* may_reuse = */ !FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH), p);
if (r < 0)
(void) rmdir(p);
/* If the credentials dir is empty and not a mount point, then there's no point in having it. Let's
* try to remove it. This matters in particular if we created the dir as mount point but then didn't
* actually end up mounting anything on it. In that case we'd rather have ENOENT than EACCESS being
* seen by users when trying access this inode. */
(void) rmdir(p);
return r; return r;
} }
static int refresh_credentials_in_namespace_child(int cfd, const char *cred_dir) {
int r;
assert(cfd >= 0);
assert(cred_dir);
/* Paranoia: before doing anything, check if the credentials tree inside the mountns is available.
*
* Note that setup_namespace() always installs a mount for cred dir, hence path_is_mount_point()
* is the appropriate check here. */
r = path_is_mount_point(cred_dir);
if (IN_SET(r, 0, -ENOENT)) {
log_full_errno_zerook(LOG_WARNING, r,
"Credentials tree in the unit mount namespace is masked, skipping refresh.");
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to check whether '%s' is a mountpoint in unit mount namespace: %m",
cred_dir);
/* Inform the parent that we're good to go */
ssize_t n = write(cfd, &r, sizeof(r));
if (n < 0)
return log_error_errno(errno, "Failed to write to socket: %m");
_cleanup_close_ int mfd = receive_one_fd(cfd, /* flags = */ 0);
if (mfd < 0)
return log_error_errno(mfd, "Failed to receive credentials tree fd from socket: %m");
r = mount_exchange_graceful(mfd, cred_dir, /* mount_beneath = */ true);
if (r < 0)
return log_error_errno(r, "Failed to update credentials mount in namespace: %m");
return 1;
}
int unit_refresh_credentials(Unit *u) {
_cleanup_free_ char *cred_dir = NULL;
int r;
/* Refresh the credentials for a unit, potentially forking off a second process to join the mountns
* if needed. Returns > 0 on successful refresh, == 0 if the credentials tree is masked and the operation
* is skipped. */
assert(u);
assert(u->manager);
r = get_credential_directory(u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id, &cred_dir);
if (r < 0)
return log_oom();
assert(r > 0);
if (access(cred_dir, F_OK) < 0) {
if (errno == ENOENT) {
log_warning_errno(errno, "Requested to refresh credentials, but credentials aren't populated, skipping.");
return 0;
}
return log_error_errno(errno, "Failed to check if credentials dir '%s' exists: %m", cred_dir);
}
_cleanup_close_pair_ int tunnel_fds[2] = EBADF_PAIR;
_cleanup_(pidref_done) PidRef child = PIDREF_NULL;
_cleanup_close_ int userns_fd = -EBADF;
PidRef *main_pid = unit_main_pid(u);
if (pidref_is_set(main_pid)) {
_cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF;
r = pidref_namespace_open(main_pid,
&pidns_fd,
&mntns_fd,
/* ret_netns_fd = */ NULL,
MANAGER_IS_USER(u->manager) ? &userns_fd : NULL,
&root_fd);
if (r < 0)
return log_error_errno(r, "Failed to open namespace of unit main process '" PID_FMT "': %m",
main_pid->pid);
r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return log_error_errno(r, "Failed to check if main process resides in a separate mount namespace: %m");
if (r == 0) {
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, tunnel_fds) < 0)
return log_error_errno(errno, "Failed to allocate socket pair: %m");
r = namespace_fork_full("(sd-creds-ns)", "(sd-creds-ns-inner)",
(int[]) { tunnel_fds[1] }, 1,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG,
pidns_fd, mntns_fd, /* netns_fd = */ -EBADF, userns_fd, root_fd,
/* delegated = */ MANAGER_IS_USER(u->manager),
&child);
if (r < 0)
return log_full_errno(ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_WARNING : LOG_ERR, r,
"Failed to fork off process into unit namespace to refresh credentials: %m");
if (r == 0) {
r = refresh_credentials_in_namespace_child(tunnel_fds[1], cred_dir);
report_errno_and_exit(tunnel_fds[1], r);
}
tunnel_fds[1] = safe_close(tunnel_fds[1]);
/* Wait for the child to validate the creds tree in the unit namespace is populated. */
ssize_t n = read(tunnel_fds[0], &r, sizeof(r));
if (n < 0)
return log_error_errno(errno, "Failed to read from socket: %m");
if (!IN_SET(n, 0, sizeof(r)))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Received unexpected amount of bytes (%zi) while reading errno.", n);
if (n == 0 || r == 0) {
/* The child exited without sending anything or 0 is received signifying
* the credentials are masked? Check the exit status to be sure. */
r = pidref_wait_for_terminate_and_check("(sd-creds-ns)", &child, WAIT_LOG);
if (r < 0)
return r;
if (r != EXIT_SUCCESS)
return -EPROTO;
return 0; /* skipped */
}
if (r < 0)
return r;
/* Yay! Got > 0 from child indicating all good, proceed with refreshing. */
}
}
SetupCredentialsContext ctx = {
.scope = u->manager->runtime_scope,
.exec_context = ASSERT_PTR(unit_get_exec_context(u)),
.unit = u->id,
.runtime_prefix = u->manager->prefix[EXEC_DIRECTORY_RUNTIME],
.received_credentials_directory = u->manager->received_credentials_directory,
.received_encrypted_credentials_directory = u->manager->received_encrypted_credentials_directory,
.always_ipc = false, /* we don't migrate to unit cgroup, hence cannot be restricted by cgroup bpf */
.uid = u->ref_uid,
.gid = u->ref_gid,
};
r = setup_credentials_internal(&ctx, /* may_reuse = */ false, cred_dir);
if (r < 0)
return log_error_errno(r, "Failed to set up credentials: %m");
/* The main process doesn't run in a mountns hence nothing got forked off? Then we're all set. */
if (!pidref_is_set(&child))
return 1;
if (userns_fd >= 0) {
assert(MANAGER_IS_USER(u->manager));
/* Enter the unit userns now and unshare mountns, so that we have permissions to clone
* the mount tree using open_tree() */
if (setns(userns_fd, CLONE_NEWUSER) < 0)
return log_error_errno(errno, "Failed to enter user namespace: %m");
if (unshare(CLONE_NEWNS) < 0)
return log_error_errno(errno, "Failed to unshare mount namespace: %m");
}
_cleanup_close_ int tfd = open_tree(AT_FDCWD, cred_dir, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW);
if (tfd < 0)
return log_error_errno(errno, "Failed to clone mount tree at '%s': %m", cred_dir);
r = send_one_fd(tunnel_fds[0], tfd, /* flags = */ 0);
if (r < 0)
return log_error_errno(r, "Failed to send mount fd to child: %m");
r = pidref_wait_for_terminate_and_check("(sd-creds-ns)", &child, WAIT_LOG_ABNORMAL);
if (r < 0)
return r;
if (r != EXIT_SUCCESS) {
r = read_errno(tunnel_fds[0]);
if (r < 0)
return r;
return -EPROTO;
}
return 1;
}

View File

@ -57,8 +57,9 @@ int exec_setup_credentials(
const ExecContext *context, const ExecContext *context,
const CGroupContext *cgroup_context, const CGroupContext *cgroup_context,
const ExecParameters *params, const ExecParameters *params,
const char *unit,
uid_t uid, uid_t uid,
gid_t gid); gid_t gid);
int unit_refresh_credentials(Unit *u);
bool mount_point_is_credentials(const char *runtime_prefix, const char *path); bool mount_point_is_credentials(const char *runtime_prefix, const char *path);

View File

@ -5705,7 +5705,7 @@ int exec_invoke(
return log_error_errno(r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]); return log_error_errno(r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
} }
r = exec_setup_credentials(context, cgroup_context, params, params->unit_id, uid, gid); r = exec_setup_credentials(context, cgroup_context, params, uid, gid);
if (r < 0) { if (r < 0) {
*exit_status = EXIT_CREDENTIALS; *exit_status = EXIT_CREDENTIALS;
return log_error_errno(r, "Failed to set up credentials: %m"); return log_error_errno(r, "Failed to set up credentials: %m");

View File

@ -479,6 +479,7 @@ Service.USBFunctionStrings, config_parse_unit_path_printf,
Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy) Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy)
Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files) Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files)
Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal) Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal)
Service.RefreshOnReload, config_parse_service_refresh_on_reload, 0, 0
{{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }} {{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }} {{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Service') }} {{ KILL_CONTEXT_CONFIG_ITEMS('Service') }}

View File

@ -4816,6 +4816,55 @@ int config_parse_import_credential(
return 0; return 0;
} }
int config_parse_service_refresh_on_reload(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Service *s = ASSERT_PTR(userdata);
int r;
if (isempty(rvalue)) {
s->refresh_on_reload_set = false;
return 0;
}
r = parse_boolean(rvalue);
if (r >= 0) {
s->refresh_on_reload_flags = r > 0 ? _SERVICE_REFRESH_ON_RELOAD_ALL : 0;
s->refresh_on_reload_set = true;
return 0;
}
ServiceRefreshOnReload f;
bool invert = false;
if (rvalue[0] == '~') {
invert = true;
rvalue++;
}
r = service_refresh_on_reload_from_string_many(rvalue, &f);
if (r < 0)
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
/* If the first entry is negated, mask off from default; otherwise assign "positive" values directly */
if (!s->refresh_on_reload_set)
s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f;
else
SET_FLAG(s->refresh_on_reload_flags, f, !invert);
s->refresh_on_reload_set = true;
return 0;
}
int config_parse_set_status( int config_parse_set_status(
const char *unit, const char *unit,
const char *filename, const char *filename,

View File

@ -105,6 +105,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_quota);
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential); CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential); CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_import_credential); CONFIG_PARSER_PROTOTYPE(config_parse_import_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_service_refresh_on_reload);
CONFIG_PARSER_PROTOTYPE(config_parse_set_status); CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv); CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems); CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);

View File

@ -1888,13 +1888,15 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); u = manager_get_unit(m, SPECIAL_DBUS_SERVICE);
if (!u) if (!u)
return false; return false;
if (!IN_SET((deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state), if (!IN_SET(deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state,
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_MOUNTING,
SERVICE_RELOAD,
SERVICE_RELOAD_NOTIFY,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD_SIGNAL)) SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
SERVICE_RELOAD_POST,
SERVICE_MOUNTING))
return false; return false;
return true; return true;

View File

@ -2845,7 +2845,6 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
.mode = MOUNT_BIND, .mode = MOUNT_BIND,
.read_only = true, .read_only = true,
.source_const = p->creds_path, .source_const = p->creds_path,
.ignore = true,
}; };
} }

View File

@ -69,6 +69,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_RUNNING] = UNIT_ACTIVE, [SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE, [SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING, [SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING, [SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
@ -102,6 +103,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_RUNNING] = UNIT_ACTIVE, [SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE, [SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING, [SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING, [SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
@ -134,11 +136,14 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f
static void service_reload_finish(Service *s, ServiceResult f); static void service_reload_finish(Service *s, ServiceResult f);
static void service_enter_reload_by_notify(Service *s); static void service_enter_reload_by_notify(Service *s);
static bool service_can_reload_extensions(Service *s, bool warn);
static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) { static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
return IN_SET(state, return IN_SET(state,
SERVICE_START, SERVICE_START_POST, SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING, SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL); SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL);
@ -148,7 +153,7 @@ static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) {
return IN_SET(state, return IN_SET(state,
SERVICE_CONDITION, SERVICE_CONDITION,
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS, SERVICE_RELOAD, SERVICE_RELOAD_POST,
SERVICE_MOUNTING, SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@ -159,7 +164,8 @@ static bool SERVICE_STATE_WITH_WATCHDOG(ServiceState state) {
return IN_SET(state, return IN_SET(state,
SERVICE_START_POST, SERVICE_START_POST,
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING); SERVICE_MOUNTING);
} }
@ -754,6 +760,15 @@ static int service_verify(Service *s) {
s->restart_usec = s->restart_max_delay_usec; s->restart_usec = s->restart_max_delay_usec;
} }
if (s->refresh_on_reload_set && s->refresh_on_reload_flags != _SERVICE_REFRESH_ON_RELOAD_ALL) {
if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS))
service_can_reload_extensions(s, /* warn = */ true);
if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_CREDENTIALS) &&
!exec_context_has_credentials(&s->exec_context))
log_unit_warning(UNIT(s), "Service has RefreshOnReload=credentials, but no credentials are in use. The credentials tree will be masked which blocks further refreshing. Continuing.");
}
return 0; return 0;
} }
@ -891,6 +906,11 @@ static int service_add_extras(Service *s) {
(IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0))
s->notify_access = NOTIFY_MAIN; s->notify_access = NOTIFY_MAIN;
if (!s->refresh_on_reload_set) {
assert_cc(SERVICE_REFRESH_ON_RELOAD_DEFAULT == SERVICE_RELOAD_EXTENSIONS);
s->refresh_on_reload_flags = service_can_reload_extensions(s, /* warn = */ false) ? SERVICE_RELOAD_EXTENSIONS : 0;
}
/* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when /* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when
* delegation is on, in that case it we assume the payload knows better what to do and can process * delegation is on, in that case it we assume the payload knows better what to do and can process
* things in a more focused way. */ * things in a more focused way. */
@ -1296,7 +1316,8 @@ static void service_set_state(Service *s, ServiceState state) {
if (!IN_SET(state, if (!IN_SET(state,
SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING, SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@ -1367,6 +1388,7 @@ static usec_t service_coldplug_timeout(Service *s) {
case SERVICE_START: case SERVICE_START:
case SERVICE_START_POST: case SERVICE_START_POST:
case SERVICE_REFRESH_EXTENSIONS: case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD: case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL: case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY: case SERVICE_RELOAD_NOTIFY:
@ -1667,11 +1689,16 @@ static Service *service_get_triggering_service(Service *s) {
return NULL; return NULL;
} }
static ExecFlags service_exec_flags(ServiceExecCommand command_id, ExecFlags cred_flag) { static ExecFlags service_exec_flags(
const Service *s,
ServiceExecCommand command_id,
ExecFlags cred_flag) {
/* All service main/control processes honor sandboxing and namespacing options (except those /* All service main/control processes honor sandboxing and namespacing options (except those
explicitly excluded in service_spawn()) */ explicitly excluded in service_spawn()) */
ExecFlags flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT; ExecFlags flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT;
assert(s);
assert(command_id >= 0); assert(command_id >= 0);
assert(command_id < _SERVICE_EXEC_COMMAND_MAX); assert(command_id < _SERVICE_EXEC_COMMAND_MAX);
assert((cred_flag & ~(EXEC_SETUP_CREDENTIALS_FRESH|EXEC_SETUP_CREDENTIALS)) == 0); assert((cred_flag & ~(EXEC_SETUP_CREDENTIALS_FRESH|EXEC_SETUP_CREDENTIALS)) == 0);
@ -1703,6 +1730,12 @@ static ExecFlags service_exec_flags(ServiceExecCommand command_id, ExecFlags cre
if (!IN_SET(command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE)) if (!IN_SET(command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE))
flags |= EXEC_CONTROL_CGROUP; flags |= EXEC_CONTROL_CGROUP;
/* Pass credentials to ExecReload*= too, but only if the credentials are actually refreshed,
* to make sure they have the same understanding of the world as the main process. */
if (IN_SET(command_id, SERVICE_EXEC_RELOAD, SERVICE_EXEC_RELOAD_POST) &&
FLAGS_SET(s->refreshed_mask, SERVICE_RELOAD_CREDENTIALS))
flags |= EXEC_SETUP_CREDENTIALS;
if (IN_SET(command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST)) if (IN_SET(command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST))
flags |= EXEC_SETENV_RESULT; flags |= EXEC_SETENV_RESULT;
@ -2228,7 +2261,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_stop_usec, s->timeout_stop_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2340,7 +2373,7 @@ static void service_enter_stop(Service *s, ServiceResult f) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_stop_usec, s->timeout_stop_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2425,7 +2458,7 @@ static void service_enter_start_post(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec, s->timeout_start_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2535,7 +2568,7 @@ static void service_enter_start(Service *s) {
r = service_spawn(s, r = service_spawn(s,
c, c,
service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH), service_exec_flags(s, SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH),
timeout, timeout,
&pidref); &pidref);
if (r < 0) { if (r < 0) {
@ -2595,7 +2628,7 @@ static void service_enter_start_pre(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec, s->timeout_start_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2631,7 +2664,7 @@ static void service_enter_condition(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec, s->timeout_start_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2735,7 +2768,7 @@ static void service_enter_reload_post(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec, s->timeout_start_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2810,7 +2843,7 @@ static void service_enter_reload(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec, s->timeout_start_usec,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2823,28 +2856,114 @@ static void service_enter_reload(Service *s) {
service_enter_reload_signal(s); service_enter_reload_signal(s);
} }
static bool service_should_reload_extensions(Service *s) { static bool service_get_effective_reload_credentials(Service *s) {
assert(s);
return FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_CREDENTIALS) &&
exec_context_has_credentials(&s->exec_context);
}
static void service_enter_refresh_credentials(Service *s) {
_cleanup_(pidref_done) PidRef worker = PIDREF_NULL;
int r; int r;
assert(s); assert(s);
if (!pidref_is_set(&s->main_pid)) { if (!service_get_effective_reload_credentials(s))
log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID."); return service_enter_reload(s);
return false;
service_unwatch_control_pid(s);
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;
} }
r = exec_context_has_vpicked_extensions(&s->exec_context); r = unit_fork_helper_process_full(UNIT(s), "(sd-refresh-creds)", /* into_cgroup = */ false,
if (r < 0) FORK_ALLOW_DLOPEN, /* allow loading libacl to avoid doing so in pid1 */
log_unit_warning_errno(UNIT(s), r, "Failed to determine if service should reload extensions, assuming false: %m"); &worker);
if (r == 0) if (r < 0) {
log_unit_debug(UNIT(s), "Service has no extensions to reload."); log_unit_error_errno(UNIT(s), r, "Failed to fork process to refresh credentials in unit's namespace: %m");
if (r <= 0) goto fail;
return false; }
if (r == 0) {
LOG_CONTEXT_PUSH_UNIT(UNIT(s));
r = unit_refresh_credentials(UNIT(s));
if (ERRNO_IS_NEG_PRIVILEGE(r))
_exit(EXIT_NOPERMISSION);
if (r < 0)
_exit(EXIT_FAILURE);
if (r == 0)
_exit(EXIT_NOTINSTALLED);
_exit(EXIT_SUCCESS);
}
r = unit_watch_pidref(UNIT(s), &worker, /* exclusive = */ true);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to watch credentials refresh helper process: %m");
goto fail;
}
s->control_pid = TAKE_PIDREF(worker);
service_set_state(s, SERVICE_REFRESH_CREDENTIALS);
return;
fail:
service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
static bool service_can_reload_extensions(Service *s, bool warn) {
assert(s);
// TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload. // TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload.
// For now, skip for user services. // For now, skip for user services.
if (exec_context_has_vpicked_extensions(&s->exec_context) <= 0) {
if (warn)
log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no extensions using vpick. Ignoring.");
return false;
}
if (!s->exec_command[SERVICE_EXEC_START]) {
if (warn)
log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no main process (ExecStart=). Ignoring.");
return false;
}
if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) { if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) {
log_once(LOG_WARNING, "Not reloading extensions for user services."); if (warn)
log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, which is not supported in user mode. Ignoring.");
return false;
}
return true;
}
static bool service_get_effective_reload_extensions(Service *s) {
assert(s);
if (!FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS))
return false;
if (!service_can_reload_extensions(s, /* warn = */ false))
return false;
return true;
}
static bool service_should_reload_extensions(Service *s) {
assert(s);
if (!service_get_effective_reload_extensions(s))
return false;
if (!pidref_is_set(&s->main_pid)) {
log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
return false; return false;
} }
@ -2857,9 +2976,9 @@ static void service_enter_refresh_extensions(Service *s) {
assert(s); assert(s);
/* If we don't have extensions to refresh, immediately transition to reload state */ /* If we don't have extensions to refresh, immediately transition to next state */
if (!service_should_reload_extensions(s)) if (!service_should_reload_extensions(s))
return service_enter_reload(s); return service_enter_refresh_credentials(s);
service_unwatch_control_pid(s); service_unwatch_control_pid(s);
s->control_command = NULL; s->control_command = NULL;
@ -2955,7 +3074,7 @@ static void service_run_next_control(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->control_command, s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0), service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
timeout, timeout,
&s->control_pid); &s->control_pid);
if (r < 0) { if (r < 0) {
@ -2986,7 +3105,7 @@ static void service_run_next_main(Service *s) {
r = service_spawn(s, r = service_spawn(s,
s->main_command, s->main_command,
service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS), service_exec_flags(s, SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS),
s->timeout_start_usec, s->timeout_start_usec,
&pidref); &pidref);
if (r < 0) { if (r < 0) {
@ -3124,6 +3243,7 @@ static int service_stop(Unit *u) {
service_live_mount_finish(s, SERVICE_FAILURE_PROTOCOL, BUS_ERROR_UNIT_INACTIVE); service_live_mount_finish(s, SERVICE_FAILURE_PROTOCOL, BUS_ERROR_UNIT_INACTIVE);
_fallthrough_; _fallthrough_;
case SERVICE_REFRESH_EXTENSIONS: case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
service_kill_control_process(s); service_kill_control_process(s);
_fallthrough_; _fallthrough_;
case SERVICE_CONDITION: case SERVICE_CONDITION:
@ -3166,6 +3286,7 @@ static int service_reload(Unit *u) {
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
s->reload_result = SERVICE_SUCCESS; s->reload_result = SERVICE_SUCCESS;
s->refreshed_mask = 0;
service_enter_refresh_extensions(s); service_enter_refresh_extensions(s);
@ -3176,7 +3297,10 @@ static bool service_can_reload(Unit *u) {
Service *s = ASSERT_PTR(SERVICE(u)); Service *s = ASSERT_PTR(SERVICE(u));
return s->exec_command[SERVICE_EXEC_RELOAD] || return s->exec_command[SERVICE_EXEC_RELOAD] ||
s->type == SERVICE_NOTIFY_RELOAD; s->type == SERVICE_NOTIFY_RELOAD ||
(s->refresh_on_reload_set &&
(service_get_effective_reload_extensions(s) ||
service_get_effective_reload_credentials(s)));
} }
static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, const ExecCommand *current) { static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, const ExecCommand *current) {
@ -3386,6 +3510,21 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
(void) serialize_usec(f, "reload-begin-usec", s->reload_begin_usec); (void) serialize_usec(f, "reload-begin-usec", s->reload_begin_usec);
if (s->refreshed_mask > 0) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *t = NULL;
r = service_refresh_on_reload_to_strv(s->refreshed_mask, &l);
if (r < 0)
return log_oom();
t = strv_join(l, " ");
if (!t)
return log_oom();
(void) serialize_item(f, "refreshed-mask", t);
}
return 0; return 0;
} }
@ -3775,7 +3914,11 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
} else if (streq(key, "reload-begin-usec")) } else if (streq(key, "reload-begin-usec"))
(void) deserialize_usec(value, &s->reload_begin_usec); (void) deserialize_usec(value, &s->reload_begin_usec);
else else if (streq(key, "refreshed-mask")) {
r = service_refresh_on_reload_from_string_many(value, &s->refreshed_mask);
if (r < 0)
log_unit_debug_errno(u, r, "Failed to parse refresh-mask value: %s", value);
} else
log_unit_debug(u, "Unknown serialization key: %s", key); log_unit_debug(u, "Unknown serialization key: %s", key);
return 0; return 0;
@ -4188,6 +4331,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
case SERVICE_START_POST: case SERVICE_START_POST:
case SERVICE_REFRESH_EXTENSIONS: case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD: case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL: case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY: case SERVICE_RELOAD_NOTIFY:
@ -4307,7 +4451,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
success, success,
code, status); code, status);
if (!IN_SET(s->state, SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_MOUNTING) && if (!IN_SET(s->state, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_MOUNTING) &&
s->result == SERVICE_SUCCESS) s->result == SERVICE_SUCCESS)
s->result = f; s->result = f;
@ -4396,10 +4540,21 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
break; break;
case SERVICE_REFRESH_EXTENSIONS: case SERVICE_REFRESH_EXTENSIONS:
if (f == SERVICE_SUCCESS) if (f == SERVICE_SUCCESS) {
/* Remounting extensions asynchronously done, proceed to reload */ s->refreshed_mask |= SERVICE_RELOAD_EXTENSIONS;
service_enter_refresh_credentials(s);
} else
service_reload_finish(s, f);
break;
case SERVICE_REFRESH_CREDENTIALS:
if (f == SERVICE_SUCCESS ||
(f == SERVICE_FAILURE_EXIT_CODE && IN_SET(status, EXIT_NOTINSTALLED, EXIT_NOPERMISSION))) {
/* Refreshing asynchronously done, proceed to reload */
s->refreshed_mask |= f == SERVICE_SUCCESS ? SERVICE_RELOAD_CREDENTIALS : 0;
service_enter_reload(s); service_enter_reload(s);
else } else
service_reload_finish(s, f); service_reload_finish(s, f);
break; break;
@ -4515,6 +4670,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
break; break;
case SERVICE_REFRESH_EXTENSIONS: case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD: case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL: case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY: case SERVICE_RELOAD_NOTIFY:
@ -4838,7 +4994,9 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
if (strv_contains(tags, "STOPPING=1")) { if (strv_contains(tags, "STOPPING=1")) {
s->notify_state = NOTIFY_STOPPING; s->notify_state = NOTIFY_STOPPING;
if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS)) if (IN_SET(s->state, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY))
service_enter_stop_by_notify(s); service_enter_stop_by_notify(s);
return; return;
@ -4935,7 +5093,8 @@ static void service_notify_message(
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid); r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 && if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_STOP, SERVICE_STOP_SIGTERM) && SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
(!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) { (!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) {
@ -5204,6 +5363,7 @@ static bool pick_up_pid_from_bus_name(Service *s) {
SERVICE_START_POST, SERVICE_START_POST,
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_NOTIFY,
@ -5390,6 +5550,7 @@ static bool service_needs_console(Unit *u) {
SERVICE_START_POST, SERVICE_START_POST,
SERVICE_RUNNING, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_NOTIFY,
@ -5888,6 +6049,73 @@ static const char* const service_timeout_failure_mode_table[_SERVICE_TIMEOUT_FAI
DEFINE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode); DEFINE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode);
static const struct {
ServiceRefreshOnReload flag;
const char *name;
} service_refresh_on_reload_table[] = {
{ SERVICE_RELOAD_EXTENSIONS, "extensions" },
{ SERVICE_RELOAD_CREDENTIALS, "credentials" },
};
ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) {
assert(s);
FOREACH_ELEMENT(i, service_refresh_on_reload_table)
if (streq(s, i->name))
return i->flag;
return _SERVICE_REFRESH_ON_RELOAD_INVALID;
}
int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret) {
ServiceRefreshOnReload flags = 0;
int r;
assert(s);
assert(ret);
for (;;) {
_cleanup_free_ char *v = NULL;
ServiceRefreshOnReload f;
r = extract_first_word(&s, &v, NULL, 0);
if (r < 0)
return r;
if (r == 0)
break;
f = service_refresh_on_reload_flag_from_string(v);
if (f < 0)
return f;
assert(f > 0);
flags |= f;
}
*ret = flags;
return 0;
}
int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(flags >= 0);
assert(ret);
FOREACH_ELEMENT(i, service_refresh_on_reload_table) {
if (!FLAGS_SET(flags, i->flag))
continue;
r = strv_extend(&l, i->name);
if (r < 0)
return r;
}
*ret = TAKE_PTR(l);
return 0;
}
const UnitVTable service_vtable = { const UnitVTable service_vtable = {
.object_size = sizeof(Service), .object_size = sizeof(Service),
.exec_context_offset = offsetof(Service, exec_context), .exec_context_offset = offsetof(Service, exec_context),

View File

@ -96,6 +96,15 @@ typedef enum ServiceRestartMode {
_SERVICE_RESTART_MODE_INVALID = -EINVAL, _SERVICE_RESTART_MODE_INVALID = -EINVAL,
} ServiceRestartMode; } ServiceRestartMode;
typedef enum ServiceRefreshOnReload {
SERVICE_RELOAD_EXTENSIONS = 1 << 0,
SERVICE_RELOAD_CREDENTIALS = 1 << 1,
_SERVICE_REFRESH_ON_RELOAD_ALL = (1 << 2) - 1,
_SERVICE_REFRESH_ON_RELOAD_INVALID = -EINVAL,
} ServiceRefreshOnReload;
#define SERVICE_REFRESH_ON_RELOAD_DEFAULT SERVICE_RELOAD_EXTENSIONS
typedef struct ServiceFDStore { typedef struct ServiceFDStore {
Service *service; Service *service;
@ -237,6 +246,10 @@ typedef struct Service {
int reload_signal; int reload_signal;
usec_t reload_begin_usec; usec_t reload_begin_usec;
bool refresh_on_reload_set;
ServiceRefreshOnReload refresh_on_reload_flags;
ServiceRefreshOnReload refreshed_mask;
OOMPolicy oom_policy; OOMPolicy oom_policy;
char *usb_function_descriptors; char *usb_function_descriptors;
@ -288,6 +301,10 @@ DECLARE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
DECLARE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode); DECLARE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode);
ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) _pure_;
int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret);
int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret);
DEFINE_CAST(SERVICE, Service); DEFINE_CAST(SERVICE, Service);
/* Only exported for unit tests */ /* Only exported for unit tests */

View File

@ -141,33 +141,33 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u
} }
const MetricFamily metric_family_table[] = { const MetricFamily metric_family_table[] = {
// Keep metrics ordered alphabetically /* Keep metrics ordered alphabetically */
{ {
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "nrestarts", .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "nRestarts",
.description = "Per unit metric: number of restarts", .description = "Per unit metric: number of restarts",
.type = METRIC_FAMILY_TYPE_COUNTER, .type = METRIC_FAMILY_TYPE_COUNTER,
.generate = nrestarts_build_json, .generate = nrestarts_build_json,
}, },
{ {
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_active_state", .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unitActiveState",
.description = "Per unit metric: active state", .description = "Per unit metric: active state",
.type = METRIC_FAMILY_TYPE_STRING, .type = METRIC_FAMILY_TYPE_STRING,
.generate = unit_active_state_build_json, .generate = unit_active_state_build_json,
}, },
{ {
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_load_state", .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unitLoadState",
.description = "Per unit metric: load state", .description = "Per unit metric: load state",
.type = METRIC_FAMILY_TYPE_STRING, .type = METRIC_FAMILY_TYPE_STRING,
.generate = unit_load_state_build_json, .generate = unit_load_state_build_json,
}, },
{ {
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "units_by_state_total", .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unitsByStateTotal",
.description = "Total number of units of different state", .description = "Total number of units of different state",
.type = METRIC_FAMILY_TYPE_GAUGE, .type = METRIC_FAMILY_TYPE_GAUGE,
.generate = units_by_state_total_build_json, .generate = units_by_state_total_build_json,
}, },
{ {
.name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "units_by_type_total", .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "unitsByTypeTotal",
.description = "Total number of units of different types", .description = "Total number of units of different types",
.type = METRIC_FAMILY_TYPE_GAUGE, .type = METRIC_FAMILY_TYPE_GAUGE,
.generate = units_by_type_total_build_json, .generate = units_by_type_total_build_json,

View File

@ -351,10 +351,11 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
#define CASE_F_19(X, ...) case X: CASE_F_18( __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_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
#define CASE_F_21(X, ...) case X: CASE_F_20( __VA_ARGS__) #define CASE_F_21(X, ...) case X: CASE_F_20( __VA_ARGS__)
#define CASE_F_22(X, ...) case X: CASE_F_21( __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,_21,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,_22,NAME,...) NAME
#define FOR_EACH_MAKE_CASE(...) \ #define FOR_EACH_MAKE_CASE(...) \
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, \ GET_CASE_F(__VA_ARGS__,CASE_F_22,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) \ 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__) (__VA_ARGS__)
@ -364,7 +365,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) \ /* 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. */ \ * 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__ }; \ static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
assert_cc(ELEMENTSOF(__assert_in_set) <= 21); \ assert_cc(ELEMENTSOF(__assert_in_set) <= 22); \
switch (x) { \ switch (x) { \
FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \ FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \
_found = true; \ _found = true; \

View File

@ -13,6 +13,7 @@
#include "alloc-util.h" #include "alloc-util.h"
#include "errno-util.h" #include "errno-util.h"
#include "escape.h"
#include "extract-word.h" #include "extract-word.h"
#include "fd-util.h" #include "fd-util.h"
#include "fs-util.h" #include "fs-util.h"
@ -633,7 +634,11 @@ static int pid_notify_with_fds_internal(
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpectedly received data on notify socket."); return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpectedly received data on notify socket.");
} }
log_debug("Notify message sent to '%s': \"%s\"", e, state); if (DEBUG_LOGGING) {
_cleanup_free_ char *escaped = xescape_full(state, "\"", /* console_width = */ SIZE_MAX, XESCAPE_8_BIT);
log_debug("Notify message sent to '%s': \"%s\"", e, escaped ?: state);
}
return 1; return 1;
} }

View File

@ -1465,7 +1465,7 @@ static int install_chroot_dropin(
ext->path, ext->path,
/* With --force tell PID1 to avoid enforcing that the image <name> and /* With --force tell PID1 to avoid enforcing that the image <name> and
* extension-release.<name> have to match. */ * extension-release.<name> have to match. */
!IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) && !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) &&
FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ? FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ?
":x-systemd.relax-extension-release-check\n" : ":x-systemd.relax-extension-release-check\n" :
"\n", "\n",
@ -1475,7 +1475,7 @@ static int install_chroot_dropin(
"LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n")) "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n"))
return -ENOMEM; return -ENOMEM;
if (pinned_ext_image_policy) { if (pinned_ext_image_policy && !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) {
_cleanup_free_ char *policy_str = NULL; _cleanup_free_ char *policy_str = NULL;
r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str); r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str);

View File

@ -3007,7 +3007,9 @@ static int partition_read_definition(
} }
/* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */
if ((partition_designator_is_verity_hash(p->type.designator) || p->verity == VERITY_DATA) && p->read_only < 0) if ((partition_designator_is_verity_hash(p->type.designator) ||
partition_designator_is_verity_sig(p->type.designator) ||
IN_SET(p->verity, VERITY_DATA, VERITY_SIG)) && p->read_only < 0)
p->read_only = true; p->read_only = true;
/* Default to "growfs" on, unless read-only */ /* Default to "growfs" on, unless read-only */

View File

@ -1172,6 +1172,56 @@ static int bus_append_import_credential(sd_bus_message *m, const char *field, co
return 1; return 1;
} }
static int bus_append_refresh_on_reload(sd_bus_message *m, const char *field, const char *eq) {
int r;
r = sd_bus_message_open_container(m, 'r', "sv");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_basic(m, 's', field);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'v', "a(bs)");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'a', "(bs)");
if (r < 0)
return bus_log_create_error(r);
bool invert = *eq == '~';
for (const char *p = eq + invert;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, 0);
if (r < 0)
return parse_log_error(r, field, eq);
if (r == 0)
break;
r = sd_bus_message_append(m, "(bs)", invert, word);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_close_container(m);
if (r < 0)
return bus_log_create_error(r);
return 1;
}
static int bus_append_log_extra_fields(sd_bus_message *m, const char *field, const char *eq) { static int bus_append_log_extra_fields(sd_bus_message *m, const char *field, const char *eq) {
int r; int r;
@ -2665,6 +2715,7 @@ static const BusProperty service_properties[] = {
{ "SuccessExitStatus", bus_append_exit_status }, { "SuccessExitStatus", bus_append_exit_status },
{ "OpenFile", bus_append_open_file }, { "OpenFile", bus_append_open_file },
{ "ReloadSignal", bus_append_signal_from_string }, { "ReloadSignal", bus_append_signal_from_string },
{ "RefreshOnReload", bus_append_refresh_on_reload },
{} {}
}; };

View File

@ -325,6 +325,8 @@ bool gpt_partition_type_knows_read_only(GptPartitionType type) {
/* pretty much implied, but let's set the bit to make things really clear */ /* pretty much implied, but let's set the bit to make things really clear */
PARTITION_ROOT_VERITY, PARTITION_ROOT_VERITY,
PARTITION_USR_VERITY, PARTITION_USR_VERITY,
PARTITION_ROOT_VERITY_SIG,
PARTITION_USR_VERITY_SIG,
PARTITION_HOME, PARTITION_HOME,
PARTITION_SRV, PARTITION_SRV,
PARTITION_VAR, PARTITION_VAR,

View File

@ -45,8 +45,8 @@ typedef enum UserRecordLoadFlags UserRecordLoadFlags;
typedef enum UserStorage UserStorage; typedef enum UserStorage UserStorage;
typedef struct Bitmap Bitmap; typedef struct Bitmap Bitmap;
typedef struct BPFProgram BPFProgram;
typedef struct BootConfig BootConfig; typedef struct BootConfig BootConfig;
typedef struct BPFProgram BPFProgram;
typedef struct BusObjectImplementation BusObjectImplementation; typedef struct BusObjectImplementation BusObjectImplementation;
typedef struct CalendarSpec CalendarSpec; typedef struct CalendarSpec CalendarSpec;
typedef struct Condition Condition; typedef struct Condition Condition;

View File

@ -18,7 +18,7 @@ static SD_VARLINK_DEFINE_ERROR(NoSuchMetric);
static SD_VARLINK_DEFINE_METHOD_FULL( static SD_VARLINK_DEFINE_METHOD_FULL(
List, List,
SD_VARLINK_REQUIRES_MORE, SD_VARLINK_REQUIRES_MORE,
SD_VARLINK_FIELD_COMMENT("Metric name, e.g. io.systemd.Manager.units_by_type_total or io.systemd.Manager.unit_active_state"), SD_VARLINK_FIELD_COMMENT("Metric name, e.g. io.systemd.Manager.unitsByTypeTotal or io.systemd.Manager.unitActiveState"),
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
/* metric value has various types depending on MetricFamilyType and actual data double/int/uint */ /* metric value has various types depending on MetricFamilyType and actual data double/int/uint */
SD_VARLINK_FIELD_COMMENT("Metric value"), SD_VARLINK_FIELD_COMMENT("Metric value"),

View File

@ -1171,7 +1171,7 @@ static int run_callout(
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial /* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
* and pending instances which are about to be installed (in which case, transfer_acquire_instance() is * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
* skipped). */ * skipped). */
static int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) { int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
_cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL; _cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
int r; int r;
@ -1260,16 +1260,16 @@ static int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMe
return 0; return 0;
} }
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) { int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata) {
_cleanup_free_ char *digest = NULL; _cleanup_free_ char *digest = NULL;
char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1]; char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
const char *where = NULL; const char *where = NULL;
InstanceMetadata f;
Instance *existing; Instance *existing;
int r; int r;
assert(t); assert(t);
assert(i); assert(i);
assert(f);
assert(i->resource == &t->source); assert(i->resource == &t->source);
assert(cb); assert(cb);
@ -1282,11 +1282,6 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
return 0; return 0;
} }
/* Compute up the temporary paths */
r = transfer_compute_temporary_paths(t, i, &f);
if (r < 0)
return r;
if (RESOURCE_IS_FILESYSTEM(t->target.type)) { if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
r = mkdir_parents(t->temporary_partial_path, 0755); r = mkdir_parents(t->temporary_partial_path, 0755);
if (r < 0) if (r < 0)
@ -1502,10 +1497,10 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
assert(t->temporary_pending_path); assert(t->temporary_pending_path);
/* Apply file attributes if set */ /* Apply file attributes if set */
if (f.mtime != USEC_INFINITY) { if (f->mtime != USEC_INFINITY) {
struct timespec ts; struct timespec ts;
timespec_store(&ts, f.mtime); timespec_store(&ts, f->mtime);
if (utimensat(AT_FDCWD, t->temporary_partial_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0) if (utimensat(AT_FDCWD, t->temporary_partial_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_partial_path); return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_partial_path);
@ -1513,12 +1508,12 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
need_sync = true; need_sync = true;
} }
if (f.mode != MODE_INVALID) { if (f->mode != MODE_INVALID) {
/* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
* kernels don't support that however, in that case we fall back to chmod(). Not as * kernels don't support that however, in that case we fall back to chmod(). Not as
* safe, but shouldn't be a problem, given that we don't create symlinks here. */ * safe, but shouldn't be a problem, given that we don't create symlinks here. */
if (fchmodat(AT_FDCWD, t->temporary_partial_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 && if (fchmodat(AT_FDCWD, t->temporary_partial_path, f->mode, AT_SYMLINK_NOFOLLOW) < 0 &&
(!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f.mode) < 0)) (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f->mode) < 0))
return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path); return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path);
need_sync = true; need_sync = true;
@ -1536,7 +1531,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path); return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path);
} }
t->install_read_only = f.read_only; t->install_read_only = f->read_only;
/* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate its ready to install. */ /* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate its ready to install. */
log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path); log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
@ -1557,28 +1552,28 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
return r; return r;
t->partition_change = PARTITION_LABEL; t->partition_change = PARTITION_LABEL;
if (f.partition_uuid_set) { if (f->partition_uuid_set) {
t->partition_info.uuid = f.partition_uuid; t->partition_info.uuid = f->partition_uuid;
t->partition_change |= PARTITION_UUID; t->partition_change |= PARTITION_UUID;
} }
if (f.partition_flags_set) { if (f->partition_flags_set) {
t->partition_info.flags = f.partition_flags; t->partition_info.flags = f->partition_flags;
t->partition_change |= PARTITION_FLAGS; t->partition_change |= PARTITION_FLAGS;
} }
if (f.no_auto >= 0) { if (f->no_auto >= 0) {
t->partition_info.no_auto = f.no_auto; t->partition_info.no_auto = f->no_auto;
t->partition_change |= PARTITION_NO_AUTO; t->partition_change |= PARTITION_NO_AUTO;
} }
if (f.read_only >= 0) { if (f->read_only >= 0) {
t->partition_info.read_only = f.read_only; t->partition_info.read_only = f->read_only;
t->partition_change |= PARTITION_READ_ONLY; t->partition_change |= PARTITION_READ_ONLY;
} }
if (f.growfs >= 0) { if (f->growfs >= 0) {
t->partition_info.growfs = f.growfs; t->partition_info.growfs = f->growfs;
t->partition_change |= PARTITION_GROWFS; t->partition_change |= PARTITION_GROWFS;
} }

View File

@ -66,7 +66,8 @@ int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version); int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata); int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f);
int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata);
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i); int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i);
int transfer_install_instance(Transfer *t, Instance *i, const char *root); int transfer_install_instance(Transfer *t, Instance *i, const char *root);

View File

@ -1055,6 +1055,23 @@ static int context_acquire(
log_info("Selected update '%s' for install.", us->version); log_info("Selected update '%s' for install.", us->version);
_cleanup_free_ InstanceMetadata *metadata = new0(InstanceMetadata, c->n_transfers);
if (!metadata)
return log_oom();
/* Compute up the temporary paths before vacuuming so we don't vacuum anything if we fail to compute
* any paths because of failed validations (e.g. exceeding the gpt partition label size). */
for (size_t i = 0; i < c->n_transfers; i++) {
Instance *inst = us->instances[i];
Transfer *t = c->transfers[i];
assert(inst);
r = transfer_compute_temporary_paths(t, inst, metadata + i);
if (r < 0)
return r;
}
(void) sd_notifyf(/* unset_environment= */ false, (void) sd_notifyf(/* unset_environment= */ false,
"READY=1\n" "READY=1\n"
"X_SYSUPDATE_VERSION=%s\n" "X_SYSUPDATE_VERSION=%s\n"
@ -1090,7 +1107,7 @@ static int context_acquire(
continue; continue;
} }
r = transfer_acquire_instance(t, inst, context_on_acquire_progress, c); r = transfer_acquire_instance(t, inst, metadata + i, context_on_acquire_progress, c);
if (r < 0) if (r < 0)
return r; return r;
} }

View File

@ -21,6 +21,7 @@
#include "open-file.h" #include "open-file.h"
#include "pcre2-util.h" #include "pcre2-util.h"
#include "rm-rf.h" #include "rm-rf.h"
#include "service.h"
#include "set.h" #include "set.h"
#include "specifier.h" #include "specifier.h"
#include "string-util.h" #include "string-util.h"
@ -1033,6 +1034,86 @@ TEST(config_parse_open_file) {
ASSERT_NULL(of); ASSERT_NULL(of);
} }
TEST(config_parse_service_refresh_on_reload) {
ServiceRefreshOnReload flags;
_cleanup_strv_free_ char **l = NULL;
int r;
r = service_refresh_on_reload_from_string_many("extensions", &flags);
ASSERT_OK(r);
ASSERT_EQ(flags, SERVICE_RELOAD_EXTENSIONS);
ASSERT_OK(service_refresh_on_reload_to_strv(flags, &l));
ASSERT_TRUE(strv_equal(l, STRV_MAKE("extensions")));
l = strv_free(l);
r = service_refresh_on_reload_from_string_many("credentials extensions", &flags);
ASSERT_OK(r);
ASSERT_EQ(flags, SERVICE_RELOAD_EXTENSIONS|SERVICE_RELOAD_CREDENTIALS);
ASSERT_OK(service_refresh_on_reload_to_strv(flags, &l));
ASSERT_TRUE(strv_equal(l, STRV_MAKE("extensions", "credentials")));
ASSERT_ERROR(service_refresh_on_reload_from_string_many("hoge", &flags), EINVAL);
_cleanup_(manager_freep) Manager *m = NULL;
_cleanup_(unit_freep) Unit *u = NULL;
r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
if (manager_errno_skip_test(r)) {
log_notice_errno(r, "Skipping test: manager_new: %m");
return;
}
ASSERT_OK(r);
ASSERT_OK(manager_startup(m, NULL, NULL, NULL));
ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service)));
ASSERT_OK_ZERO(unit_add_name(u, "foobar.service"));
ASSERT_FALSE(SERVICE(u)->refresh_on_reload_set);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "no",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, 0);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "yes",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, _SERVICE_REFRESH_ON_RELOAD_ALL);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "~extensions",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, _SERVICE_REFRESH_ON_RELOAD_ALL & ~SERVICE_RELOAD_EXTENSIONS);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "~extensions credentials",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, 0);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "",
NULL, u));
ASSERT_FALSE(SERVICE(u)->refresh_on_reload_set);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "~extensions",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~SERVICE_RELOAD_EXTENSIONS);
}
static int intro(void) { static int intro(void) {
if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) if (enter_cgroup_subroot(NULL) == -ENOMEDIUM)
return log_tests_skipped("cgroupfs not available"); return log_tests_skipped("cgroupfs not available");

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
OUTPUT_FILE="$1"
dump_creds_tree() {
grep . "$CREDENTIALS_DIRECTORY"/* >"$OUTPUT_FILE"
}
on_sighup() {
systemd-notify --reloading
dump_creds_tree
systemd-notify --ready
}
trap on_sighup SIGHUP
export SYSTEMD_LOG_LEVEL=debug
dump_creds_tree
systemd-notify --ready
sleep infinity &
while :; do
wait || :
done

View File

@ -351,6 +351,7 @@ if install_tests
'integration-tests/TEST-30-ONCLOCKCHANGE/TEST-30-ONCLOCKCHANGE.units', 'integration-tests/TEST-30-ONCLOCKCHANGE/TEST-30-ONCLOCKCHANGE.units',
'integration-tests/TEST-38-FREEZER/TEST-38-FREEZER.units', 'integration-tests/TEST-38-FREEZER/TEST-38-FREEZER.units',
'integration-tests/TEST-52-HONORFIRSTSHUTDOWN/TEST-52-HONORFIRSTSHUTDOWN.units', 'integration-tests/TEST-52-HONORFIRSTSHUTDOWN/TEST-52-HONORFIRSTSHUTDOWN.units',
'integration-tests/TEST-54-CREDS/TEST-54-CREDS.units',
'integration-tests/TEST-55-OOMD/TEST-55-OOMD.units', 'integration-tests/TEST-55-OOMD/TEST-55-OOMD.units',
'integration-tests/TEST-62-RESTRICT-IFACES/TEST-62-RESTRICT-IFACES.units', 'integration-tests/TEST-62-RESTRICT-IFACES/TEST-62-RESTRICT-IFACES.units',
'integration-tests/TEST-63-PATH/TEST-63-PATH.units', 'integration-tests/TEST-63-PATH/TEST-63-PATH.units',

View File

@ -127,6 +127,19 @@ test -L /run/systemd/system.attached/app0.service.d/10-profile.conf
test -L /run/systemd/system.attached/app1.service.d/10-profile.conf test -L /run/systemd/system.attached/app1.service.d/10-profile.conf
portablectl detach --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1 portablectl detach --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
# Ensure that --force works with directory extensions, and that ExtensionDirectories=
# is not decorated with :x-systemd.relax-extension-release-check
portablectl "${ARGS[@]}" attach --force --copy=symlink --now --runtime --extension /tmp/app0 /tmp/rootdir app0
systemctl is-active app0.service
status="$(portablectl is-attached --extension app0 rootdir)"
[[ "${status}" == "running-runtime" ]]
grep -q -F "ExtensionDirectories=" /run/systemd/system.attached/app0.service.d/20-portable.conf
(! grep -q -F "x-systemd.relax-extension-release-check" /run/systemd/system.attached/app0.service.d/20-portable.conf)
portablectl detach --now --runtime --extension /tmp/app0 /tmp/rootdir app0
# Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce. # Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce.
# Provides coverage for https://github.com/systemd/systemd/issues/23481 # Provides coverage for https://github.com/systemd/systemd/issues/23481
portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0 portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0

View File

@ -189,6 +189,10 @@ systemctl is-active app0.service
status="$(portablectl is-attached --extension /tmp/app10.raw /usr/share/minimal_0.raw)" status="$(portablectl is-attached --extension /tmp/app10.raw /usr/share/minimal_0.raw)"
[[ "${status}" == "running-runtime" ]] [[ "${status}" == "running-runtime" ]]
# Ensure --force adds relax-extension-release-check for image extensions
grep -q -F "ExtensionImages=" /run/systemd/system.attached/app0.service.d/20-portable.conf
grep -q -F "ExtensionImagePolicy=" /run/systemd/system.attached/app0.service.d/20-portable.conf
portablectl inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 | grep -F "Extension Release: /tmp/app10.raw" >/dev/null portablectl inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 | grep -F "Extension Release: /tmp/app10.raw" >/dev/null
# Ensure that we can detach even when an image has been deleted already (stop the unit manually as # Ensure that we can detach even when an image has been deleted already (stop the unit manually as
@ -245,3 +249,19 @@ status="$(portablectl is-attached --extension app1 minimal_0)"
[[ "${status}" == "attached-runtime" ]] [[ "${status}" == "attached-runtime" ]]
portablectl detach --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app portablectl detach --runtime --extension /tmp/app1.raw /usr/share/minimal_0.raw app
# Ensure that when mixing directory and image extensions, ExtensionImagePolicy= is only
# applied to image extensions and not to directory extensions
mkdir -p /tmp/app1
mount /tmp/app1.raw /tmp/app1
portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime --extension /tmp/app1 --extension /tmp/app0.raw /usr/share/minimal_0.raw app0
systemctl is-active app0.service
grep -q -F "ExtensionDirectories=/tmp/app1" /run/systemd/system.attached/app0.service.d/20-portable.conf
grep -q -F "ExtensionImages=/tmp/app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
# ExtensionImagePolicy= should appear exactly once (for the image, not the directory)
[[ "$(grep -c -F "ExtensionImagePolicy=" /run/systemd/system.attached/app0.service.d/20-portable.conf)" == "1" ]]
portablectl detach --now --runtime --extension /tmp/app1 --extension /tmp/app0.raw /usr/share/minimal_0.raw app0
umount -l /tmp/app1

View File

@ -571,4 +571,106 @@ systemd-run -M testuser@ --user --wait -p ImportCredential=brummbaer \
kill "$PID" kill "$PID"
# Now test credential refreshing
UNIT_NAME="TEST-54-CREDS-refreshing-$RANDOM.service"
OUTPUT_FILE="/tmp/$UNIT_NAME.out"
POST_FLAG_FILE="/tmp/$UNIT_NAME.post-flag"
cat >/run/systemd/system/"$UNIT_NAME" <<EOF
[Service]
Type=notify-reload
ImportCredential=test.creds.*
ExecStart=/usr/lib/systemd/tests/testdata/TEST-54-CREDS.units/refresh.sh $OUTPUT_FILE
EOF
systemctl start "$UNIT_NAME"
ls -l /run/credentials/"$UNIT_NAME"/
echo "neu" >/run/credstore/test.creds.new-refresh-1
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1 ]]
systemctl reload "$UNIT_NAME"
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1 ]]
(! grep -q "test.creds.new-refresh-1" "$OUTPUT_FILE")
echo "RefreshOnReload=credentials" >>/run/systemd/system/"$UNIT_NAME"
systemctl daemon-reload
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-1 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
systemctl stop "$UNIT_NAME"
cat >>/run/systemd/system/"$UNIT_NAME" <<EOF
ProtectSystem=strict
ReadWritePaths=/tmp
ExecReloadPost=bash -c 'diff $OUTPUT_FILE <(grep . %d/*) || rm -v $POST_FLAG_FILE'
EOF
systemctl daemon-reload
systemctl start "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-1 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
systemctl reload "$UNIT_NAME"
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
echo "2" >/run/credstore/test.creds.new-refresh-2
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-2 ]]
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-2 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-2
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
echo "3" >/run/credstore/test.creds.new-refresh-3
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-3 ]]
rm "$OUTPUT_FILE"
systemctl edit --runtime --stdin "$UNIT_NAME" <<EOF
[Service]
Type=notify
ExecReloadPost=
EOF
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-3 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-3
[[ ! -e "$OUTPUT_FILE" ]]
echo "RefreshOnReload=no" >>/run/systemd/system/"$UNIT_NAME"
systemctl daemon-reload
assert_eq "$(systemctl show "$UNIT_NAME" -P CanReload)" "no"
systemctl revert "$UNIT_NAME"
assert_eq "$(systemctl show "$UNIT_NAME" -P CanReload)" "yes"
echo "BOGUS" >/run/credstore/test.creds.refresh-bogus
touch "$POST_FLAG_FILE"
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-3 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-3
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.refresh-bogus ]]
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
[[ ! -e "$POST_FLAG_FILE" ]]
OUTPUT_FILE_USER="/tmp/TEST-54-CREDS-refreshing-user.out"
systemd-notify --fork -- \
systemd-run -M testuser@ --user --wait \
--unit=brummbaer-refresh.service \
--service-type=notify-reload \
-p NotifyAccess=all \
-p 'ImportCredential=brummbaer*' \
-p RefreshOnReload=credentials \
-p ProtectSystem=strict \
-p ReadWritePaths=/tmp \
/usr/lib/systemd/tests/testdata/TEST-54-CREDS.units/refresh.sh "$OUTPUT_FILE_USER"
[[ -f "$TESTUSER_CRED_DIR/brummbaer-refresh.service/brummbaer" ]]
diff "$OUTPUT_FILE_USER" <(grep . "$TESTUSER_CRED_DIR"/brummbaer-refresh.service/*)
run0 -u testuser --pipe -i \
--property=EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
'mkdir -p .config/credstore && echo "refreshed" >.config/credstore/brummbaer.refreshed'
systemctl -M testuser@ --user reload brummbaer-refresh.service
assert_eq "$(cat "$TESTUSER_CRED_DIR"/brummbaer-refresh.service/brummbaer.refreshed)" "refreshed"
diff "$OUTPUT_FILE_USER" <(grep . "$TESTUSER_CRED_DIR"/brummbaer-refresh.service/*)
touch /testok touch /testok

View File

@ -204,7 +204,7 @@ MatchPattern=part2-@v.raw.gz
[Target] [Target]
Type=partition Type=partition
Path=$blockdev Path=$blockdev
MatchPattern=part2-@v MatchPattern=a-very-long-partition-name-@v
MatchPartitionType=root-x86-64-verity MatchPartitionType=root-x86-64-verity
EOF EOF
@ -397,7 +397,7 @@ MatchPattern=part2-@v.raw.gz
[Target] [Target]
Type=partition Type=partition
Path=$blockdev Path=$blockdev
MatchPattern=part2-@v MatchPattern=a-very-long-partition-name-@v
MatchPartitionType=root-x86-64-verity MatchPartitionType=root-x86-64-verity
EOF EOF