Merge pull request #24263 from pothos/sysext-for-static-binaries

sysext: Support distribution-independent extensions with static binaries
This commit is contained in:
Lennart Poettering 2022-08-15 13:34:54 +02:00 committed by GitHub
commit e228d48b9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 9 deletions

View file

@ -411,6 +411,18 @@
determines the fallback hostname.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ARCHITECTURE=</varname></term>
<listitem><para>A string that specifies which CPU architecture the userspace binaries require.
The architecture identifiers are the same as for <varname>ConditionArchitecture=</varname>
described in <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
The field is optional and should only be used when just single architecture is supported.
It may provide redundant information when used in a GPT partition with a GUID type that already
encodes the architecture. If this is not the case, the architecture should be specified in
e.g., an extension image, to prevent an incompatible host from loading it.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SYSEXT_LEVEL=</varname></term>

View file

@ -115,12 +115,17 @@
<para>A simple mechanism for version compatibility is enforced: a system extension image must carry a
<filename>/usr/lib/extension-release.d/extension-release.<replaceable>$name</replaceable></filename>
file, which must match its image name, that is compared with the host <filename>os-release</filename>
file: the contained <varname>ID=</varname> fields have to match, as well as the
<varname>SYSEXT_LEVEL=</varname> field (if defined). If the latter is not defined, the
<varname>VERSION_ID=</varname> field has to match instead. System extensions should not ship a
<filename>/usr/lib/os-release</filename> file (as that would be merged into the host
<filename>/usr/</filename> tree, overriding the host OS version data, which is not desirable). The
<filename>extension-release</filename> file follows the same format and semantics, and carries the same
file: the contained <varname>ID=</varname> fields have to match unless <literal>_any</literal> is set
for the extension. If the extension <varname>ID=</varname> is not <literal>_any</literal>, the
<varname>SYSEXT_LEVEL=</varname> field (if defined) has to match. If the latter is not defined, the
<varname>VERSION_ID=</varname> field has to match instead. If the extension defines the
<varname>ARCHITECTURE=</varname> field and the value is not <literal>_any</literal> it has to match the kernel's
architecture reported by <citerefentry><refentrytitle>uname</refentrytitle><manvolnum>2</manvolnum></citerefentry>
but the used architecture identifiers are the same as for <varname>ConditionArchitecture=</varname>
described in <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
System extensions should not ship a <filename>/usr/lib/os-release</filename> file (as that would be merged
into the host <filename>/usr/</filename> tree, overriding the host OS version data, which is not desirable).
The <filename>extension-release</filename> file follows the same format and semantics, and carries the same
content, as the <filename>os-release</filename> file of the OS, but it describes the resources carried
in the extension image.</para>
</refsect1>

View file

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "architecture.h"
#include "env-util.h"
#include "extension-release.h"
#include "log.h"
@ -15,7 +16,7 @@ int extension_release_validate(
const char *host_sysext_scope,
char **extension_release) {
const char *extension_release_id = NULL, *extension_release_sysext_level = NULL;
const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;
assert(name);
assert(!isempty(host_os_release_id));
@ -48,13 +49,30 @@ int extension_release_validate(
}
}
/* When the architecture field is present and not '_any' it must match the host - for now just look at uname but in
* the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
!streq(architecture_to_string(uname_architecture()), extension_architecture)) {
log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
name, extension_architecture, architecture_to_string(uname_architecture()));
return 0;
}
extension_release_id = strv_env_pairs_get(extension_release, "ID");
if (isempty(extension_release_id)) {
log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s'",
log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s' or be '_any'",
name, host_os_release_id);
return 0;
}
/* A sysext with no host OS dependency (static binaries or scripts) can match
* '_any' host OS, and VERSION_ID or SYSEXT_LEVEL are not required anywhere */
if (streq(extension_release_id, "_any")) {
log_debug("Extension '%s' matches '_any' OS.", name);
return 1;
}
if (!streq(host_os_release_id, extension_release_id)) {
log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
name, extension_release_id, host_os_release_id);

View file

@ -712,6 +712,13 @@ EOF
chmod +x "$initdir/opt/script1.sh"
echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file"
mksquashfs "$initdir" "$oldinitdir/usr/share/app1.raw" -noappend
export initdir="$TESTDIR/app-nodistro"
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system"
( echo "ID=_any"
echo "ARCHITECTURE=_any" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-nodistro"
echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file"
mksquashfs "$initdir" "$oldinitdir/usr/share/app-nodistro.raw" -noappend
)
}

View file

@ -305,6 +305,7 @@ systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.r
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
systemd-run -P --property ExtensionImages=/usr/share/app-nodistro.raw --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
cat >/run/systemd/system/testservice-50e.service <<EOF
[Service]
MountAPIVFS=yes
@ -323,17 +324,19 @@ systemctl start testservice-50e.service
systemctl is-active testservice-50e.service
# ExtensionDirectories will set up an overlay
mkdir -p "${image_dir}/app0" "${image_dir}/app1"
mkdir -p "${image_dir}/app0" "${image_dir}/app1" "${image_dir}/app-nodistro"
systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistent" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
systemd-dissect --mount /usr/share/app0.raw "${image_dir}/app0"
systemd-dissect --mount /usr/share/app1.raw "${image_dir}/app1"
systemd-dissect --mount /usr/share/app-nodistro.raw "${image_dir}/app-nodistro"
systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
systemd-run -P --property ExtensionDirectories="${image_dir}/app0 ${image_dir}/app1" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
systemd-run -P --property ExtensionDirectories="${image_dir}/app-nodistro" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
cat >/run/systemd/system/testservice-50f.service <<EOF
[Service]
MountAPIVFS=yes