diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index c3df4166d55..b06a4ed59ee 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -139,6 +139,14 @@
not be able to log via the syslog or journal protocols to the host logging infrastructure, unless the
relevant sockets are mounted from the host, specifically:
+ The host's
+ os-release5
+ file will be made available for the service (read-only) as
+ /run/host/os-release.
+ It will be updated automatically on soft reboot (see:
+ systemd-soft-reboot.service8),
+ in case the service is configured to survive it.
+
Mounting logging sockets into root environment
@@ -172,6 +180,14 @@
Units making use of RootImage= automatically gain an
After= dependency on systemd-udevd.service.
+ The host's
+ os-release5
+ file will be made available for the service (read-only) as
+ /run/host/os-release.
+ It will be updated automatically on soft reboot (see:
+ systemd-soft-reboot.service8),
+ in case the service is configured to survive it.
+
diff --git a/src/core/execute.c b/src/core/execute.c
index 067f7bdb8bd..9dafdffa08f 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -3967,7 +3967,7 @@ static int apply_mount_namespace(
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL,
**read_write_paths_cleanup = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL,
- *extension_dir = NULL;
+ *extension_dir = NULL, *host_os_release = NULL;
const char *root_dir = NULL, *root_image = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
char **read_write_paths;
NamespaceInfo ns_info;
@@ -4087,11 +4087,24 @@ static int apply_mount_namespace(
extension_dir = strdup("/run/systemd/unit-extensions");
if (!extension_dir)
return -ENOMEM;
+
+ /* If running under a different root filesystem, propagate the host's os-release. We make a
+ * copy rather than just bind mounting it, so that it can be updated on soft-reboot. */
+ if (root_dir || root_image) {
+ host_os_release = strdup("/run/systemd/propagate/os-release");
+ if (!host_os_release)
+ return -ENOMEM;
+ }
} else {
assert(params->runtime_scope == RUNTIME_SCOPE_USER);
if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0)
return -ENOMEM;
+
+ if (root_dir || root_image) {
+ if (asprintf(&host_os_release, "/run/user/" UID_FMT "/systemd/propagate/os-release", geteuid()) < 0)
+ return -ENOMEM;
+ }
}
if (root_image) {
@@ -4139,6 +4152,7 @@ static int apply_mount_namespace(
incoming_dir,
extension_dir,
root_dir || root_image ? params->notify_socket : NULL,
+ host_os_release,
error_path);
/* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports
diff --git a/src/core/main.c b/src/core/main.c
index bbbf77a7792..7094be8dba0 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -35,6 +35,7 @@
#include "clock-util.h"
#include "conf-parser.h"
#include "confidential-virt.h"
+#include "copy.h"
#include "cpu-set-util.h"
#include "crash-handler.h"
#include "dbus-manager.h"
@@ -1382,6 +1383,38 @@ static int os_release_status(void) {
return 0;
}
+static int setup_os_release(RuntimeScope scope) {
+ _cleanup_free_ char *os_release_dst = NULL;
+ const char *os_release_src = "/etc/os-release";
+ int r;
+
+ if (access("/etc/os-release", F_OK) < 0) {
+ if (errno != ENOENT)
+ log_debug_errno(errno, "Failed to check if /etc/os-release exists, ignoring: %m");
+
+ os_release_src = "/usr/lib/os-release";
+ }
+
+ if (scope == RUNTIME_SCOPE_SYSTEM) {
+ os_release_dst = strdup("/run/systemd/propagate/os-release");
+ if (!os_release_dst)
+ return log_oom_debug();
+ } else {
+ if (asprintf(&os_release_dst, "/run/user/" UID_FMT "/systemd/propagate/os-release", geteuid()) < 0)
+ return log_oom_debug();
+ }
+
+ r = mkdir_parents_label(os_release_dst, 0755);
+ if (r < 0 && r != -EEXIST)
+ return log_debug_errno(r, "Failed to create parent directory of %s, ignoring: %m", os_release_dst);
+
+ r = copy_file(os_release_src, os_release_dst, /* open_flags= */ 0, 0644, COPY_MAC_CREATE);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create %s, ignoring: %m", os_release_dst);
+
+ return 0;
+}
+
static int write_container_id(void) {
const char *c;
int r = 0; /* avoid false maybe-uninitialized warning */
@@ -2253,6 +2286,12 @@ static int initialize_runtime(
bump_file_max_and_nr_open();
test_usr();
write_container_id();
+
+ /* Copy os-release to the propagate directory, so that we update it for services running
+ * under RootDirectory=/RootImage= when we do a soft reboot. */
+ r = setup_os_release(RUNTIME_SCOPE_SYSTEM);
+ if (r < 0)
+ log_warning_errno(r, "Failed to copy os-release for propagation, ignoring: %m");
}
r = watchdog_set_device(arg_watchdog_device);
@@ -2275,6 +2314,9 @@ static int initialize_runtime(
(void) mkdir_p_label(p, 0755);
(void) make_inaccessible_nodes(p, UID_INVALID, GID_INVALID);
+ r = setup_os_release(RUNTIME_SCOPE_USER);
+ if (r < 0)
+ log_warning_errno(r, "Failed to copy os-release for propagation, ignoring: %m");
break;
}
diff --git a/src/core/namespace.c b/src/core/namespace.c
index f39ab2f4689..850454944e7 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -1701,7 +1701,8 @@ static size_t namespace_calculate_mounts(
const char *creds_path,
const char* log_namespace,
bool setup_propagate,
- const char* notify_socket) {
+ const char* notify_socket,
+ const char* host_os_release) {
size_t protect_home_cnt;
size_t protect_system_cnt =
@@ -1746,6 +1747,7 @@ static size_t namespace_calculate_mounts(
!!log_namespace +
setup_propagate + /* /run/systemd/incoming */
!!notify_socket +
+ !!host_os_release +
ns_info->private_network + /* /sys */
ns_info->private_ipc; /* /dev/mqueue */
}
@@ -2005,6 +2007,7 @@ int setup_namespace(
const char *incoming_dir,
const char *extension_dir,
const char *notify_socket,
+ const char *host_os_release,
char **error_path) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
@@ -2130,7 +2133,8 @@ int setup_namespace(
creds_path,
log_namespace,
setup_propagate,
- notify_socket);
+ notify_socket,
+ host_os_release);
if (n_mounts > 0) {
m = mounts = new0(MountEntry, n_mounts);
@@ -2365,6 +2369,15 @@ int setup_namespace(
.read_only = true,
};
+ if (host_os_release)
+ *(m++) = (MountEntry) {
+ .path_const = "/run/host/os-release",
+ .source_const = host_os_release,
+ .mode = BIND_MOUNT,
+ .read_only = true,
+ .ignore = true, /* Live copy, don't hard-fail if it goes missing */
+ };
+
assert(mounts + n_mounts == m);
/* Prepend the root directory where that's necessary */
diff --git a/src/core/namespace.h b/src/core/namespace.h
index 4ddd6a7d583..44e8f097dac 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -133,6 +133,7 @@ int setup_namespace(
const char *incoming_dir,
const char *extension_dir,
const char *notify_socket,
+ const char *host_os_release,
char **error_path);
#define RUN_SYSTEMD_EMPTY "/run/systemd/empty"
diff --git a/src/portable/portable.c b/src/portable/portable.c
index 5891547b90e..c2d01cf4dbc 100644
--- a/src/portable/portable.c
+++ b/src/portable/portable.c
@@ -1052,20 +1052,12 @@ static int install_chroot_dropin(
return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m");
if (endswith(m->name, ".service")) {
- const char *os_release_source, *root_type;
+ const char *root_type;
_cleanup_free_ char *base_name = NULL;
Image *ext;
root_type = root_setting_from_image(type);
- if (access("/etc/os-release", F_OK) < 0) {
- if (errno != ENOENT)
- return log_debug_errno(errno, "Failed to check if /etc/os-release exists: %m");
-
- os_release_source = "/usr/lib/os-release";
- } else
- os_release_source = "/etc/os-release";
-
r = path_extract_filename(m->image_path ?: image_path, &base_name);
if (r < 0)
return log_debug_errno(r, "Failed to extract basename from '%s': %m", m->image_path ?: image_path);
@@ -1075,7 +1067,6 @@ static int install_chroot_dropin(
"[Service]\n",
root_type, image_path, "\n"
"Environment=PORTABLE=", base_name, "\n"
- "BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n"
"LogExtraFields=PORTABLE=", base_name, "\n"))
return -ENOMEM;
diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c
index b6ee628533e..25aafc35ca8 100644
--- a/src/test/test-namespace.c
+++ b/src/test/test-namespace.c
@@ -205,6 +205,7 @@ TEST(protect_kernel_logs) {
NULL,
NULL,
NULL,
+ NULL,
NULL);
assert_se(r == 0);
diff --git a/src/test/test-ns.c b/src/test/test-ns.c
index 3a3af3584d4..77afd2f6b9e 100644
--- a/src/test/test-ns.c
+++ b/src/test/test-ns.c
@@ -107,6 +107,7 @@ int main(int argc, char *argv[]) {
NULL,
NULL,
NULL,
+ NULL,
NULL);
if (r < 0) {
log_error_errno(r, "Failed to set up namespace: %m");
diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh
index cf31ec72630..b97766a1e24 100755
--- a/test/units/testsuite-50.sh
+++ b/test/units/testsuite-50.sh
@@ -583,4 +583,6 @@ grep -q -F "MARKER_CONFEXT_123" /etc/testfile
systemd-confext unmerge
rm -rf /run/confexts/ testjob/
+systemd-run -P -p RootImage="${image}.raw" cat /run/host/os-release | cmp "${os_release}"
+
touch /testok
diff --git a/test/units/testsuite-82.sh b/test/units/testsuite-82.sh
index a078d97e75c..a1c82a9fc9d 100755
--- a/test/units/testsuite-82.sh
+++ b/test/units/testsuite-82.sh
@@ -54,6 +54,8 @@ elif [ -f /run/testsuite82.touch2 ]; then
# Test that we really are in the new overlayfs root fs
read -r x /tmp/nextroot-lower/lower
- mount -t overlay nextroot /run/nextroot -o lowerdir=/:/tmp/nextroot-lower,ro
+
+ # Copy os-release away, so that we can manipulate it and check that it is updated in the propagate
+ # directory across soft reboots.
+ mkdir -p /tmp/nextroot-lower/usr/lib
+ cp /etc/os-release /tmp/nextroot-lower/usr/lib/os-release
+ echo MARKER=1 >>/tmp/nextroot-lower/usr/lib/os-release
+ cmp /etc/os-release /run/systemd/propagate/os-release
+ (! grep -q MARKER=1 /etc/os-release)
+
+ mount -t overlay nextroot /run/nextroot -o lowerdir=/tmp/nextroot-lower:/,ro
# Bind our current root into the target so that we later can return to it
mount --bind / /run/nextroot/original-root