Compare commits

...

15 Commits

Author SHA1 Message Date
leafcompost 1b2b82ec03
Merge bf2328c714 into f6793bbcf0 2024-11-20 11:05:16 -08:00
Lennart Poettering f6793bbcf0 killall: gracefully handle processes inserted into containers via nsenter -a
"nsenter -a" doesn't migrate the specified process into the target
cgroup (it really should). Thus the cgroup will remain in a cgroup
that is (due to cgroup ns) outside our visibility. The kernel will
report the cgroup path of such cgroups as starting with "/../". Detect
that and print a reasonably error message instead of trying to resolve
that.
2024-11-20 18:11:38 +00:00
Mike Yuan f87863a8ff process-util: refuse to operate on remote PidRef
Follow-up for 7e3e540b88
2024-11-20 18:10:26 +00:00
Antonio Alvarez Feijoo 58c3c2886d cryptenroll: fix typo 2024-11-20 18:03:44 +00:00
Daan De Meyer dbbe895807 test-audit-util: Migrate to new assertion macros 2024-11-20 16:48:55 +00:00
Yu Watanabe 52b0351a15
core/exec-invoke: suppress placeholder home only in build_environment() (#35219)
Alternative to https://github.com/systemd/systemd/pull/34789
Closes #34789
2024-11-20 17:34:25 +09:00
Luca Boccassi fe077a1a58 units: add initrd directory to list of conditions for systemd-confext
systemd-sysext has the same check, but it was forgotten for confexts.
Needed to activate confexts from the ESP in the initrd.
2024-11-20 09:12:24 +01:00
Mike Yuan b718b86e1b
core/exec-invoke: suppress placeholder home only in build_environment()
Currently, get_fixed_user() employs USER_CREDS_SUPPRESS_PLACEHOLDER,
meaning home path is set to NULL if it's empty or root. However,
the path is also used for applying WorkingDirectory=~, and we'd
spuriously use the invoking user's home as fallback even if
User= is changed in that case.

Let's instead delegate such suppression to build_environment(),
so that home is proper initialized for usage at other steps.
shell doesn't actually suffer from such problem, but it's changed
too for consistency.

Alternative to #34789
2024-11-19 00:38:18 +01:00
Mike Yuan d911778877
core/exec-invoke: minor cleanup for apply_working_directory() error handling
Assign exit_status at the same site where error log is emitted,
for readability.
2024-11-19 00:38:18 +01:00
Mike Yuan eea9d3eb10
basic/user-util: split out placeholder suppression from USER_CREDS_CLEAN into its own flag
No functional change, preparation for later commits.
2024-11-19 00:38:18 +01:00
Mike Yuan 579ce77ead
basic/user-util: introduce shell_is_placeholder() helper 2024-11-19 00:38:18 +01:00
maia x. bf2328c714 man: document confext reload behavior for ExtensionDirectories/Images 2024-10-05 15:55:53 -07:00
maia x. 1e4e23cb7e test: check reloading notify-reload service refreshes vpick extensions 2024-10-05 15:55:53 -07:00
maia x. 77eb9bf10d core: reload confexts when reloading notify-reload services
`ExtensionImages=` and `ExtensionDirectories=` now let you specify vpick-named
extensions; however, since they just get set up once when the service is
started, you can't see newer versions without restarting the service entirely.
Here, also reload confext extensions when you reload a service. This allows you
to deploy a new version of some configuration and have it picked up at reload
time without interruption to your workload.

Right now, we would only reload confext extensions and leave the sysext ones
behind, since it didn't seem prudent to swap out what is likely program code at
reload. This is made possible by only going for the
`SYSTEMD_CONFEXT_HIERARCHIES` overlays (which only contains `/etc`). For now, we
also only do this for the notify-reload service type until more knobs are added
in the future.
2024-10-05 15:55:53 -07:00
maia x. 650e82ee3c vpick: add path_uses_vpick helper method
Add a path_uses_vpick helper method to determine if a path matches
the vpick format ('PATH/NAME.SUFFIX.v' or 'PATH.v/NAME___.SUFFIX').
2024-10-05 15:55:53 -07:00
23 changed files with 477 additions and 53 deletions

View File

@ -562,6 +562,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
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>
to manage configuration updates. When such a service carrying confext images is reloaded, the confext
itself will also be reloaded to pick up any changes. This only applies to confext extensions. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
also for details.</para>
<para>When <varname>DevicePolicy=</varname> is set to <literal>closed</literal> or <para>When <varname>DevicePolicy=</varname> is set to <literal>closed</literal> or
<literal>strict</literal>, or set to <literal>auto</literal> and <varname>DeviceAllow=</varname> is <literal>strict</literal>, or set to <literal>auto</literal> and <varname>DeviceAllow=</varname> is
set, then this setting adds <filename>/dev/loop-control</filename> with <constant>rw</constant> mode, set, then this setting adds <filename>/dev/loop-control</filename> with <constant>rw</constant> mode,
@ -606,6 +613,14 @@
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
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>
to manage configuration updates. When such a system service carrying confext directories is reloaded,
the confext itself will also be reloaded to pick up any changes. This only applies to confext
extensions. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
also for details.</para>
<para>Note that usage from user units requires overlayfs support in unprivileged user namespaces, <para>Note that usage from user units requires overlayfs support in unprivileged user namespaces,
which was first introduced in kernel v5.11.</para> which was first introduced in kernel v5.11.</para>

View File

@ -803,6 +803,10 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **ret_path) {
if (!path) if (!path)
return -ENOMEM; return -ENOMEM;
/* Refuse cgroup paths from outside our cgroup namespace */
if (startswith(path, "/../"))
return -EUNATCH;
/* Truncate suffix indicating the process is a zombie */ /* Truncate suffix indicating the process is a zombie */
e = endswith(path, " (deleted)"); e = endswith(path, " (deleted)");
if (e) if (e)

View File

@ -102,8 +102,8 @@ int pid_get_comm(pid_t pid, char **ret) {
_cleanup_free_ char *escaped = NULL, *comm = NULL; _cleanup_free_ char *escaped = NULL, *comm = NULL;
int r; int r;
assert(ret);
assert(pid >= 0); assert(pid >= 0);
assert(ret);
if (pid == 0 || pid == getpid_cached()) { if (pid == 0 || pid == getpid_cached()) {
comm = new0(char, TASK_COMM_LEN + 1); /* Must fit in 16 byte according to prctl(2) */ comm = new0(char, TASK_COMM_LEN + 1); /* Must fit in 16 byte according to prctl(2) */
@ -143,6 +143,9 @@ int pidref_get_comm(const PidRef *pid, char **ret) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_comm(pid->pid, &comm); r = pid_get_comm(pid->pid, &comm);
if (r < 0) if (r < 0)
return r; return r;
@ -289,6 +292,9 @@ int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlag
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_cmdline(pid->pid, max_columns, flags, &s); r = pid_get_cmdline(pid->pid, max_columns, flags, &s);
if (r < 0) if (r < 0)
return r; return r;
@ -331,6 +337,9 @@ int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char *
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_cmdline_strv(pid->pid, flags, &args); r = pid_get_cmdline_strv(pid->pid, flags, &args);
if (r < 0) if (r < 0)
return r; return r;
@ -477,6 +486,9 @@ int pidref_is_kernel_thread(const PidRef *pid) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
result = pid_is_kernel_thread(pid->pid); result = pid_is_kernel_thread(pid->pid);
if (result < 0) if (result < 0)
return result; return result;
@ -594,6 +606,9 @@ int pidref_get_uid(const PidRef *pid, uid_t *ret) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_uid(pid->pid, &uid); r = pid_get_uid(pid->pid, &uid);
if (r < 0) if (r < 0)
return r; return r;
@ -794,6 +809,9 @@ int pidref_get_start_time(const PidRef *pid, usec_t *ret) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
r = pid_get_start_time(pid->pid, ret ? &t : NULL); r = pid_get_start_time(pid->pid, ret ? &t : NULL);
if (r < 0) if (r < 0)
return r; return r;
@ -1093,6 +1111,9 @@ int pidref_is_my_child(const PidRef *pid) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
result = pid_is_my_child(pid->pid); result = pid_is_my_child(pid->pid);
if (result < 0) if (result < 0)
return result; return result;
@ -1128,6 +1149,9 @@ int pidref_is_unwaited(const PidRef *pid) {
if (!pidref_is_set(pid)) if (!pidref_is_set(pid))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pid))
return -EREMOTE;
if (pid->pid == 1 || pidref_is_self(pid)) if (pid->pid == 1 || pidref_is_self(pid))
return true; return true;
@ -1169,6 +1193,9 @@ int pidref_is_alive(const PidRef *pidref) {
if (!pidref_is_set(pidref)) if (!pidref_is_set(pidref))
return -ESRCH; return -ESRCH;
if (pidref_is_remote(pidref))
return -EREMOTE;
result = pid_is_alive(pidref->pid); result = pid_is_alive(pidref->pid);
if (result < 0) { if (result < 0) {
assert(result != -ESRCH); assert(result != -ESRCH);

View File

@ -220,9 +220,9 @@ static int synthesize_user_creds(
if (ret_gid) if (ret_gid)
*ret_gid = GID_NOBODY; *ret_gid = GID_NOBODY;
if (ret_home) if (ret_home)
*ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/"; *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/";
if (ret_shell) if (ret_shell)
*ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN; *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN;
return 0; return 0;
} }
@ -244,6 +244,7 @@ int get_user_creds(
assert(username); assert(username);
assert(*username); assert(*username);
assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN)));
if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
(!ret_home && !ret_shell)) { (!ret_home && !ret_shell)) {
@ -315,17 +316,14 @@ int get_user_creds(
if (ret_home) if (ret_home)
/* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
*ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) && *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) ||
(empty_or_root(p->pw_dir) || (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir)))
!path_is_valid(p->pw_dir) || ? NULL : p->pw_dir;
!path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir;
if (ret_shell) if (ret_shell)
*ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) && *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) ||
(isempty(p->pw_shell) || (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell)))
!path_is_valid(p->pw_shell) || ? NULL : p->pw_shell;
!path_is_absolute(p->pw_shell) ||
is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell;
if (patch_username) if (patch_username)
*username = p->pw_name; *username = p->pw_name;

View File

@ -12,6 +12,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include "string-util.h"
/* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */ /* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */
#define HOME_UID_MIN ((uid_t) 60001) #define HOME_UID_MIN ((uid_t) 60001)
#define HOME_UID_MAX ((uid_t) 60513) #define HOME_UID_MAX ((uid_t) 60513)
@ -36,10 +38,20 @@ static inline int parse_gid(const char *s, gid_t *ret_gid) {
char* getlogname_malloc(void); char* getlogname_malloc(void);
char* getusername_malloc(void); char* getusername_malloc(void);
const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
bool is_nologin_shell(const char *shell);
static inline bool shell_is_placeholder(const char *shell) {
return isempty(shell) || is_nologin_shell(shell);
}
typedef enum UserCredsFlags { typedef enum UserCredsFlags {
USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */ USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */
USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */ USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */
USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */
USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */
} UserCredsFlags; } UserCredsFlags;
int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags);
@ -125,10 +137,6 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg);
int putsgent_sane(const struct sgrp *sg, FILE *stream); int putsgent_sane(const struct sgrp *sg, FILE *stream);
#endif #endif
bool is_nologin_shell(const char *shell);
const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
int is_this_me(const char *username); int is_this_me(const char *username);
const char* get_home_root(void); const char* get_home_root(void);

View File

@ -855,9 +855,6 @@ static int get_fixed_user(
assert(user_or_uid); assert(user_or_uid);
assert(ret_username); assert(ret_username);
/* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
* (i.e. are "/" or "/bin/nologin"). */
r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN); r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, USER_CREDS_CLEAN);
if (r < 0) if (r < 0)
return r; return r;
@ -1883,7 +1880,10 @@ static int build_environment(
} }
} }
if (home && set_user_login_env) { /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
* (i.e. are "/" or "/bin/nologin"). */
if (home && set_user_login_env && !empty_or_root(home)) {
x = strjoin("HOME=", home); x = strjoin("HOME=", home);
if (!x) if (!x)
return -ENOMEM; return -ENOMEM;
@ -1892,7 +1892,7 @@ static int build_environment(
our_env[n_env++] = x; our_env[n_env++] = x;
} }
if (shell && set_user_login_env) { if (shell && set_user_login_env && !shell_is_placeholder(shell)) {
x = strjoin("SHELL=", shell); x = strjoin("SHELL=", shell);
if (!x) if (!x)
return -ENOMEM; return -ENOMEM;
@ -3471,20 +3471,16 @@ static int apply_working_directory(
const ExecContext *context, const ExecContext *context,
const ExecParameters *params, const ExecParameters *params,
ExecRuntime *runtime, ExecRuntime *runtime,
const char *home, const char *home) {
int *exit_status) {
const char *wd; const char *wd;
int r; int r;
assert(context); assert(context);
assert(exit_status);
if (context->working_directory_home) { if (context->working_directory_home) {
if (!home) { if (!home)
*exit_status = EXIT_CHDIR;
return -ENXIO; return -ENXIO;
}
wd = home; wd = home;
} else } else
@ -3503,13 +3499,7 @@ static int apply_working_directory(
if (r >= 0) if (r >= 0)
r = RET_NERRNO(fchdir(dfd)); r = RET_NERRNO(fchdir(dfd));
} }
return context->working_directory_missing_ok ? 0 : r;
if (r < 0 && !context->working_directory_missing_ok) {
*exit_status = EXIT_CHDIR;
return r;
}
return 0;
} }
static int apply_root_directory( static int apply_root_directory(
@ -3785,7 +3775,7 @@ static int acquire_home(const ExecContext *c, const char **home, char **ret_buf)
if (!c->working_directory_home) if (!c->working_directory_home)
return 0; return 0;
if (c->dynamic_user) if (c->dynamic_user || (c->user && is_this_me(c->user) <= 0))
return -EADDRNOTAVAIL; return -EADDRNOTAVAIL;
r = get_home_dir(ret_buf); r = get_home_dir(ret_buf);
@ -4543,7 +4533,7 @@ int exec_invoke(
r = acquire_home(context, &home, &home_buffer); r = acquire_home(context, &home, &home_buffer);
if (r < 0) { if (r < 0) {
*exit_status = EXIT_CHDIR; *exit_status = EXIT_CHDIR;
return log_exec_error_errno(context, params, r, "Failed to determine $HOME for user: %m"); return log_exec_error_errno(context, params, r, "Failed to determine $HOME for the invoking user: %m");
} }
/* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */ /* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */
@ -5382,9 +5372,11 @@ int exec_invoke(
* running this service might have the correct privilege to change to the working directory. Also, it * running this service might have the correct privilege to change to the working directory. Also, it
* is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that * is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that
* the cwd cannot be used to pin directories outside of the sandbox. */ * the cwd cannot be used to pin directories outside of the sandbox. */
r = apply_working_directory(context, params, runtime, home, exit_status); r = apply_working_directory(context, params, runtime, home);
if (r < 0) if (r < 0) {
*exit_status = EXIT_CHDIR;
return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m"); return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m");
}
if (needs_sandboxing) { if (needs_sandboxing) {
/* Apply other MAC contexts late, but before seccomp syscall filtering, as those should really be last to /* Apply other MAC contexts late, but before seccomp syscall filtering, as those should really be last to

View File

@ -71,6 +71,7 @@
#include "unit-serialize.h" #include "unit-serialize.h"
#include "user-util.h" #include "user-util.h"
#include "utmp-wtmp.h" #include "utmp-wtmp.h"
#include "vpick.h"
static bool is_terminal_input(ExecInput i) { static bool is_terminal_input(ExecInput i) {
return IN_SET(i, return IN_SET(i,
@ -1938,6 +1939,25 @@ char** exec_context_get_restrict_filesystems(const ExecContext *c) {
return l ? TAKE_PTR(l) : strv_new(NULL); return l ? TAKE_PTR(l) : strv_new(NULL);
} }
int exec_context_has_vpicked_extensions(const ExecContext *context) {
int r;
assert(context);
FOREACH_ARRAY(mi, context->extension_images, context->n_extension_images) {
r = path_uses_vpick(mi->source);
if (r != 0)
return r;
}
STRV_FOREACH(ed, context->extension_directories) {
r = path_uses_vpick(*ed);
if (r != 0)
return r;
}
return 0;
}
void exec_status_start(ExecStatus *s, pid_t pid, const dual_timestamp *ts) { void exec_status_start(ExecStatus *s, pid_t pid, const dual_timestamp *ts) {
assert(s); assert(s);

View File

@ -559,6 +559,8 @@ char** exec_context_get_syscall_log(const ExecContext *c);
char** exec_context_get_address_families(const ExecContext *c); char** exec_context_get_address_families(const ExecContext *c);
char** exec_context_get_restrict_filesystems(const ExecContext *c); char** exec_context_get_restrict_filesystems(const ExecContext *c);
int exec_context_has_vpicked_extensions(const ExecContext *context);
void exec_status_start(ExecStatus *s, pid_t pid, const dual_timestamp *ts); void exec_status_start(ExecStatus *s, pid_t pid, const dual_timestamp *ts);
void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status); void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status);
void exec_status_handoff(ExecStatus *s, const struct ucred *ucred, const dual_timestamp *ts); void exec_status_handoff(ExecStatus *s, const struct ucred *ucred, const dual_timestamp *ts);

View File

@ -37,6 +37,8 @@
#include "nulstr-util.h" #include "nulstr-util.h"
#include "os-util.h" #include "os-util.h"
#include "path-util.h" #include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "selinux-util.h" #include "selinux-util.h"
#include "socket-util.h" #include "socket-util.h"
#include "sort-util.h" #include "sort-util.h"
@ -3296,6 +3298,117 @@ bool ns_type_supported(NamespaceType type) {
return access(ns_proc, F_OK) == 0; return access(ns_proc, F_OK) == 0;
} }
int refresh_extensions_in_namespace(
const PidRef *target,
const char *hierarchy_env,
const NamespaceParameters *p) {
const char *overlay_prefix = "/run/systemd/mount-rootfs";
_cleanup_(mount_list_done) MountList ml = {};
_cleanup_free_ char *extension_dir = NULL;
_cleanup_strv_free_ char **hierarchies = NULL;
MountInNamespaceFlags min_flags = 0;
int r;
assert(pidref_is_set(target));
assert(hierarchy_env);
assert(p);
log_debug("Refreshing extensions in-namespace for hierarchy '%s'", hierarchy_env);
extension_dir = path_join(p->private_namespace_dir, "unit-extensions");
if (!extension_dir)
return -ENOMEM;
min_flags |= MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY;
r = parse_env_extension_hierarchies(&hierarchies, hierarchy_env);
if (r < 0)
return r;
r = append_extensions(
&ml,
overlay_prefix,
p->private_namespace_dir,
hierarchies,
p->extension_images,
p->n_extension_images,
p->extension_directories);
if (r < 0)
return r;
if (ml.n_mounts == 0)
return 0;
r = safe_fork("(sd-ns-refresh-exts)",
FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE,
NULL);
if (r < 0)
return r;
if (r == 0) {
(void) mkdir_p_label(overlay_prefix, 0555);
/* This is effectively two rounds, since all the extensions come before overlays
* (setup_namespace() similarly relies on this property).
*
* (1) First, set up all the extension mounts in the child, which are not visible from the
* process. (2) Then, set up overlays for the sysext/confext hierarchies again using the new
* extension mounts as layers, and move them into the namespace. */
FOREACH_ARRAY(m, ml.mounts, ml.n_mounts) {
if (IN_SET(m->mode, MOUNT_EXTENSION_DIRECTORY, MOUNT_EXTENSION_IMAGE)) {
r = apply_one_mount(p->root_directory, m, p);
if (r < 0) {
log_debug_errno(r, "Failed to apply extension mount: %m");
_exit(EXIT_FAILURE);
}
} else if (m->mode == MOUNT_OVERLAY) {
_cleanup_free_ char *path_relative = NULL, *path_in_namespace = NULL;
r = apply_one_mount(p->root_directory, m, p);
if (r < 0)
_exit(EXIT_FAILURE);
if (r == 0) {
/* Tried to mount overlay, but it is now empty - umount it then. */
min_flags |= MOUNT_IN_NAMESPACE_UMOUNT;
}
/* bind_mount_in_namespace takes a src on the outside and a dest evaluated
* within the namespace. First, figure out where we want the overlay on top
* of within the namespace.
*/
r = path_make_relative(overlay_prefix, mount_entry_path(m), &path_relative);
if (r < 0) {
log_debug_errno(r, "Failed to make path relative: %m");
_exit(EXIT_FAILURE);
}
r = asprintf(&path_in_namespace, "%s/%s", empty_to_root(p->root_directory), path_relative);
if (r < 0) {
log_oom_debug();
_exit(EXIT_FAILURE);
}
r = bind_mount_in_namespace(
target,
p->propagate_dir,
p->incoming_dir,
/* src= */ mount_entry_path(m),
/* dest= */ path_in_namespace,
min_flags);
if (r < 0) {
log_debug_errno(
r,
"Failed to move overlay within %s->%s: %m",
mount_entry_path(m),
path_in_namespace);
_exit(EXIT_FAILURE);
}
}
}
_exit(EXIT_SUCCESS);
}
return 0;
}
static const char *const protect_home_table[_PROTECT_HOME_MAX] = { static const char *const protect_home_table[_PROTECT_HOME_MAX] = {
[PROTECT_HOME_NO] = "no", [PROTECT_HOME_NO] = "no",
[PROTECT_HOME_YES] = "yes", [PROTECT_HOME_YES] = "yes",

View File

@ -16,6 +16,7 @@ typedef struct MountImage MountImage;
#include "fs-util.h" #include "fs-util.h"
#include "macro.h" #include "macro.h"
#include "namespace-util.h" #include "namespace-util.h"
#include "pidref.h"
#include "runtime-scope.h" #include "runtime-scope.h"
#include "string-util.h" #include "string-util.h"
@ -250,3 +251,8 @@ const char* namespace_type_to_string(NamespaceType t) _const_;
NamespaceType namespace_type_from_string(const char *s) _pure_; NamespaceType namespace_type_from_string(const char *s) _pure_;
bool ns_type_supported(NamespaceType type); bool ns_type_supported(NamespaceType type);
int refresh_extensions_in_namespace(
const PidRef *target,
const char *hierarchy_env,
const NamespaceParameters *p);

View File

@ -21,6 +21,7 @@
#include "devnum-util.h" #include "devnum-util.h"
#include "env-util.h" #include "env-util.h"
#include "escape.h" #include "escape.h"
#include "execute.h"
#include "exec-credential.h" #include "exec-credential.h"
#include "exit-status.h" #include "exit-status.h"
#include "fd-util.h" #include "fd-util.h"
@ -33,11 +34,13 @@
#include "manager.h" #include "manager.h"
#include "missing_audit.h" #include "missing_audit.h"
#include "mount-util.h" #include "mount-util.h"
#include "namespace.h"
#include "open-file.h" #include "open-file.h"
#include "parse-util.h" #include "parse-util.h"
#include "path-util.h" #include "path-util.h"
#include "process-util.h" #include "process-util.h"
#include "random-util.h" #include "random-util.h"
#include "runtime-scope.h"
#include "selinux-util.h" #include "selinux-util.h"
#include "serialize.h" #include "serialize.h"
#include "service.h" #include "service.h"
@ -2709,6 +2712,67 @@ static void service_enter_reload_by_notify(Service *s) {
log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r)); log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r));
} }
static bool service_should_reload_extensions(Service *s) {
int r;
assert(s);
/* Only support this for notify-reload service types. */
if (s->type != SERVICE_NOTIFY_RELOAD)
return false;
/* TODO: Add support for user services, which can use
* ExtensionDirectories= + notify-reload. For now, skip for user
* services. */
if (UNIT(s)->manager->runtime_scope != RUNTIME_SCOPE_SYSTEM) {
log_unit_debug(UNIT(s), "Not reloading extensions for user services.");
return false;
}
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");
return false;
}
return r > 0;
}
static int service_reload_extensions(Service *s) {
/* TODO: do this asynchronously */
_cleanup_free_ char *propagate_dir = NULL;
assert(s);
/* TODO: remove after adding support for user services */
assert(UNIT(s)->manager->runtime_scope == RUNTIME_SCOPE_SYSTEM);
if (!service_should_reload_extensions(s))
return 0;
propagate_dir = path_join("/run/systemd/propagate/", UNIT(s)->id);
if (!propagate_dir)
return -ENOMEM;
NamespaceParameters p = {
.private_namespace_dir = "/run/systemd",
.incoming_dir = "/run/systemd/incoming",
.propagate_dir = propagate_dir,
.runtime_scope = UNIT(s)->manager->runtime_scope,
.root_directory = s->exec_context.root_directory,
.extension_images = s->exec_context.extension_images,
.n_extension_images = s->exec_context.n_extension_images,
.extension_directories = s->exec_context.extension_directories,
.extension_image_policy = s->exec_context.extension_image_policy
};
/* Only reload confext, and not sysext, because it doesn't make sense
for program code to be swapped at reload. */
return refresh_extensions_in_namespace(
unit_main_pid(UNIT(s)),
"SYSTEMD_CONFEXT_HIERARCHIES",
&p);
}
static void service_enter_reload(Service *s) { static void service_enter_reload(Service *s) {
bool killed = false; bool killed = false;
int r; int r;
@ -2720,6 +2784,14 @@ static void service_enter_reload(Service *s) {
usec_t ts = now(CLOCK_MONOTONIC); usec_t ts = now(CLOCK_MONOTONIC);
/* If we have confexts extensions, try to reload vpick'd confext extensions, which is particularly
* beneficial for notify-reload services that could potentially pick up a new version of its
* configuration.
*/
r = service_reload_extensions(s);
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to reload confexts, ignoring: %m");
if (s->type == SERVICE_NOTIFY_RELOAD && pidref_is_set(&s->main_pid)) { if (s->type == SERVICE_NOTIFY_RELOAD && pidref_is_set(&s->main_pid)) {
r = pidref_kill_and_sigcont(&s->main_pid, s->reload_signal); r = pidref_kill_and_sigcont(&s->main_pid, s->reload_signal);
if (r < 0) { if (r < 0) {

View File

@ -193,7 +193,7 @@ int enroll_fido2(
fflush(stdout); fflush(stdout);
fprintf(stderr, fprintf(stderr,
"\nPlease save this FIDO2 credential ID. It is required when unloocking the volume\n" "\nPlease save this FIDO2 credential ID. It is required when unlocking the volume\n"
"using the associated FIDO2 keyslot which we just created. To configure automatic\n" "using the associated FIDO2 keyslot which we just created. To configure automatic\n"
"unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n" "unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n"
"file, see %s for details.\n", link); "file, see %s for details.\n", link);

View File

@ -2297,7 +2297,8 @@ static int start_transient_scope(sd_bus *bus) {
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell, USER_CREDS_CLEAN|USER_CREDS_PREFER_NSS); r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell,
USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user);

View File

@ -46,13 +46,17 @@ static bool argv_has_at(pid_t pid) {
return c == '@'; return c == '@';
} }
static bool is_survivor_cgroup(const PidRef *pid) { static bool is_in_survivor_cgroup(const PidRef *pid) {
_cleanup_free_ char *cgroup_path = NULL; _cleanup_free_ char *cgroup_path = NULL;
int r; int r;
assert(pidref_is_set(pid)); assert(pidref_is_set(pid));
r = cg_pidref_get_path(/* root= */ NULL, pid, &cgroup_path); r = cg_pidref_get_path(/* root= */ NULL, pid, &cgroup_path);
if (r == -EUNATCH) {
log_warning_errno(r, "Process " PID_FMT " appears to originate in foreign namespace, ignoring.", pid->pid);
return true;
}
if (r < 0) { if (r < 0) {
log_warning_errno(r, "Failed to get cgroup path of process " PID_FMT ", ignoring: %m", pid->pid); log_warning_errno(r, "Failed to get cgroup path of process " PID_FMT ", ignoring: %m", pid->pid);
return false; return false;
@ -86,7 +90,7 @@ static bool ignore_proc(const PidRef *pid, bool warn_rootfs) {
return true; /* also ignore processes where we can't determine this */ return true; /* also ignore processes where we can't determine this */
/* Ignore processes that are part of a cgroup marked with the user.survive_final_kill_signal xattr */ /* Ignore processes that are part of a cgroup marked with the user.survive_final_kill_signal xattr */
if (is_survivor_cgroup(pid)) if (is_in_survivor_cgroup(pid))
return true; return true;
r = pidref_get_uid(pid, &uid); r = pidref_get_uid(pid, &uid);

View File

@ -1132,7 +1132,7 @@ static int mount_in_namespace(
_cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR; _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR;
pid_t child; pid_t child;
if (flags & MOUNT_IN_NAMESPACE_IS_IMAGE) { if (!(flags & MOUNT_IN_NAMESPACE_UMOUNT) && flags & MOUNT_IN_NAMESPACE_IS_IMAGE) {
r = verity_dissect_and_mount( r = verity_dissect_and_mount(
chased_src_fd, chased_src_fd,
chased_src_path, chased_src_path,
@ -1150,7 +1150,7 @@ static int mount_in_namespace(
return log_debug_errno(r, return log_debug_errno(r,
"Failed to dissect and mount image '%s': %m", "Failed to dissect and mount image '%s': %m",
chased_src_path); chased_src_path);
} else { } else if (!(flags & MOUNT_IN_NAMESPACE_UMOUNT)) {
new_mount_fd = open_tree( new_mount_fd = open_tree(
chased_src_fd, chased_src_fd,
"", "",
@ -1189,6 +1189,18 @@ static int mount_in_namespace(
if (r == 0) { if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
if (flags & MOUNT_IN_NAMESPACE_UMOUNT) {
r = umount_verbose(LOG_DEBUG, dest, UMOUNT_NOFOLLOW);
if (r < 0) {
(void) write(errno_pipe_fd[1], &r, sizeof(r));
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
if (flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY) if (flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY)
(void) mkdir_parents(dest, 0755); (void) mkdir_parents(dest, 0755);

View File

@ -119,6 +119,7 @@ typedef enum MountInNamespaceFlags {
MOUNT_IN_NAMESPACE_READ_ONLY = 1 << 0, MOUNT_IN_NAMESPACE_READ_ONLY = 1 << 0,
MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY = 1 << 1, MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY = 1 << 1,
MOUNT_IN_NAMESPACE_IS_IMAGE = 1 << 2, MOUNT_IN_NAMESPACE_IS_IMAGE = 1 << 2,
MOUNT_IN_NAMESPACE_UMOUNT = 1 << 3,
} MountInNamespaceFlags; } MountInNamespaceFlags;
int bind_mount_in_namespace( int bind_mount_in_namespace(

View File

@ -681,6 +681,41 @@ int path_pick_update_warn(
return 1; return 1;
} }
int path_uses_vpick(const char *path) {
_cleanup_free_ char *dir = NULL, *parent = NULL, *fname = NULL;
int r;
assert(path);
r = path_extract_filename(path, &fname);
if (r == -EADDRNOTAVAIL)
return 0;
if (r < 0)
return r;
/* ...PATH/NAME.SUFFIX.v */
if (endswith(fname, ".v"))
return 1;
/* ...PATH.v/NAME___.SUFFIX */
if (!strrstr(fname, "___"))
return 0;
r = path_extract_directory(path, &dir);
if (IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
return 0;
if (r < 0)
return r;
r = path_extract_filename(dir, &parent);
if (r == -EADDRNOTAVAIL)
return 0;
if (r < 0)
return r;
return !!endswith(parent, ".v");
}
const PickFilter pick_filter_image_raw = { const PickFilter pick_filter_image_raw = {
.type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK), .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
.architecture = _ARCHITECTURE_INVALID, .architecture = _ARCHITECTURE_INVALID,

View File

@ -56,6 +56,8 @@ int path_pick_update_warn(
PickFlags flags, PickFlags flags,
PickResult *ret); PickResult *ret);
int path_uses_vpick(const char *path);
extern const PickFilter pick_filter_image_raw; extern const PickFilter pick_filter_image_raw;
extern const PickFilter pick_filter_image_dir; extern const PickFilter pick_filter_image_dir;
extern const PickFilter pick_filter_image_any; extern const PickFilter pick_filter_image_any;

View File

@ -7,24 +7,26 @@ TEST(audit_loginuid_from_pid) {
_cleanup_(pidref_done) PidRef self = PIDREF_NULL, pid1 = PIDREF_NULL; _cleanup_(pidref_done) PidRef self = PIDREF_NULL, pid1 = PIDREF_NULL;
int r; int r;
assert_se(pidref_set_self(&self) >= 0); ASSERT_OK(pidref_set_self(&self));
assert_se(pidref_set_pid(&pid1, 1) >= 0); ASSERT_OK(pidref_set_pid(&pid1, 1));
uid_t uid; uid_t uid;
r = audit_loginuid_from_pid(&self, &uid); r = audit_loginuid_from_pid(&self, &uid);
assert_se(r >= 0 || r == -ENODATA); if (r != -ENODATA)
ASSERT_OK(r);
if (r >= 0) if (r >= 0)
log_info("self audit login uid: " UID_FMT, uid); log_info("self audit login uid: " UID_FMT, uid);
assert_se(audit_loginuid_from_pid(&pid1, &uid) == -ENODATA); ASSERT_ERROR(audit_loginuid_from_pid(&pid1, &uid), ENODATA);
uint32_t sessionid; uint32_t sessionid;
r = audit_session_from_pid(&self, &sessionid); r = audit_session_from_pid(&self, &sessionid);
assert_se(r >= 0 || r == -ENODATA); if (r != -ENODATA)
ASSERT_OK(r);
if (r >= 0) if (r >= 0)
log_info("self audit session id: %" PRIu32, sessionid); log_info("self audit session id: %" PRIu32, sessionid);
assert_se(audit_session_from_pid(&pid1, &sessionid) == -ENODATA); ASSERT_ERROR(audit_session_from_pid(&pid1, &sessionid), ENODATA);
} }
static int intro(void) { static int intro(void) {

View File

@ -168,4 +168,27 @@ TEST(path_pick) {
assert_se(result.architecture == ARCHITECTURE_S390); assert_se(result.architecture == ARCHITECTURE_S390);
} }
TEST(path_uses_vpick) {
assert_se(path_uses_vpick("foo.v") > 0);
assert_se(path_uses_vpick("path/to/foo.v") > 0);
assert_se(path_uses_vpick("./path/to/foo.v") > 0);
assert_se(path_uses_vpick("path/to.v/foo.v") > 0);
assert_se(path_uses_vpick("path/to/foo.raw.v") > 0);
assert_se(path_uses_vpick("/var/lib/machines/mymachine.raw.v/") > 0);
assert_se(path_uses_vpick("path/to.v/foo___.hi/a.v") > 0);
assert_se(!path_uses_vpick("path/to/foo.mp4.vtt"));
assert_se(!path_uses_vpick("path/to/foo.mp4.v.1"));
assert_se(!path_uses_vpick("path/to.v/a"));
assert_se(path_uses_vpick("to.v/foo___.raw") > 0);
assert_se(path_uses_vpick("path/to.v/foo___.raw") > 0);
assert_se(!path_uses_vpick("path/to/foo___.raw"));
assert_se(!path_uses_vpick("path/to.v/foo__"));
assert_se(!path_uses_vpick("foo___.raw"));
assert_se(path_uses_vpick("/") < 1);
assert_se(path_uses_vpick(".") < 1);
assert_se(path_uses_vpick("") < 1);
}
DEFINE_TEST_MAIN(LOG_DEBUG); DEFINE_TEST_MAIN(LOG_DEBUG);

View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
(! systemd-run --wait -p DynamicUser=yes \
-p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
-p WorkingDirectory='~' true)
assert_eq "$(systemd-run --pipe --uid=root -p WorkingDirectory='~' pwd)" "/root"
assert_eq "$(systemd-run --pipe --uid=nobody -p WorkingDirectory='~' pwd)" "/"
assert_eq "$(systemd-run --pipe --uid=testuser -p WorkingDirectory='~' pwd)" "/home/testuser"
(! systemd-run --wait -p DynamicUser=yes -p User=testuser \
-p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
-p WorkingDirectory='~' true)

View File

@ -518,6 +518,72 @@ rm -rf "$VDIR" "$EMPTY_VDIR"
systemd-dissect --umount "$IMAGE_DIR/app0" systemd-dissect --umount "$IMAGE_DIR/app0"
systemd-dissect --umount "$IMAGE_DIR/app1" systemd-dissect --umount "$IMAGE_DIR/app1"
# Check reloading refreshes vpick extensions
VBASE="vtest$RANDOM"
VDIR="/tmp/${VBASE}.v"
mkdir "$VDIR"
cat >/run/systemd/system/testservice-50g.service <<EOF
[Service]
Type=notify-reload
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
ExtensionDirectories=${VDIR}
ExecStart=bash -c ' \\
trap "{ \\
systemd-notify --reloading; \\
ls /etc | grep marker; \\
systemd-notify --ready; \\
}" SIGHUP; \\
systemd-notify --ready; \\
while true; do sleep 1; done; \\
'
EOF
mkdir -p "$VDIR/${VBASE}_1/etc/extension-release.d/"
echo "ID=_any" >"$VDIR/${VBASE}_1/etc/extension-release.d/extension-release.${VBASE}_1"
touch "$VDIR/${VBASE}_1/etc/${VBASE}_1.marker"
systemctl start testservice-50g.service
systemctl is-active testservice-50g.service
# First reload; at reload time, the marker file in /etc should be picked up.
systemctl try-reload-or-restart testservice-50g.service
journalctl -b -u testservice-50g | grep -q -F "${VBASE}_1.marker"
# Make a version 2 and reload again; this time we should see the v2 marker
mkdir -p "$VDIR/${VBASE}_2/etc/extension-release.d/"
echo "ID=_any" >"$VDIR/${VBASE}_2/etc/extension-release.d/extension-release.${VBASE}_2"
touch "$VDIR/${VBASE}_2/etc/${VBASE}_2.marker"
systemctl try-reload-or-restart testservice-50g.service
journalctl --sync
journalctl -b -u testservice-50g | grep -q -F "${VBASE}_2.marker"
# Do it for a couple more times (to make sure we're tearing down old overlays)
for _ in {1..5}; do systemctl reload testservice-50g.service; done
systemctl stop testservice-50g.service
# Repeat the same vpick notify-reload test with ExtensionImages= (keeping the
# same VBASE and reusing VDIR files for convenience, but using .raw extensions
# this time)
VDIR2="/tmp/${VBASE}.raw.v"
mkdir "$VDIR2"
cp /run/systemd/system/testservice-50g.service /run/systemd/system/testservice-50h.service
sed -i "s%ExtensionDirectories=.*%ExtensionImages=$VDIR2%g" \
/run/systemd/system/testservice-50h.service
mksquashfs "$VDIR/${VBASE}_1" "$VDIR2/${VBASE}_1.raw"
systemctl start testservice-50h.service
systemctl is-active testservice-50h.service
# First reload should pick up the v1 marker
systemctl try-reload-or-restart testservice-50h.service
journalctl --sync
journalctl -b -u testservice-50h | grep -q -F "${VBASE}_1.marker"
# Second reload should pick up the v2 marker
mksquashfs "$VDIR/${VBASE}_2" "$VDIR2/${VBASE}_2.raw"
systemctl try-reload-or-restart testservice-50h.service
journalctl --sync
journalctl -b -u testservice-50h | grep -q -F "${VBASE}_2.marker"
# Test that removing all the extensions don't cause any issues
rm -rf "${VDIR2:?}"/*
systemctl try-reload-or-restart testservice-50h.service
systemctl is-active testservice-50h.service
systemctl stop testservice-50h.service
rm -rf "$VDIR" "$VDIR2"
# Test that an extension consisting of an empty directory under /etc/extensions/ takes precedence # Test that an extension consisting of an empty directory under /etc/extensions/ takes precedence
mkdir -p /var/lib/extensions/ mkdir -p /var/lib/extensions/
ln -s /tmp/app-nodistro.raw /var/lib/extensions/app-nodistro.raw ln -s /tmp/app-nodistro.raw /var/lib/extensions/app-nodistro.raw

View File

@ -16,6 +16,7 @@ ConditionDirectoryNotEmpty=|/run/confexts
ConditionDirectoryNotEmpty=|/var/lib/confexts ConditionDirectoryNotEmpty=|/var/lib/confexts
ConditionDirectoryNotEmpty=|/usr/local/lib/confexts ConditionDirectoryNotEmpty=|/usr/local/lib/confexts
ConditionDirectoryNotEmpty=|/usr/lib/confexts ConditionDirectoryNotEmpty=|/usr/lib/confexts
ConditionDirectoryNotEmpty=|/.extra/confext
DefaultDependencies=no DefaultDependencies=no
After=local-fs.target After=local-fs.target