Skip to content

Commit 41712cd

Browse files
tormath1poettering
authored andcommitted
sysext: support EXTENSION_RELOAD_MANAGER metadata
This metadata (EXTENSION_RELOAD_MANAGER) can be set to "1" to reload the manager when merging/refreshing/unmerging a system extension image. This can be useful in case the sysext image provides systemd units that need to be loaded. With `--no-reload`, one can deactivate the EXTENSION_RELOAD_MANAGER metadata interpretation. Signed-off-by: Mathieu Tortuyaux <[email protected]>
1 parent 121ce4a commit 41712cd

File tree

4 files changed

+185
-1
lines changed

4 files changed

+185
-1
lines changed

man/systemd-sysext.xml

+16
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@
139139
architecture reported by <citerefentry><refentrytitle>uname</refentrytitle><manvolnum>2</manvolnum></citerefentry>
140140
but the used architecture identifiers are the same as for <varname>ConditionArchitecture=</varname>
141141
described in <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
142+
<varname>EXTENSION_RELOAD_MANAGER=</varname> can be set to 1 if the extension requires a service manager reload after application
143+
of the extension. Note that the for the reasons mentioned earlier:
144+
<ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> remain
145+
the recommended way to ship system services.
146+
142147
System extensions should not ship a <filename>/usr/lib/os-release</filename> file (as that would be merged
143148
into the host <filename>/usr/</filename> tree, overriding the host OS version data, which is not desirable).
144149
The <filename>extension-release</filename> file follows the same format and semantics, and carries the same
@@ -299,6 +304,17 @@
299304
it.</para></listitem>
300305
</varlistentry>
301306

307+
<varlistentry>
308+
<term><option>--no-reload</option></term>
309+
310+
<listitem>
311+
<para>When used with <command>merge</command>,
312+
<command>unmerge</command> or <command>refresh</command>, do not reload daemon
313+
after executing the changes even if an extension that is applied requires a reload via the
314+
<varname>EXTENSION_RELOAD_MANAGER=</varname> set to 1.</para>
315+
</listitem>
316+
</varlistentry>
317+
302318
<xi:include href="standard-options.xml" xpointer="no-pager" />
303319
<xi:include href="standard-options.xml" xpointer="no-legend" />
304320
<xi:include href="standard-options.xml" xpointer="json" />

src/sysext/sysext.c

+121-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@
77
#include <sys/mount.h>
88
#include <unistd.h>
99

10+
#include "sd-bus.h"
11+
1012
#include "build.h"
13+
#include "bus-locator.h"
14+
#include "bus-error.h"
15+
#include "bus-unit-util.h"
16+
#include "bus-util.h"
1117
#include "capability-util.h"
1218
#include "chase.h"
19+
#include "constants.h"
1320
#include "devnum-util.h"
1421
#include "discover-image.h"
1522
#include "dissect-image.h"
@@ -45,6 +52,7 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
4552
static PagerFlags arg_pager_flags = 0;
4653
static bool arg_legend = true;
4754
static bool arg_force = false;
55+
static bool arg_no_reload = false;
4856
static int arg_noexec = -1;
4957
static ImagePolicy *arg_image_policy = NULL;
5058

@@ -144,6 +152,87 @@ static int is_our_mount_point(const char *p) {
144152
return true;
145153
}
146154

