diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index e6c2c6fdab8..fbed12c4be9 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -2833,6 +2833,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly as ExtraFileDescriptorNames = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i ReloadSignal = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as RefreshOnReload = ['...', ...];
readonly t ExecMainStartTimestamp = ...;
readonly t ExecMainStartTimestampMonotonic = ...;
readonly t ExecMainExitTimestamp = ...;
@@ -3534,6 +3536,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -4180,6 +4184,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -12514,8 +12520,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
ManagedOOMKills,
ExecReloadPost, and
ExecReloadPostEx were added in version 259.
- BindNetworkInterface, and
- MemoryTHP were added in version 260.
+ BindNetworkInterface,
+ MemoryTHP, and
+ RefreshOnReload were added in version 260.
Socket Unit Objects
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 106e1dcdb50..0ba3e011f21 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -572,14 +572,13 @@
To disable the safety check that the extension-release file name matches the image file name, the
x-systemd.relax-extension-release-check mount option may be appended.
- This option can be used together with a service type and
- systemd.v7
- to manage configuration updates. When such a service carrying confext images is reloaded via
- systemctl reload foo.service or equivalent D-Bus method, the confext itself will
- be reloaded to pick up any changes. This only applies to confext extensions. Note that in case a
- service has this configuration enabled at first, and then it is subsequently removed in an update
- followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service
- restart is required instead. See
+ If a service employs this option with
+ systemd.v7,
+ and has enabled (the default), the confexts will
+ be refreshed to pick up any changes on service reload. This only applies to confext extensions.
+ Note that in case a service has this configuration enabled at first, and then it is subsequently
+ removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op,
+ and a full service restart is required instead. See
systemd.service5
also for details.
@@ -630,14 +629,13 @@
or the host. See:
os-release5.
- This option can be used together with a service type and
- systemd.v7
- to manage configuration updates. When such a service carrying confext directories is reloaded via
- systemctl reload foo.service or equivalent D-Bus method, the confext itself will
- be reloaded to pick up any changes. This only applies to confext extensions. Note that in case a
- service has this configuration enabled at first, and then it is subsequently removed in an update
- followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service
- restart is required instead. See
+ If a service employs this option with
+ systemd.v7,
+ and has enabled (the default), the confexts will
+ be refreshed to pick up any changes on service reload. This only applies to confext extensions.
+ Note that in case a service has this configuration enabled at first, and then it is subsequently
+ removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op,
+ and a full service restart is required instead. See
systemd.service5
also for details.
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index cea9384399d..8b9994a46ab 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -1349,6 +1349,28 @@
+
+ RefreshOnReload=
+
+ Takes a boolean argument, or a list of resources defined in
+ systemd.exec5.
+ Possible values are and , separated by space.
+ Prepending the list with a single tilde character (~) inverts the effect.
+ Defaults to . An empty assignment resets the list to default. If enabled,
+ the corresponding resources (/
+ for and //
+ (along with their counterparts)
+ for ) will be refreshed on service reload. If ,
+ all resources listed above that are used by the service shall be refreshed.
+
+ Specially, if this option is set explicitly, and the respective resources are in use,
+ the service may be reloaded without any actual reload mechanism (
+ or ) for notifying the main process, in which case the reload
+ is considered complete immediately after refreshing.
+
+
+
+
Check
diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c
index c3b84ea7840..69bd4945dfb 100644
--- a/src/basic/namespace-util.c
+++ b/src/basic/namespace-util.c
@@ -262,6 +262,71 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
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 r;
diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h
index 32e64fa9e68..994125818f9 100644
--- a/src/basic/namespace-util.h
+++ b/src/basic/namespace-util.h
@@ -47,6 +47,7 @@ int namespace_open(
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_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 is_our_namespace(int fd, NamespaceType type);
diff --git a/src/basic/process-util.c b/src/basic/process-util.c
index 9c44193b67c..52af6a01c8a 100644
--- a/src/basic/process-util.c
+++ b/src/basic/process-util.c
@@ -1778,6 +1778,7 @@ int namespace_fork_full(
int netns_fd,
int userns_fd,
int root_fd,
+ bool delegated,
PidRef *ret) {
_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]);
- 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) {
log_full_errno(prio, r, "Failed to join namespace: %m");
report_errno_and_exit(errno_pipe_fd[1], r);
diff --git a/src/basic/process-util.h b/src/basic/process-util.h
index 46a5612048f..66bb194bac0 100644
--- a/src/basic/process-util.h
+++ b/src/basic/process-util.h
@@ -201,6 +201,7 @@ int namespace_fork_full(
int netns_fd,
int userns_fd,
int root_fd,
+ bool delegated,
PidRef *ret);
static inline int namespace_fork(
@@ -215,7 +216,7 @@ static inline int namespace_fork(
PidRef *ret) {
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);
}
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
index e8afaf1c3f7..6da61c510d8 100644
--- a/src/basic/unit-def.c
+++ b/src/basic/unit-def.c
@@ -223,6 +223,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_RUNNING] = "running",
[SERVICE_EXITED] = "exited",
[SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
+ [SERVICE_REFRESH_CREDENTIALS] = "refresh-credentials",
[SERVICE_RELOAD] = "reload",
[SERVICE_RELOAD_SIGNAL] = "reload-signal",
[SERVICE_RELOAD_NOTIFY] = "reload-notify",
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
index 0324dc5a374..6ed4c0cbb7a 100644
--- a/src/basic/unit-def.h
+++ b/src/basic/unit-def.h
@@ -132,6 +132,7 @@ typedef enum ServiceState {
SERVICE_RUNNING,
SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
+ SERVICE_REFRESH_CREDENTIALS, /* ditto, but for credentials */
SERVICE_RELOAD, /* Reloading via ExecReload= */
SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */
SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index f95b5259ff9..9b1b1f77218 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -30,6 +30,7 @@
#include "signal-util.h"
#include "stat-util.h"
#include "string-util.h"
+#include "strv.h"
#include "unit.h"
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);
}
+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(
sd_bus *bus,
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("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("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_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;
}
+ 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;
}
diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c
index 1971aafee88..07d83ac7a6a 100644
--- a/src/core/exec-credential.c
+++ b/src/core/exec-credential.c
@@ -16,19 +16,25 @@
#include "iovec-util.h"
#include "label-util.h"
#include "log.h"
+#include "manager.h"
#include "mkdir-label.h"
#include "mount-util.h"
#include "mountpoint-util.h"
+#include "namespace-util.h"
#include "ordered-set.h"
#include "path-lookup.h"
#include "path-util.h"
+#include "pidref.h"
+#include "process-util.h"
#include "random-util.h"
#include "recurse-dir.h"
#include "rm-rf.h"
#include "siphash24.h"
+#include "socket-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
+#include "unit.h"
#include "user-util.h"
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;
}
+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 {
CREDENTIAL_SEARCH_PATH_TRUSTED,
CREDENTIAL_SEARCH_PATH_ENCRYPTED,
@@ -318,11 +352,15 @@ typedef enum CredentialSearchPath {
_CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL,
} 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;
int r;
- assert(params);
+ assert(context);
assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX);
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). */
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)
return r;
_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)
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)) {
- r = strv_extend(&l, params->received_credentials_directory);
+ r = strv_extend(&l, context->received_credentials_directory);
if (r < 0)
return r;
_cleanup_strv_free_ char **add = NULL;
- r = credential_store_path(params->runtime_scope, &add);
+ r = credential_store_path(context->scope, &add);
if (r < 0)
return r;
@@ -369,23 +407,6 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
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(
int dfd,
const char *id,
@@ -431,7 +452,7 @@ static int write_credential(
}
static int maybe_decrypt_and_write_credential(
- struct load_cred_args *args,
+ LoadCredentialArguments *args,
const char *id,
const char *data,
size_t size,
@@ -449,7 +470,7 @@ static int maybe_decrypt_and_write_credential(
if (args->encrypted) {
CredentialFlags flags = 0; /* only allow user creds in user scope */
- switch (args->params->runtime_scope) {
+ switch (args->context->scope) {
case RUNTIME_SCOPE_SYSTEM:
/* 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;
- if (!args->always_ipc) {
+ if (!args->context->always_ipc) {
r = decrypt_credential_and_warn(
id,
now(CLOCK_REALTIME),
@@ -506,7 +527,7 @@ static int maybe_decrypt_and_write_credential(
if (add > args->left)
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)
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(
- struct load_cred_args *args,
+ LoadCredentialArguments *args,
const ExecImportCredential *ic,
char * const *search_path,
ReadFullFileFlags flags) {
@@ -595,7 +616,7 @@ static int load_credential_glob(
}
static int load_credential(
- struct load_cred_args *args,
+ LoadCredentialArguments *args,
const char *id,
int read_dfd,
const char *path) {
@@ -611,9 +632,8 @@ static int load_credential(
assert(args);
assert(args->context);
- assert(args->params);
- assert(args->unit);
- assert(args->write_dfd >= 0);
+ assert(args->context->exec_context);
+ assert(args->context->unit);
assert(id);
assert(read_dfd >= 0 || read_dfd == AT_FDCWD);
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
* 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;
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
* 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)
return r;
@@ -695,17 +715,20 @@ static int load_credential(
else
assert_not_reached();
- if (r == -ENOENT && (missing_ok || hashmap_contains(args->context->set_credentials, id))) {
- /* Make a missing inherited credential non-fatal, let's just continue. After all apps
- * 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
- * worse than when we handle the error here and make it fatal.
- *
- * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
- * we are fine, too. */
- log_full_errno(hashmap_contains(args->context->set_credentials, id) ? LOG_DEBUG : LOG_INFO,
- r, "Couldn't read inherited credential '%s', skipping: %m", path);
- return 0;
+ if (r == -ENOENT) {
+ bool in_set_credentials = hashmap_contains(args->context->exec_context->set_credentials, id);
+ if (missing_ok || in_set_credentials) {
+ /* Make a missing inherited credential non-fatal, let's just continue. After all apps
+ * 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
+ * worse than when we handle the error here and make it fatal.
+ *
+ * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
+ * we are fine, too. */
+ 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)
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,
void *userdata) {
- struct load_cred_args *args = ASSERT_PTR(userdata);
+ LoadCredentialArguments *args = ASSERT_PTR(userdata);
_cleanup_free_ char *sub_id = NULL;
int r;
@@ -779,38 +802,27 @@ static bool device_nodes_restricted(
}
static int acquire_credentials(
- const ExecContext *context,
- const CGroupContext *cgroup_context,
- const ExecParameters *params,
- const char *unit,
+ const SetupCredentialsContext *context,
int dfd,
- uid_t uid,
- gid_t gid,
bool ownership_ok) {
int r;
assert(context);
- assert(cgroup_context);
- assert(params);
- assert(unit);
+ assert(context->exec_context);
assert(dfd >= 0);
- struct load_cred_args args = {
+ LoadCredentialArguments args = {
.context = context,
- .params = params,
- .unit = unit,
- .always_ipc = device_nodes_restricted(context, cgroup_context),
+
.write_dfd = dfd,
- .uid = uid,
- .gid = gid,
.ownership_ok = ownership_ok,
.left = CREDENTIALS_TOTAL_SIZE_MAX,
};
/* First, load credentials off disk (or acquire via AF_UNIX socket) */
ExecLoadCredential *lc;
- HASHMAP_FOREACH(lc, context->load_credentials) {
+ HASHMAP_FOREACH(lc, context->exec_context->load_credentials) {
_cleanup_close_ int sub_fd = -EBADF;
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
* override any credentials found earlier. */
ExecImportCredential *ic;
- ORDERED_SET_FOREACH(ic, context->import_credentials) {
+ ORDERED_SET_FOREACH(ic, context->exec_context->import_credentials) {
_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)
return r;
@@ -868,7 +880,7 @@ static int acquire_credentials(
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)
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
* add them, so that they can act as a "default" if the same credential is specified multiple times. */
ExecSetCredential *sc;
- HASHMAP_FOREACH(sc, context->set_credentials) {
+ HASHMAP_FOREACH(sc, context->exec_context->set_credentials) {
args.encrypted = sc->encrypted;
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(
- const ExecContext *context,
- const CGroupContext *cgroup_context,
- const ExecParameters *params,
- const char *unit,
- const char *cred_dir,
- uid_t uid,
- gid_t gid) {
+ const SetupCredentialsContext *context,
+ const char *cred_dir) {
_cleanup_free_ char *t = NULL, *workspace = NULL;
_cleanup_(rm_rf_safep) const char *workspace_rm = NULL;
@@ -948,13 +955,13 @@ static int setup_credentials_plain_dir(
int r;
assert(context);
- assert(params);
- assert(unit);
+ assert(context->unit);
+ assert(context->runtime_prefix);
assert(cred_dir);
/* 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. */
- t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
+ t = path_join(context->runtime_prefix, "systemd/temporary-credentials");
if (!t)
return -ENOMEM;
@@ -962,7 +969,7 @@ static int setup_credentials_plain_dir(
if (r < 0 && r != -EEXIST)
return r;
- workspace = path_join(t, unit);
+ workspace = path_join(t, context->unit);
if (!workspace)
return -ENOMEM;
@@ -973,7 +980,7 @@ static int setup_credentials_plain_dir(
(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)
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
* 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)
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(
- const ExecContext *context,
- const CGroupContext *cgroup_context,
- const ExecParameters *params,
- const char *unit,
- const char *cred_dir,
- uid_t uid,
- gid_t gid) {
+ const SetupCredentialsContext *context,
+ bool may_reuse,
+ const char *cred_dir) {
_cleanup_close_ int fs_fd = -EBADF, mfd = -EBADF, dfd = -EBADF;
bool dir_mounted;
int r;
assert(context);
- assert(params);
- assert(unit);
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);
if (r < 0)
return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", cred_dir);
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);
if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) {
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)
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);
- 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)
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)
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 CGroupContext *cgroup_context,
const ExecParameters *params,
- const char *unit,
uid_t uid,
gid_t gid) {
@@ -1108,7 +1118,6 @@ int exec_setup_credentials(
assert(context);
assert(params);
- assert(unit);
if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context))
return 0;
@@ -1126,7 +1135,7 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST)
return r;
- p = path_join(q, unit);
+ p = path_join(q, params->unit_id);
if (!p)
return -ENOMEM;
@@ -1134,12 +1143,208 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST)
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;
}
+
+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;
+}
diff --git a/src/core/exec-credential.h b/src/core/exec-credential.h
index 056e5173588..89fd12e5bd5 100644
--- a/src/core/exec-credential.h
+++ b/src/core/exec-credential.h
@@ -57,8 +57,9 @@ int exec_setup_credentials(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
- const char *unit,
uid_t uid,
gid_t gid);
+int unit_refresh_credentials(Unit *u);
+
bool mount_point_is_credentials(const char *runtime_prefix, const char *path);
diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c
index a0c101a662d..a0b0a87a659 100644
--- a/src/core/exec-invoke.c
+++ b/src/core/exec-invoke.c
@@ -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]);
}
- 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) {
*exit_status = EXIT_CREDENTIALS;
return log_error_errno(r, "Failed to set up credentials: %m");
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 7f8198ff487..5605445c34e 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -479,6 +479,7 @@ Service.USBFunctionStrings, config_parse_unit_path_printf,
Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy)
Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files)
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') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Service') }}
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index ce90b08e562..d2bfd20fd43 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -4816,6 +4816,55 @@ int config_parse_import_credential(
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(
const char *unit,
const char *filename,
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 3b0ee5fb30c..4677564904c 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -105,6 +105,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_quota);
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_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_namespace_path_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
diff --git a/src/core/manager.c b/src/core/manager.c
index fec11065652..79fa19d976e 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1888,13 +1888,15 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
u = manager_get_unit(m, SPECIAL_DBUS_SERVICE);
if (!u)
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_MOUNTING,
- SERVICE_RELOAD,
- SERVICE_RELOAD_NOTIFY,
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 true;
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 6e9c6a09f87..0ed686f9ece 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -2845,7 +2845,6 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
.mode = MOUNT_BIND,
.read_only = true,
.source_const = p->creds_path,
- .ignore = true,
};
}
diff --git a/src/core/service.c b/src/core/service.c
index ec3fd2ed8c3..255e86cfb56 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -69,6 +69,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
+ [SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = 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_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
+ [SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = 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_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) {
return IN_SET(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_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
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,
SERVICE_CONDITION,
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_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
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,
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_MOUNTING);
}
@@ -754,6 +760,15 @@ static int service_verify(Service *s) {
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;
}
@@ -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))
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
* 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. */
@@ -1296,7 +1316,8 @@ static void service_set_state(Service *s, ServiceState state) {
if (!IN_SET(state,
SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
- SERVICE_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_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
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_POST:
case SERVICE_REFRESH_EXTENSIONS:
+ case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
@@ -1667,11 +1689,16 @@ static Service *service_get_triggering_service(Service *s) {
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
explicitly excluded in service_spawn()) */
ExecFlags flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT;
+ assert(s);
assert(command_id >= 0);
assert(command_id < _SERVICE_EXEC_COMMAND_MAX);
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))
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))
flags |= EXEC_SETENV_RESULT;
@@ -2228,7 +2261,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2340,7 +2373,7 @@ static void service_enter_stop(Service *s, ServiceResult f) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2425,7 +2458,7 @@ static void service_enter_start_post(Service *s) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2535,7 +2568,7 @@ static void service_enter_start(Service *s) {
r = service_spawn(s,
c,
- service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH),
+ service_exec_flags(s, SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH),
timeout,
&pidref);
if (r < 0) {
@@ -2595,7 +2628,7 @@ static void service_enter_start_pre(Service *s) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2631,7 +2664,7 @@ static void service_enter_condition(Service *s) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2735,7 +2768,7 @@ static void service_enter_reload_post(Service *s) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2810,7 +2843,7 @@ static void service_enter_reload(Service *s) {
r = service_spawn(s,
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->control_pid);
if (r < 0) {
@@ -2823,28 +2856,114 @@ static void service_enter_reload(Service *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;
assert(s);
- if (!pidref_is_set(&s->main_pid)) {
- log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
- return false;
+ if (!service_get_effective_reload_credentials(s))
+ return service_enter_reload(s);
+
+ 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);
- if (r < 0)
- log_unit_warning_errno(UNIT(s), r, "Failed to determine if service should reload extensions, assuming false: %m");
- if (r == 0)
- log_unit_debug(UNIT(s), "Service has no extensions to reload.");
- if (r <= 0)
- return false;
+ r = unit_fork_helper_process_full(UNIT(s), "(sd-refresh-creds)", /* into_cgroup = */ false,
+ FORK_ALLOW_DLOPEN, /* allow loading libacl to avoid doing so in pid1 */
+ &worker);
+ if (r < 0) {
+ log_unit_error_errno(UNIT(s), r, "Failed to fork process to refresh credentials in unit's namespace: %m");
+ goto fail;
+ }
+ 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.
// 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)) {
- 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;
}
@@ -2857,9 +2976,9 @@ static void service_enter_refresh_extensions(Service *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))
- return service_enter_reload(s);
+ return service_enter_refresh_credentials(s);
service_unwatch_control_pid(s);
s->control_command = NULL;
@@ -2955,7 +3074,7 @@ static void service_run_next_control(Service *s) {
r = service_spawn(s,
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,
&s->control_pid);
if (r < 0) {
@@ -2986,7 +3105,7 @@ static void service_run_next_main(Service *s) {
r = service_spawn(s,
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,
&pidref);
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);
_fallthrough_;
case SERVICE_REFRESH_EXTENSIONS:
+ case SERVICE_REFRESH_CREDENTIALS:
service_kill_control_process(s);
_fallthrough_;
case SERVICE_CONDITION:
@@ -3166,6 +3286,7 @@ static int service_reload(Unit *u) {
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
s->reload_result = SERVICE_SUCCESS;
+ s->refreshed_mask = 0;
service_enter_refresh_extensions(s);
@@ -3176,7 +3297,10 @@ static bool service_can_reload(Unit *u) {
Service *s = ASSERT_PTR(SERVICE(u));
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) {
@@ -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);
+ 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;
}
@@ -3775,7 +3914,11 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
} else if (streq(key, "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);
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_REFRESH_EXTENSIONS:
+ case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
@@ -4307,7 +4451,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
success,
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 = f;
@@ -4396,10 +4540,21 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
break;
case SERVICE_REFRESH_EXTENSIONS:
- if (f == SERVICE_SUCCESS)
- /* Remounting extensions asynchronously done, proceed to reload */
+ if (f == SERVICE_SUCCESS) {
+ 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);
- else
+ } else
service_reload_finish(s, f);
break;
@@ -4515,6 +4670,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
break;
case SERVICE_REFRESH_EXTENSIONS:
+ case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
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")) {
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);
return;
@@ -4935,7 +5093,8 @@ static void service_notify_message(
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
- SERVICE_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) &&
(!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_RUNNING,
SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
@@ -5390,6 +5550,7 @@ static bool service_needs_console(Unit *u) {
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS,
+ SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
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);
+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 = {
.object_size = sizeof(Service),
.exec_context_offset = offsetof(Service, exec_context),
diff --git a/src/core/service.h b/src/core/service.h
index 167b7891e08..9750b19ce28 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -96,6 +96,15 @@ typedef enum ServiceRestartMode {
_SERVICE_RESTART_MODE_INVALID = -EINVAL,
} 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 {
Service *service;
@@ -237,6 +246,10 @@ typedef struct Service {
int reload_signal;
usec_t reload_begin_usec;
+ bool refresh_on_reload_set;
+ ServiceRefreshOnReload refresh_on_reload_flags;
+ ServiceRefreshOnReload refreshed_mask;
+
OOMPolicy oom_policy;
char *usb_function_descriptors;
@@ -288,6 +301,10 @@ DECLARE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
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);
/* Only exported for unit tests */
diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h
index 9eebbfb2a9a..e8757b1fc37 100644
--- a/src/fundamental/macro-fundamental.h
+++ b/src/fundamental/macro-fundamental.h
@@ -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_20(X, ...) case X: CASE_F_19( __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(...) \
- 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) \
(__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) \
* here to widen the type of x if it is a bit-field as this would otherwise be illegal. */ \
static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
- assert_cc(ELEMENTSOF(__assert_in_set) <= 21); \
+ assert_cc(ELEMENTSOF(__assert_in_set) <= 22); \
switch (x) { \
FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \
_found = true; \
diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c
index 8d7dd0823f7..2ab50287b4f 100644
--- a/src/libsystemd/sd-daemon/sd-daemon.c
+++ b/src/libsystemd/sd-daemon/sd-daemon.c
@@ -13,6 +13,7 @@
#include "alloc-util.h"
#include "errno-util.h"
+#include "escape.h"
#include "extract-word.h"
#include "fd-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.");
}
- 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;
}
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 9a8e31474a3..057a89b8abe 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -1172,6 +1172,56 @@ static int bus_append_import_credential(sd_bus_message *m, const char *field, co
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) {
int r;
@@ -2665,6 +2715,7 @@ static const BusProperty service_properties[] = {
{ "SuccessExitStatus", bus_append_exit_status },
{ "OpenFile", bus_append_open_file },
{ "ReloadSignal", bus_append_signal_from_string },
+ { "RefreshOnReload", bus_append_refresh_on_reload },
{}
};
diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h
index 4804dd9e49e..f35287746b0 100644
--- a/src/shared/shared-forward.h
+++ b/src/shared/shared-forward.h
@@ -45,8 +45,8 @@ typedef enum UserRecordLoadFlags UserRecordLoadFlags;
typedef enum UserStorage UserStorage;
typedef struct Bitmap Bitmap;
-typedef struct BPFProgram BPFProgram;
typedef struct BootConfig BootConfig;
+typedef struct BPFProgram BPFProgram;
typedef struct BusObjectImplementation BusObjectImplementation;
typedef struct CalendarSpec CalendarSpec;
typedef struct Condition Condition;
diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c
index 9ddc8a2eba2..892e471d8ab 100644
--- a/src/test/test-load-fragment.c
+++ b/src/test/test-load-fragment.c
@@ -21,6 +21,7 @@
#include "open-file.h"
#include "pcre2-util.h"
#include "rm-rf.h"
+#include "service.h"
#include "set.h"
#include "specifier.h"
#include "string-util.h"
@@ -1033,6 +1034,86 @@ TEST(config_parse_open_file) {
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) {
if (enter_cgroup_subroot(NULL) == -ENOMEDIUM)
return log_tests_skipped("cgroupfs not available");
diff --git a/test/integration-tests/TEST-54-CREDS/TEST-54-CREDS.units/refresh.sh b/test/integration-tests/TEST-54-CREDS/TEST-54-CREDS.units/refresh.sh
new file mode 100755
index 00000000000..24aa5e88324
--- /dev/null
+++ b/test/integration-tests/TEST-54-CREDS/TEST-54-CREDS.units/refresh.sh
@@ -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
diff --git a/test/meson.build b/test/meson.build
index 6c7fd666a08..7bf557cc193 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -351,6 +351,7 @@ if install_tests
'integration-tests/TEST-30-ONCLOCKCHANGE/TEST-30-ONCLOCKCHANGE.units',
'integration-tests/TEST-38-FREEZER/TEST-38-FREEZER.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-62-RESTRICT-IFACES/TEST-62-RESTRICT-IFACES.units',
'integration-tests/TEST-63-PATH/TEST-63-PATH.units',
diff --git a/test/units/TEST-54-CREDS.sh b/test/units/TEST-54-CREDS.sh
index ae16e5b3dfb..0eaf8a2dfb0 100755
--- a/test/units/TEST-54-CREDS.sh
+++ b/test/units/TEST-54-CREDS.sh
@@ -571,4 +571,106 @@ systemd-run -M testuser@ --user --wait -p ImportCredential=brummbaer \
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" </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" </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" <>/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