1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-03 05:18:09 +03:00

Compare commits

...

6 Commits

Author SHA1 Message Date
leafcompost
dbe879064c
Merge 77915855c8 into 8a135111ca 2024-12-20 14:04:54 +09:00
maia x.
77915855c8 man: document confext reload behavior for ExtensionDirectories/Images 2024-12-19 12:57:03 -08:00
maia x.
fbd2c51533 test: check reloading notify-reload service refreshes vpick extensions 2024-12-19 12:57:03 -08:00
maia x.
043a6609b1 mkosi: add socat to minimal-0/1 images
Include 'socat' as part of the tools built with minimal-0/minimal-1
images used for integration testing. This would allow notify-reload
services to signal reloading without using the systemd-notify tool
and other socket communication test cases.
2024-12-19 12:57:03 -08:00
maia x.
4c896a7c71 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-12-19 12:57:03 -08:00
maia x.
6f0c327f3a 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-12-19 12:57:02 -08:00
11 changed files with 623 additions and 0 deletions

View File

@ -562,6 +562,13 @@
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>
<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
<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,
@ -606,6 +613,14 @@
or the host. See:
<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,
which was first introduced in kernel v5.11.</para>

View File

@ -13,6 +13,7 @@ Packages=
bash
coreutils
grep
socat
util-linux
[Include]

View File