155+
static int need_reload(void) {
156+
/* Parse the mounted images to find out if we need
157+
to reload the daemon. */
158+
int r;
159+
160+
if (arg_no_reload)
161+
return false;
162+
163+
STRV_FOREACH(p, arg_hierarchies) {
164+
_cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL;
165+
_cleanup_strv_free_ char **mounted_extensions = NULL;
166+
167+
r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
168+
if (r == -ENOENT) {
169+
log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
170+
continue;
171+
}
172+
if (r < 0) {
173+
log_warning_errno(r, "Failed to resolve path to hierarchy '%s%s': %m, ignoring.", strempty(arg_root), *p);
174+
continue;
175+
}
176+
177+
r = is_our_mount_point(resolved);
178+
if (r < 0)
179+
return r;
180+
if (!r)
181+
continue;
182+
183+
f = path_join(*p, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
184+
if (!f)
185+
return log_oom();
186+
187+
r = read_full_file(f, &buf, NULL);
188+
if (r < 0)
189+
return log_error_errno(r, "Failed to open '%s': %m", f);
190+
191+
mounted_extensions = strv_split_newlines(buf);
192+
if (!mounted_extensions)
193+
return log_oom();
194+
195+
STRV_FOREACH(extension, mounted_extensions) {
196+
_cleanup_strv_free_ char **extension_release = NULL;
197+
const char *extension_reload_manager = NULL;
198+
int b;
199+
200+
r = load_extension_release_pairs(arg_root, arg_image_class, *extension, /* relax_extension_release_check */ true, &extension_release);
201+
if (r < 0) {
202+
log_debug_errno(r, "Failed to parse extension-release metadata of %s, ignoring: %m", *extension);
203+
continue;
204+
}
205+
206+
extension_reload_manager = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER");
207+
if (isempty(extension_reload_manager))
208+
continue;
209+
210+
b = parse_boolean(extension_reload_manager);
211+
if (b < 0) {
212+
log_warning_errno(b, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m");
213+
continue;
214+
}
215+
216+
if (b)
217+
/* If at least one extension wants a reload, we reload. */
218+
return true;
219+
}
220+
}
221+
222+
return false;
223+
}
224+
225+
static int daemon_reload(void) {
226+
sd_bus *bus;
227+
int r;
228+
229+
r = bus_connect_system_systemd(&bus);
230+
if (r < 0)
231+
return log_error_errno(r, "Failed to get D-Bus connection: %m");
232+
233+
return bus_service_manager_reload(bus);
234+
}
235+
147236
static int unmerge_hierarchy(const char *p) {
148237
int r;
149238

@@ -169,6 +258,12 @@ static int unmerge_hierarchy(const char *p) {
169258

170259
static int unmerge(void) {
171260
int r, ret = 0;
261+
bool need_to_reload;
262+
263+
r = need_reload();
264+
if (r < 0)
265+
return r;
266+
need_to_reload = r > 0;
172267

173268
STRV_FOREACH(p, arg_hierarchies) {
174269
_cleanup_free_ char *resolved = NULL;
@@ -191,6 +286,12 @@ static int unmerge(void) {
191286
ret = r;
192287
}
193288

289+
if (need_to_reload) {
290+
r = daemon_reload();
291+
if (r < 0)
292+
return r;
293+
}
294+
194295
return ret;
195296
}
196297

@@ -784,7 +885,19 @@ static int merge(Hashmap *images) {
784885
if (r < 0)
785886
return r;
786887

787-
return r != 123; /* exit code 123 means: didn't do anything */
888+
if (r == 123) /* exit code 123 means: didn't do anything */
889+
return 0;
890+
891+
r = need_reload();
892+
if (r < 0)
893+
return r;
894+
if (r > 0) {
895+
r = daemon_reload();
896+
if (r < 0)
897+
return r;
898+
}
899+
900+
return 1;
788901
}
789902

790903
static int image_discover_and_read_metadata(Hashmap **ret_images) {
@@ -955,6 +1068,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
9551068
" --json=pretty|short|off\n"
9561069
" Generate JSON output\n"
9571070
" --force Ignore version incompatibilities\n"
1071+
" --no-reload Do not reload the service manager\n"
9581072
" --image-policy=POLICY\n"
9591073
" Specify disk image dissection policy\n"
9601074
" --noexec=BOOL Whether to mount extension overlay with noexec\n"
@@ -980,6 +1094,7 @@ static int parse_argv(int argc, char *argv[]) {
9801094
ARG_FORCE,
9811095
ARG_IMAGE_POLICY,
9821096
ARG_NOEXEC,
1097+
ARG_NO_RELOAD,
9831098
};
9841099

9851100
static const struct option options[] = {
@@ -992,6 +1107,7 @@ static int parse_argv(int argc, char *argv[]) {
9921107
{ "force", no_argument, NULL, ARG_FORCE },
9931108
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
9941109
{ "noexec", required_argument, NULL, ARG_NOEXEC },
1110+
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
9951111
{}
9961112
};
9971113

@@ -1049,6 +1165,10 @@ static int parse_argv(int argc, char *argv[]) {
10491165
arg_noexec = r;
10501166
break;
10511167

1168+
case ARG_NO_RELOAD:
1169+
arg_no_reload = true;
1170+
break;
1171+
10521172
case '?':
10531173
return -EINVAL;
10541174

test/test-functions

+20
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,26 @@ EOF
846846
echo "ARCHITECTURE=_any" ) >"$initdir/etc/extension-release.d/extension-release.service-scoped-test"
847847
echo MARKER_CONFEXT_123 >"$initdir/etc/systemd/system/some_file"
848848
mksquashfs "$initdir" "$oldinitdir/etc/service-scoped-test.raw" -noappend
849+
850+
# We need to create a dedicated sysext image to test the reload mechanism. If we share an image to install the
851+
# 'foo.service' it will be loaded from another test run, which will impact the targeted test.
852+
export initdir="$TESTDIR/app-reload"
853+
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system"
854+
( echo "ID=_any"
855+
echo "ARCHITECTURE=_any"
856+
echo "EXTENSION_RELOAD_MANAGER=1" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-reload"
857+
mkdir -p "$initdir/usr/lib/systemd/system/multi-user.target.d"
858+
cat >"${initdir}/usr/lib/systemd/system/foo.service" <<EOF
859+
[Service]
860+
Type=oneshot
861+
RemainAfterExit=yes
862+
ExecStart=echo foo
863+
864+
[Install]
865+
WantedBy=multi-user.target
866+
EOF
867+
{ echo "[Unit]"; echo "Upholds=foo.service"; } > "$initdir/usr/lib/systemd/system/multi-user.target.d/10-foo-service.conf"
868+
mksquashfs "$initdir" "$oldinitdir/usr/share/app-reload.raw" -noappend
849869
)
850870
}
851871

test/units/testsuite-50.sh

+28
Original file line numberDiff line numberDiff line change
@@ -596,4 +596,32 @@ rm -rf /run/confexts/ testjob/
596596

597597
systemd-run -P -p RootImage="${image}.raw" cat /run/host/os-release | cmp "${os_release}"
598598

599+
# Test that systemd-sysext reloads the daemon.
600+
mkdir -p /var/lib/extensions/
601+
ln -s /usr/share/app-reload.raw /var/lib/extensions/app-reload.raw
602+
systemd-sysext merge --no-reload
603+
# the service should not be running
604+
if systemctl --quiet is-active foo.service; then
605+
echo "foo.service should not be active"
606+
exit 1
607+
fi
608+
systemd-sysext unmerge --no-reload
609+
systemd-sysext merge
610+
for RETRY in $(seq 60) LAST; do
611+
if journalctl --boot --unit foo.service | grep -q -P 'echo\[[0-9]+\]: foo'; then
612+
break
613+
fi
614+
if [ "${RETRY}" = LAST ]; then
615+
echo "Output of foo.service not found"
616+
exit 1
617+
fi
618+
sleep 0.5
619+
done
620+
systemd-sysext unmerge --no-reload
621+
# Grep on the Warning to find the warning helper mentioning the daemon reload.
622+
systemctl status foo.service 2>&1 | grep -q -F "Warning"
623+
systemd-sysext merge
624+
systemd-sysext unmerge
625+
systemctl status foo.service 2>&1 | grep -v -q -F "Warning"
626+
599627
touch /testok

0 commit comments

Comments
 (0)