@ -71,6 +71,7 @@
#include "unit-serialize.h"
#include "user-util.h"
#include "utmp-wtmp.h"
#include "vpick.h"
static bool is_terminal_input(ExecInput i) {
return IN_SET(i,
@ -1964,6 +1965,25 @@ char** exec_context_get_restrict_filesystems(const ExecContext *c) {
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) {
assert(s);

View File

@ -560,6 +560,8 @@ char** exec_context_get_syscall_log(const ExecContext *c);
char** exec_context_get_address_families(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_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);

View File

@ -37,6 +37,8 @@
#include "nulstr-util.h"
#include "os-util.h"
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "selinux-util.h"
#include "socket-util.h"
#include "sort-util.h"
@ -3306,6 +3308,336 @@ bool ns_type_supported(NamespaceType type) {
return access(ns_proc, F_OK) == 0;
}
static int get_unpeeled_mount_fd(const char *mount_path, int *ret_fd) {
_cleanup_close_pair_ int pipe_fds[2] = EBADF_PAIR;
pid_t pid;
int r;
assert(mount_path);
assert(ret_fd);
r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pipe_fds);
if (r < 0)
return log_debug_errno(errno, "Failed to create socket pair: %m");
r = safe_fork("(sd-ns-unpeel)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, &pid);
if (r < 0)
return r;
if (r == 0) {
_cleanup_close_ int dir_fd = -EBADF;
pipe_fds[0] = safe_close(pipe_fds[0]);
/* Clone mount namespace here to unpeel without affecting live process */
r = detach_mount_namespace();
if (r < 0) {
log_debug_errno(r, "Failed to detach mount namespace: %m");
_exit(EXIT_FAILURE);
}
/* Opportunistically unmount any overlay at this path */
(void) umount_recursive(mount_path, MNT_DETACH);
/* Now that /mount_path is exposed, get an FD for it and pass back */
dir_fd = open_tree(-EBADF, mount_path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLONE);
if (dir_fd < 0) {
log_debug_errno(errno, "Failed to clone mount %s: %m", mount_path);
_exit(EXIT_FAILURE);
}
r = send_one_fd(pipe_fds[1], dir_fd, 0);
if (r < 0) {
log_debug_errno(r, "Failed to send mount fd: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
pipe_fds[1] = safe_close(pipe_fds[1]);
r = receive_one_fd(pipe_fds[0], 0);
if (r < 0)
return log_debug_errno(r, "Failed to receive mount fd: %m");
*ret_fd = r;
return 0;
}
/* In target namespace, unmounts an existing overlayfs at mount_path (if one exists), grabs FD from the
* underlying directory, and sets up a new overlayfs mount. Coordinates with parent process over pair_fd:
* 1. Creates and sends new overlay fs fd to parent
* 2. Fake-unmounts overlay at mount_path to obtain underlying directory fd to build new overlay
* 3. Waits for parent to configure layers
* 4. Performs final mount at mount_path
*
* This is used by refresh_extensions_in_namespace() to peel back any existing overlays and reapply them.
*/
static int unpeel_mount_and_setup_overlay(int pair_fd, const char *mount_path) {
_cleanup_close_ int dir_unpeeled_fd = -EBADF, overlay_fs_fd = -EBADF, mount_fd = -EBADF;
int r;
assert(pair_fd >= 0);
assert(mount_path);
/* Create new OverlayFS and send to parent */
overlay_fs_fd = fsopen("overlay", FSOPEN_CLOEXEC);
if (overlay_fs_fd < 0)
return log_debug_errno(errno, "Failed to create overlay fs for %s: %m", mount_path);
r = send_one_fd(pair_fd, overlay_fs_fd, 0);
if (r < 0)
return log_debug_errno(r, "Failed to send overlay fs fd to parent: %m");
/* Unpeel in cloned mount namespace to get underlying directory fd */
r = get_unpeeled_mount_fd(mount_path, &dir_unpeeled_fd);
if (r < 0)
return log_debug_errno(r, "Failed to unpeel mount %s: %m", mount_path);
/* Send the fd to the parent */
r = send_one_fd(pair_fd, dir_unpeeled_fd, 0);
if (r < 0)
return log_debug_errno(r, "Failed to send %s fd to parent: %m", mount_path);
/* Wait for parent to signal overlay configuration completion */
log_debug("Waiting for configured overlay fs for %s", mount_path);
r = receive_one_fd(pair_fd, 0);
if (r < 0)
return log_debug_errno(r, "Failed to receive configured overlay: %m");
/* Create the mount */
mount_fd = fsmount(overlay_fs_fd, FSMOUNT_CLOEXEC, 0);
if (mount_fd < 0)
return log_debug_errno(errno, "Failed to create overlay mount: %m");
/* Move mount to final location */
r = mount_exchange_graceful(mount_fd, mount_path, true);
if (r < 0)
return log_debug_errno(r, "Failed to move overlay to %s: %m", mount_path);
return 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;
int r;
assert(pidref_is_set(target));
assert(hierarchy_env);
assert(p);
if (!mount_new_api_supported()) {
log_once(LOG_WARNING, "Skipping extension refresh in namespace, as the kernel does not support the new mount API");
return 0;
}
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;
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;
sort_and_drop_unused_mounts(&ml, overlay_prefix);
if (ml.n_mounts == 0)
return 0;
/**
* There are three main steps:
* 1. In child, set up the extension images and directories in a slave mountns, so that we have
* access to their FDs
* 2. Fork into a grandchild, which will enter the target namespace and attempt to "unpeel" the
* overlays to obtain FDs the underlying directories, over which we will reapply the overlays
* 3. In the child again, receive the FDs and reapply the overlays
*/
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) {
/* Child (host namespace) */
_cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF;
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
_cleanup_(sigkill_nowaitp) pid_t grandchild_pid = 0;
(void) mkdir_p_label(overlay_prefix, 0555);
/* Open all extension roots on the host */
FOREACH_ARRAY(m, ml.mounts, ml.n_mounts)
if (IN_SET(m->mode, MOUNT_EXTENSION_DIRECTORY, MOUNT_EXTENSION_IMAGE)) {
r = apply_one_mount("/", m, p);
if (r < 0) {
log_debug_errno(r, "Failed to apply extension mount: %m");
_exit(EXIT_FAILURE);
}
}
/* Create a grandchild process to handle the unmounting and reopening of hierarchy */
r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair);
if (r < 0) {
log_debug_errno(errno, "Failed to create socket pair: %m");
_exit(EXIT_FAILURE);
}
r = safe_fork("(sd-ns-refresh-exts-grandchild)",
FORK_DEATHSIG_SIGKILL,
&grandchild_pid);
if (r < 0)
_exit(EXIT_FAILURE);
if (r == 0) {
/* Grandchild (target service namespace) */
pair[0] = safe_close(pair[0]);
r = namespace_open(target->pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
if (r < 0) {
log_debug_errno(r, "Failed to open namespace: %m");
_exit(EXIT_FAILURE);
}
r = namespace_enter(pidns_fd, mntns_fd, -EBADF, -EBADF, root_fd);
if (r < 0) {
log_debug_errno(r, "Failed to enter namespace: %m");
_exit(EXIT_FAILURE);
}
/* Prevent propagation while unmounting */
r = mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL);
/* Handle each overlay mount path */
FOREACH_ARRAY(m, ml.mounts, ml.n_mounts) {
if (m->mode != MOUNT_OVERLAY)
continue;
const char *mount_path = mount_entry_unprefixed_path(m);
if (startswith(mount_path, overlay_prefix))
mount_path += strlen(overlay_prefix);
r = unpeel_mount_and_setup_overlay(pair[1], mount_path);
if (r < 0) {
log_debug_errno(r, "Failed to setup overlay mount for %s: %m", mount_path);
/* In case parent is waiting, send the return code as FD. */
(void) send_one_fd(pair[1], r, 0);
_exit(EXIT_FAILURE);
}
}
_exit(EXIT_SUCCESS);
}
FOREACH_ARRAY(m, ml.mounts, ml.n_mounts) {
if (m->mode != MOUNT_OVERLAY)
continue;
_cleanup_close_ int hierarchy_path_fd = -EBADF, overlay_fs_fd = -EBADF;
_cleanup_free_ char *layers = NULL, *options = NULL, *hierarchy_path_moved_mount = NULL;
_cleanup_strv_free_ char **new_layers = NULL;
const char *mount_path = mount_entry_unprefixed_path(m);
if (startswith(mount_path, overlay_prefix))
mount_path = mount_path + strlen(overlay_prefix);
/* Receive the fds from grandchild */
overlay_fs_fd = receive_one_fd(pair[0], 0);
if (overlay_fs_fd < 0) {
log_debug_errno(overlay_fs_fd, "Failed to receive overlay fs fd from grandchild: %m");
_exit(EXIT_FAILURE);
}
hierarchy_path_fd = receive_one_fd(pair[0], 0);
if (hierarchy_path_fd < 0) {
log_debug_errno(hierarchy_path_fd, "Failed to receive fd from grandchild for %s: %m", mount_path);
_exit(EXIT_FAILURE);
}
/* move_mount so that it is visible on our end. */
hierarchy_path_moved_mount = path_join(overlay_prefix, mount_path);
if (!hierarchy_path_moved_mount)
_exit(EXIT_FAILURE);
(void) mkdir_p_label(hierarchy_path_moved_mount, 0555);
r = move_mount(hierarchy_path_fd, "", AT_FDCWD, hierarchy_path_moved_mount, MOVE_MOUNT_F_EMPTY_PATH);
/* Turn all overlay layer directories into FD-based references */
STRV_FOREACH(ol, m->overlay_layers) {
int chased_fd;
chased_fd = open_tree(-EBADF, *ol, 0);
if (chased_fd < 0) {
log_debug_errno(errno, "Failed to open_tree overlay layer: %m");
_exit(EXIT_FAILURE);
}
r = strv_push(&new_layers, FORMAT_PROC_FD_PATH(chased_fd)); // todo path join
if (r < 0) {
log_debug_errno(r, "Failed to append overlay layer: %m");
_exit(EXIT_FAILURE);
}
}
m->overlay_layers = strv_free(m->overlay_layers);
m->overlay_layers = TAKE_PTR(new_layers);
layers = strv_join(m->overlay_layers, ":");
if (!layers)
_exit(EXIT_FAILURE);
/* Append the underlying hierarchy path as the last lowerdir */
options = strjoin(layers, ":", FORMAT_PROC_FD_PATH(hierarchy_path_fd));
if (!options)
_exit(EXIT_FAILURE);
r = fsconfig(overlay_fs_fd, FSCONFIG_SET_STRING, "lowerdir", options, 0);
if (r < 0) {
log_debug_errno(SYNTHETIC_ERRNO(errno), "Failed to set lowerdir: %m");
_exit(EXIT_FAILURE);
}
/* Create the superblock */
r = fsconfig(overlay_fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
if (r < 0) {
log_debug_errno(r, "Failed to create overlay superblock: %m");
_exit(EXIT_FAILURE);
}
/* Signal completion to grandchild */
r = send_one_fd(pair[0], overlay_fs_fd, 0);
if (r < 0) {
log_debug_errno(r, "Failed to signal overlay configuration complete for %s: %m", mount_path);
_exit(EXIT_FAILURE);
}
}
r = wait_for_terminate(grandchild_pid, NULL);
if (r < 0) {
log_debug_errno(r, "Failed to wait for target ns fork to finish: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
return 0;
}
static const char *const protect_home_table[_PROTECT_HOME_MAX] = {
[PROTECT_HOME_NO] = "no",
[PROTECT_HOME_YES] = "yes",

View File

@ -16,6 +16,7 @@ typedef struct MountImage MountImage;
#include "fs-util.h"
#include "macro.h"
#include "namespace-util.h"
#include "pidref.h"
#include "runtime-scope.h"
#include "string-util.h"
@ -263,3 +264,8 @@ const char* namespace_type_to_string(NamespaceType t) _const_;
NamespaceType namespace_type_from_string(const char *s) _pure_;
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 "env-util.h"
#include "escape.h"
#include "execute.h"
#include "exec-credential.h"
#include "exit-status.h"
#include "fd-util.h"
@ -33,11 +34,13 @@
#include "manager.h"
#include "missing_audit.h"
#include "mount-util.h"
#include "namespace.h"
#include "open-file.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "random-util.h"
#include "runtime-scope.h"
#include "selinux-util.h"
#include "serialize.h"
#include "service.h"
@ -2700,6 +2703,70 @@ static void service_enter_reload_by_notify(Service *s) {
log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r));
}
static 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_once(LOG_WARNING, "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;
PidRef *unit_pid = NULL;
assert(s);
unit_pid = unit_main_pid(UNIT(s));
if (!pidref_is_set(unit_pid)) {
log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
return 0;
}
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,
.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_pid,
"SYSTEMD_CONFEXT_HIERARCHIES",
&p);
}
static void service_enter_reload(Service *s) {
bool killed = false;
int r;
@ -2711,6 +2778,14 @@ static void service_enter_reload(Service *s) {
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)) {
r = pidref_kill_and_sigcont(&s->main_pid, s->reload_signal);
if (r < 0) {

View File

@ -681,6 +681,41 @@ int path_pick_update_warn(
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 = {
.type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
.architecture = _ARCHITECTURE_INVALID,

View File

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

View File

@ -168,4 +168,27 @@ TEST(path_pick) {
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);

View File

@ -558,6 +558,118 @@ rm -rf "$VDIR" "$EMPTY_VDIR"
systemd-dissect --umount "$IMAGE_DIR/app0"
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 --sync
VPICK_HAS_NEW_MOUNT_API=$(journalctl -b -u testservice-50g | grep -q -F "does not support the new mount API" && echo "false" || echo "true")
journalctl -b -u testservice-50g | grep -q -F "${VBASE}_1.marker" || [ "$VPICK_HAS_NEW_MOUNT_API" = "false" ]
# 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" || [ "$VPICK_HAS_NEW_MOUNT_API" = "false" ]
# 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" || [ "$VPICK_HAS_NEW_MOUNT_API" = "false" ]
# 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" || [ "$VPICK_HAS_NEW_MOUNT_API" = "false" ]
# 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
# Test combining vpick reload/restart with RootDirectory= & RootImage=
cat >/run/systemd/system/testservice-50i.service <<EOF
[Service]
Type=notify-reload
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
RootImage=$MINIMAL_IMAGE.raw
ExtensionDirectories=${VDIR}
NotifyAccess=all
ExecStart=bash -c ' \
trap '"'"' \
printf "RELOADING=1\\nMONOTONIC_USEC=\$\${EPOCHREALTIME/[^0-9]/}" | socat - UNIX-SENDTO:\$\$NOTIFY_SOCKET; \
ls /etc | grep marker; \
cat /usr/lib/os-release; \
echo -n "READY=1" | socat - UNIX-SENDTO:\$\$NOTIFY_SOCKET; \
'"'"' SIGHUP; \
echo -n "READY=1" | socat - UNIX-SENDTO:\$\$NOTIFY_SOCKET; \
while true; do sleep 1; done; \
'
EOF
# Move away the v2 extension for now
mv "$VDIR/${VBASE}_2/" "$VDIR/.${VBASE}_2"
systemctl start testservice-50i.service
systemctl is-active testservice-50i.service
# Move the v2 extension back and reload
mv "$VDIR/.${VBASE}_2" "$VDIR/${VBASE}_2/"
systemctl try-reload-or-restart testservice-50i.service
journalctl --sync
journalctl -b -u testservice-50i | grep -q -F "${VBASE}_2.marker" || [ "$VPICK_HAS_NEW_MOUNT_API" = "false" ]
# Ensure that we are also still seeing files exclusive to the root image
journalctl -b -u testservice-50i | grep -q -F "MARKER=1"
systemctl stop testservice-50i.service
unsquashfs -no-xattrs -d /tmp/vpickminimg "$MINIMAL_IMAGE.raw"
cp /run/systemd/system/testservice-50i.service /run/systemd/system/testservice-50j.service
sed -i "s%RootImage=.*%RootDirectory=/tmp/vpickminimg%g" \
/run/systemd/system/testservice-50j.service
systemctl start testservice-50j.service
systemctl is-active testservice-50j.service
systemctl try-reload-or-restart testservice-50j.service
journalctl --sync
journalctl -b -u testservice-50j | grep -q -F "${VBASE}_2.marker" || [ "$VPICK_HAS_NEW_MOUNT_API" = "false" ]
journalctl -b -u testservice-50j | grep -q -F "MARKER=1"
systemctl stop testservice-50j.service
rm -rf "$VDIR" "$VDIR2" /tmp/vpickminimg
# Test that an extension consisting of an empty directory under /etc/extensions/ takes precedence
mkdir -p /var/lib/extensions/
ln -s /tmp/app-nodistro.raw /var/lib/extensions/app-nodistro.raw