mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #26013 from goenkam/maanya/syscfg-feature
confext: extension of sysext
This commit is contained in:
24
TODO
24
TODO
@@ -521,13 +521,13 @@ Features:
|
||||
* add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing
|
||||
an encrypted image on some host given a public key belonging to a specific
|
||||
other host, so that only hosts possessing the private key in the TPM2 chip
|
||||
can decrypt the volume key and activate the volume. Usecase: systemd-syscfg
|
||||
for a central orchestrator to generate syscfg images securely that can only
|
||||
can decrypt the volume key and activate the volume. Usecase: systemd-confext
|
||||
for a central orchestrator to generate confext images securely that can only
|
||||
be activated on one specific host (which can be used for installing a bunch
|
||||
of creds in /etc/credstore/ for example). Extending on this: allow binding
|
||||
LUKS2 TPM based encryption also to the TPM2 internal clock. Net result:
|
||||
prepare a syscfg image that can only be activated on a specific host that
|
||||
runs a specific software in a specific time window. syscfg would be
|
||||
prepare a confext image that can only be activated on a specific host that
|
||||
runs a specific software in a specific time window. confext would be
|
||||
automatically invalidated outside of it.
|
||||
|
||||
* maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of
|
||||
@@ -538,17 +538,17 @@ Features:
|
||||
this: have the report tool upload these reports every 3min somewhere. Then
|
||||
have the orchestrator collect these reports centrally over a 3min time
|
||||
window, and use them to determine what which node should now start/stop what,
|
||||
and generate a small syscfg for each node, that uses Uphold= to pin services
|
||||
on each node. The syscfg would be encrypted using the asymmetric encryption
|
||||
and generate a small confext for each node, that uses Uphold= to pin services
|
||||
on each node. The confext would be encrypted using the asymmetric encryption
|
||||
proposed above, so that it can only be activated on the specific host, if the
|
||||
software is in a good state, and within a specific time frame. Then run a
|
||||
loop on each node that sends report to orchestrator and then sysupdate to
|
||||
update syscfg. Orchestrator would be stateless, i.e. operate on desired
|
||||
update confext. Orchestrator would be stateless, i.e. operate on desired
|
||||
config and collected reports in the last 3min time window only, and thus can
|
||||
be trivially scaled up since all instances of the orchestrator should come to
|
||||
the same conclusions given the same inputs of reports/desired workload info.
|
||||
Could also be used to deliver Wireguard secrets and thus to clients, thus
|
||||
permitting zero-trust networking: secrets are rolled over via syscfg updates,
|
||||
permitting zero-trust networking: secrets are rolled over via confext updates,
|
||||
and via the time window TPM logic invalidated if node doesn't keep itself
|
||||
updated, or becomes corrupted in some way.
|
||||
|
||||
@@ -597,7 +597,7 @@ Features:
|
||||
keyring, so that the kernel does this validation for us for verity and kernel
|
||||
modules
|
||||
|
||||
* for systemd-syscfg: add a tool that can generate suitable DDIs with verity +
|
||||
* for systemd-confext: add a tool that can generate suitable DDIs with verity +
|
||||
sig using squashfs-tools-ng's library. Maybe just systemd-repart called under
|
||||
a new name with a built-in config?
|
||||
|
||||
@@ -914,12 +914,6 @@ Features:
|
||||
|
||||
* sysext: measure all activated sysext into a TPM PCR
|
||||
|
||||
* maybe add a "syscfg" concept, that is almost entirely identical to "sysext",
|
||||
but operates on /etc/ instead of /usr/ and /opt/. Use case would be: trusted,
|
||||
authenticated, atomic, additive configuration management primitive: drop in a
|
||||
configuration bundle, and activate it, so that it is instantly visible,
|
||||
comprehensively.
|
||||
|
||||
* systemd-dissect: show available versions inside of a disk image, i.e. if
|
||||
multiple versions are around of the same resource, show which ones. (in other
|
||||
words: show partition labels).
|
||||
|
||||
@@ -328,7 +328,9 @@ the journal instead of only when logging in debug mode.
|
||||
paths. Only "real" file systems and directories that only contain "real" file
|
||||
systems as submounts should be used. Do not specify API file systems such as
|
||||
`/proc/` or `/sys/` here, or hierarchies that have them as submounts. In
|
||||
particular, do not specify the root directory `/` here.
|
||||
particular, do not specify the root directory `/` here. Similarly,
|
||||
`$SYSTEMD_CONFEXT_HIERARCHIES` works for confext images and supports the
|
||||
systemd-confext multi-call functionality of sysext.
|
||||
|
||||
`systemd-tmpfiles`:
|
||||
|
||||
|
||||
@@ -442,6 +442,17 @@
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>CONFEXT_LEVEL=</varname></term>
|
||||
|
||||
<listitem><para>Semantically the same as <varname>SYSEXT_LEVEL=</varname> but for confext images.
|
||||
See <filename>/etc/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename>
|
||||
for more information.</para>
|
||||
|
||||
<para>Examples: <literal>CONFEXT_LEVEL=2</literal>, <literal>CONFEXT_LEVEL=15.14</literal>.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SYSEXT_SCOPE=</varname></term>
|
||||
<listitem><para>Takes a space-separated list of one or more of the strings
|
||||
@@ -453,6 +464,12 @@
|
||||
but not to initrd environments.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>CONFEXT_SCOPE=</varname></term>
|
||||
|
||||
<listitem><para>Semantically the same as <varname>SYSEXT_SCOPE=</varname> but for confext images.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PORTABLE_PREFIXES=</varname></term>
|
||||
<listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
|
||||
|
||||
@@ -1043,7 +1043,10 @@ manpages = [
|
||||
'systemd-suspend-then-hibernate.service'],
|
||||
''],
|
||||
['systemd-sysctl.service', '8', ['systemd-sysctl'], ''],
|
||||
['systemd-sysext', '8', ['systemd-sysext.service'], ''],
|
||||
['systemd-sysext',
|
||||
'8',
|
||||
['systemd-confext', 'systemd-confext.service', 'systemd-sysext.service'],
|
||||
''],
|
||||
['systemd-system-update-generator', '8', [], ''],
|
||||
['systemd-system.conf',
|
||||
'5',
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
<refnamediv>
|
||||
<refname>systemd-sysext</refname>
|
||||
<refname>systemd-sysext.service</refname>
|
||||
<refname>systemd-confext</refname>
|
||||
<refname>systemd-confext.service</refname>
|
||||
<refpurpose>Activates System Extension Images</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
@@ -31,6 +33,14 @@
|
||||
|
||||
<para><literallayout><filename>systemd-sysext.service</filename></literallayout></para>
|
||||
|
||||
<cmdsynopsis>
|
||||
<command>systemd-confext</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">COMMAND</arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
<para><literallayout><filename>systemd-confext.service</filename></literallayout></para>
|
||||
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
@@ -129,6 +139,29 @@
|
||||
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>
|
||||
|
||||
<para>The <command>systemd-confext</command> concept follows the same principle as the
|
||||
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
functionality but instead of working on <filename>/usr</filename> and <filename>/opt</filename>,
|
||||
<command>confext</command> will extend only <filename>/etc</filename>. Files and directories contained
|
||||
in the confext images outside of the <filename>/etc/</filename> hierarchy are <emphasis>not</emphasis>
|
||||
merged, and hence have no effect when included in the image. Formats for these images are of the
|
||||
same as sysext images.</para>
|
||||
|
||||
<para>Confexts are looked for in the directories <filename>/run/confexts/</filename>,
|
||||
<filename>/var/lib/confexts/</filename>, <filename>/usr/lib/confexts/</filename> and
|
||||
<filename>/usr/local/lib/confexts/</filename>. The first two listed directories are not suitable for
|
||||
carrying large binary images, however are still useful for carrying symlinks to them. The primary place
|
||||
for installing system extensions is <filename>/var/lib/confexts/</filename>. Any directories found in
|
||||
these search directories are considered directory based confext images, any files with the
|
||||
<filename>.raw</filename> suffix are considered disk image based confext images.</para>
|
||||
|
||||
<para>Again, just like sysext images, the confext images will contain a
|
||||
<filename>/etc/extension-release.d/extension-release.<replaceable>$name</replaceable></filename>
|
||||
file, which must match the image name (with the usual escape hatch of xattr), and again with content
|
||||
being one or more of <varname>ID=</varname>, <varname>VERSION_ID=</varname>, and
|
||||
<varname>CONFEXT_LEVEL</varname>. Confext images will then be checked and matched against the
|
||||
base OS layer.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@@ -153,20 +186,25 @@
|
||||
<filename>/usr/</filename> as if it was installed in the OS image itself.) This case works regardless if
|
||||
the underlying host <filename>/usr/</filename> is managed as immutable disk image or is a traditional
|
||||
package manager controlled (i.e. writable) tree.</para>
|
||||
</refsect1>
|
||||
|
||||
<para>For the confext case, the OSConfig project aims to perform runtime reconfiguration of OS services.
|
||||
Sometimes, there is a need to swap certain configuration parameter values or restart only a specific
|
||||
service without deployment of new code or a complete OS deployment. In other words, we want to be able
|
||||
to tie the most frequently configured options to runtime updateable flags that can be changed without a
|
||||
system reboot. This will help reduce servicing times when there is a need for changing the OS configuration.</para></refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
<para>The following commands are understood by both the sysext and confext concepts:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>status</option></term>
|
||||
|
||||
<listitem><para>When invoked without any command verb, or when <option>status</option> is specified
|
||||
the current merge status is shown, separately for both <filename>/usr/</filename> and
|
||||
<filename>/opt/</filename>.</para></listitem>
|
||||
the current merge status is shown, separately (for both <filename>/usr/</filename> and
|
||||
<filename>/opt/</filename> of sysext and for <filename>/etc/</filename> of confext).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
@@ -174,14 +212,15 @@
|
||||
<listitem><para>Merges all currently installed system extension images into
|
||||
<filename>/usr/</filename> and <filename>/opt/</filename>, by overmounting these hierarchies with an
|
||||
<literal>overlayfs</literal> file system combining the underlying hierarchies with those included in
|
||||
the extension images. This command will fail if the hierarchies are already merged.</para></listitem>
|
||||
the extension images. This command will fail if the hierarchies are already merged. For confext, the merge
|
||||
happens into the <filename>/etc/</filename> directory instead.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>unmerge</option></term>
|
||||
<listitem><para>Unmerges all currently installed system extension images from
|
||||
<filename>/usr/</filename> and <filename>/opt/</filename>, by unmounting the
|
||||
<literal>overlayfs</literal> file systems created by <option>merge</option>
|
||||
<filename>/usr/</filename> and <filename>/opt/</filename> for sysext and <filename>/etc/</filename>,
|
||||
for confext, by unmounting the <literal>overlayfs</literal> file systems created by <option>merge</option>
|
||||
prior.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -191,11 +230,11 @@
|
||||
mounted the existing <literal>overlayfs</literal> instance is unmounted temporarily, and then
|
||||
replaced by a new version. This command is useful after installing/removing system extension images,
|
||||
in order to update the <literal>overlayfs</literal> file system accordingly. If no system extensions
|
||||
are installed when this command is executed, the equivalent of <option>unmerge</option> is
|
||||
executed, without establishing any new <literal>overlayfs</literal> instance. Note that currently
|
||||
there's a brief moment where neither the old nor the new <literal>overlayfs</literal> file system is
|
||||
mounted. This implies that all resources supplied by a system extension will briefly disappear — even
|
||||
if it exists continuously during the refresh operation.</para></listitem>
|
||||
are installed when this command is executed, the equivalent of <option>unmerge</option> is executed,
|
||||
without establishing any new <literal>overlayfs</literal> instance.
|
||||
Note that currently there's a brief moment where neither the old nor the new <literal>overlayfs</literal>
|
||||
file system is mounted. This implies that all resources supplied by a system extension will briefly
|
||||
disappear — even if it exists continuously during the refresh operation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
@@ -218,16 +257,17 @@
|
||||
|
||||
<listitem><para>Operate relative to the specified root directory, i.e. establish the
|
||||
<literal>overlayfs</literal> mount not on the top-level host <filename>/usr/</filename> and
|
||||
<filename>/opt/</filename> hierarchies, but below some specified root directory.</para></listitem>
|
||||
<filename>/opt/</filename> hierarchies for sysext or <filename>/etc/</filename> for confext,
|
||||
but below some specified root directory.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--force</option></term>
|
||||
|
||||
<listitem><para>When merging system extensions into <filename>/usr/</filename> and
|
||||
<filename>/opt/</filename>, ignore version incompatibilities, i.e. force merging regardless of
|
||||
whether the version information included in the extension images matches the host or
|
||||
not.</para></listitem>
|
||||
<filename>/opt/</filename> for sysext and <filename>/etc/</filename> for confext,
|
||||
ignore version incompatibilities, i.e. force merging regardless of
|
||||
whether the version information included in the images matches the host or not.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
|
||||
85
shell-completion/bash/systemd-confext
Normal file
85
shell-completion/bash/systemd-confext
Normal file
@@ -0,0 +1,85 @@
|
||||
# systemd-confext(8) completion -*- shell-script -*-
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# systemd is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with systemd; If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
__contains_word() {
|
||||
local w word=$1; shift
|
||||
for w in "$@"; do
|
||||
[[ $w = "$word" ]] && return
|
||||
done
|
||||
}
|
||||
|
||||
_systemd-confext() {
|
||||
local i verb comps
|
||||
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
|
||||
local -A OPTS=(
|
||||
[STANDALONE]='-h --help --version
|
||||
--no-pager
|
||||
--no-legend
|
||||
--force'
|
||||
[ARG]='--root
|
||||
--json'
|
||||
)
|
||||
|
||||
local -A VERBS=(
|
||||
[STANDALONE]='status
|
||||
merge
|
||||
unmerge
|
||||
refresh
|
||||
list'
|
||||
)
|
||||
|
||||
_init_completion || return
|
||||
|
||||
if __contains_word "$prev" ${OPTS[ARG]}; then
|
||||
case $prev in
|
||||
--root)
|
||||
comps=$(compgen -A directory -- "$cur" )
|
||||
compopt -o dirnames
|
||||
;;
|
||||
--json)
|
||||
comps='pretty short off'
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$cur" = -* ]]; then
|
||||
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
for ((i=0; i < COMP_CWORD; i++)); do
|
||||
if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
|
||||
! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
|
||||
verb=${COMP_WORDS[i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z ${verb-} ]]; then
|
||||
comps=${VERBS[*]}
|
||||
elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
|
||||
comps=''
|
||||
fi
|
||||
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _systemd-confext systemd-confext
|
||||
@@ -19,6 +19,21 @@
|
||||
#include "utf8.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
/* Helper struct for naming simplicity and reusability */
|
||||
static const struct {
|
||||
const char *release_file_directory;
|
||||
const char *release_file_path_prefix;
|
||||
} image_class_release_info[_IMAGE_CLASS_MAX] = {
|
||||
[IMAGE_SYSEXT] = {
|
||||
.release_file_directory = "/usr/lib/extension-release.d/",
|
||||
.release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
|
||||
},
|
||||
[IMAGE_CONFEXT] = {
|
||||
.release_file_directory = "/etc/extension-release.d/",
|
||||
.release_file_path_prefix = "/etc/extension-release.d/extension-release.",
|
||||
}
|
||||
};
|
||||
|
||||
bool image_name_is_valid(const char *s) {
|
||||
if (!filename_is_valid(s))
|
||||
return false;
|
||||
@@ -36,10 +51,12 @@ bool image_name_is_valid(const char *s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check) {
|
||||
int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(image_class >= 0);
|
||||
assert(image_class < _IMAGE_CLASS_MAX);
|
||||
|
||||
/* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
|
||||
* always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from
|
||||
@@ -48,8 +65,9 @@ int path_is_extension_tree(const char *path, const char *extension, bool relax_e
|
||||
return -errno;
|
||||
|
||||
/* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
|
||||
* /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
|
||||
* and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
|
||||
r = open_extension_release(path, extension, relax_extension_release_check, NULL, NULL);
|
||||
r = open_extension_release(path, image_class, extension, relax_extension_release_check, NULL, NULL);
|
||||
if (r == -ENOENT) /* We got nothing */
|
||||
return 0;
|
||||
if (r < 0)
|
||||
@@ -96,18 +114,21 @@ static int extension_release_strict_xattr_value(int extension_release_fd, const
|
||||
return false;
|
||||
}
|
||||
|
||||
int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
|
||||
int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
|
||||
_cleanup_free_ char *q = NULL;
|
||||
int r, fd;
|
||||
|
||||
if (extension) {
|
||||
assert(image_class >= 0);
|
||||
assert(image_class < _IMAGE_CLASS_MAX);
|
||||
|
||||
const char *extension_full_path;
|
||||
|
||||
if (!image_name_is_valid(extension))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"The extension name %s is invalid.", extension);
|
||||
|
||||
extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
|
||||
extension_full_path = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
|
||||
r = chase(extension_full_path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
|
||||
log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
|
||||
|
||||
@@ -120,10 +141,10 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
|
||||
_cleanup_free_ char *extension_release_dir_path = NULL;
|
||||
_cleanup_closedir_ DIR *extension_release_dir = NULL;
|
||||
|
||||
r = chase_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
|
||||
r = chase_and_opendir(image_class_release_info[image_class].release_file_directory, root, CHASE_PREFIX_ROOT,
|
||||
&extension_release_dir_path, &extension_release_dir);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
|
||||
return log_debug_errno(r, "Cannot open %s%s, ignoring: %m", root, image_class_release_info[image_class].release_file_directory);
|
||||
|
||||
r = -ENOENT;
|
||||
FOREACH_DIRENT(de, extension_release_dir, return -errno) {
|
||||
@@ -137,7 +158,7 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
|
||||
continue;
|
||||
|
||||
if (!image_name_is_valid(image_name)) {
|
||||
log_debug("%s/%s is not a valid extension-release file name, ignoring.",
|
||||
log_debug("%s/%s is not a valid release file name, ignoring.",
|
||||
extension_release_dir_path, de->d_name);
|
||||
continue;
|
||||
}
|
||||
@@ -149,7 +170,7 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
|
||||
O_PATH|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (extension_release_fd < 0)
|
||||
return log_debug_errno(errno,
|
||||
"Failed to open extension-release file %s/%s: %m",
|
||||
"Failed to open release file %s/%s: %m",
|
||||
extension_release_dir_path,
|
||||
de->d_name);
|
||||
|
||||
@@ -219,16 +240,16 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
|
||||
int fopen_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
FILE *f;
|
||||
int r;
|
||||
|
||||
if (!ret_file)
|
||||
return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
|
||||
return open_extension_release(root, image_class, extension, relax_extension_release_check, ret_path, NULL);
|
||||
|
||||
r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
|
||||
r = open_extension_release(root, image_class, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -243,24 +264,27 @@ int fopen_extension_release(const char *root, const char *extension, bool relax_
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
|
||||
static int parse_release_internal(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, va_list ap) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
int r;
|
||||
|
||||
r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
|
||||
r = fopen_extension_release(root, image_class, extension, relax_extension_release_check, &p, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return parse_env_filev(f, p, ap);
|
||||
}
|
||||
|
||||
int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
|
||||
int _parse_extension_release(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) {
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
assert(image_class >= 0);
|
||||
assert(image_class < _IMAGE_CLASS_MAX);
|
||||
|
||||
va_start(ap, extension);
|
||||
r = parse_release_internal(root, relax_extension_release_check, extension, ap);
|
||||
r = parse_release_internal(root, image_class, relax_extension_release_check, extension, ap);
|
||||
va_end(ap);
|
||||
|
||||
return r;
|
||||
@@ -271,7 +295,7 @@ int _parse_os_release(const char *root, ...) {
|
||||
int r;
|
||||
|
||||
va_start(ap, root);
|
||||
r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
|
||||
r = parse_release_internal(root, -1, /* relax_extension_release_check= */ false, NULL, ap);
|
||||
va_end(ap);
|
||||
|
||||
return r;
|
||||
@@ -318,12 +342,15 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char
|
||||
return 0;
|
||||
}
|
||||
|
||||
int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret) {
|
||||
int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
int r;
|
||||
|
||||
r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
|
||||
assert(image_class >= 0);
|
||||
assert(image_class < _IMAGE_CLASS_MAX);
|
||||
|
||||
r = fopen_extension_release(root, image_class, extension, relax_extension_release_check, &p, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -5,33 +5,44 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "time-util.h"
|
||||
typedef enum ImageClass {
|
||||
IMAGE_MACHINE,
|
||||
IMAGE_PORTABLE,
|
||||
IMAGE_SYSEXT,
|
||||
IMAGE_CONFEXT,
|
||||
_IMAGE_CLASS_MAX,
|
||||
_IMAGE_CLASS_INVALID = -EINVAL,
|
||||
} ImageClass;
|
||||
|
||||
const char* image_class_to_string(ImageClass cl) _const_;
|
||||
ImageClass image_class_from_string(const char *s) _pure_;
|
||||
|
||||
/* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
|
||||
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
|
||||
|
||||
bool image_name_is_valid(const char *s) _pure_;
|
||||
|
||||
int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check);
|
||||
int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
|
||||
static inline int path_is_os_tree(const char *path) {
|
||||
return path_is_extension_tree(path, NULL, false);
|
||||
return path_is_extension_tree(IMAGE_SYSEXT, path, NULL, false);
|
||||
}
|
||||
|
||||
int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
|
||||
int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
|
||||
static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
|
||||
return open_extension_release(root, NULL, false, ret_path, ret_fd);
|
||||
return open_extension_release(root, IMAGE_SYSEXT, NULL, false, ret_path, ret_fd);
|
||||
}
|
||||
|
||||
int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
|
||||
int fopen_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
|
||||
static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
|
||||
return fopen_extension_release(root, NULL, false, ret_path, ret_file);
|
||||
return fopen_extension_release(root, IMAGE_SYSEXT, NULL, false, ret_path, ret_file);
|
||||
}
|
||||
|
||||
int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
|
||||
int _parse_extension_release(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
|
||||
int _parse_os_release(const char *root, ...) _sentinel_;
|
||||
#define parse_extension_release(root, relax_extension_release_check, extension, ...) _parse_extension_release(root, relax_extension_release_check, extension, __VA_ARGS__, NULL)
|
||||
#define parse_extension_release(root, image_class, relax_extension_release_check, extension, ...) _parse_extension_release(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
|
||||
#define parse_os_release(root, ...) _parse_os_release(root, __VA_ARGS__, NULL)
|
||||
|
||||
int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret);
|
||||
int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
|
||||
int load_os_release_pairs(const char *root, char ***ret);
|
||||
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
|
||||
|
||||
|
||||
@@ -1423,7 +1423,7 @@ static int apply_one_mount(
|
||||
if (isempty(host_os_release_id))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
|
||||
|
||||
r = load_extension_release_pairs(mount_entry_source(m), extension_name, /* relax_extension_release_check= */ false, &extension_release);
|
||||
r = load_extension_release_pairs(mount_entry_source(m), IMAGE_SYSEXT, extension_name, /* relax_extension_release_check= */ false, &extension_release);
|
||||
if (r == -ENOENT && m->ignore)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
@@ -1435,7 +1435,8 @@ static int apply_one_mount(
|
||||
host_os_release_version_id,
|
||||
host_os_release_sysext_level,
|
||||
/* host_sysext_scope */ NULL, /* Leave empty, we need to accept both system and portable */
|
||||
extension_release);
|
||||
extension_release,
|
||||
IMAGE_SYSEXT);
|
||||
if (r == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Directory %s extension-release metadata does not match the root's", extension_name);
|
||||
if (r < 0)
|
||||
@@ -2155,7 +2156,7 @@ int setup_namespace(
|
||||
}
|
||||
|
||||
if (n_extension_images > 0 || !strv_isempty(extension_directories)) {
|
||||
r = parse_env_extension_hierarchies(&hierarchies);
|
||||
r = parse_env_extension_hierarchies(&hierarchies, "SYSTEMD_SYSEXT_HIERARCHIES");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ static int extract_now(
|
||||
/* First, find os-release/extension-release and send it upstream (or just save it). */
|
||||
if (path_is_extension) {
|
||||
os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name);
|
||||
r = open_extension_release(where, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
|
||||
r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
|
||||
} else {
|
||||
os_release_id = "/etc/os-release";
|
||||
r = open_os_release(where, &os_release_path, &os_release_fd);
|
||||
@@ -607,7 +607,7 @@ static int extract_image_and_extensions(
|
||||
return r;
|
||||
|
||||
if (validate_sysext) {
|
||||
r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
|
||||
r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
|
||||
if (r == 0)
|
||||
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
|
||||
if (r < 0)
|
||||
@@ -948,17 +948,17 @@ static int append_release_log_fields(
|
||||
|
||||
static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
|
||||
[IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
|
||||
[IMAGE_EXTENSION] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
|
||||
[IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
|
||||
};
|
||||
static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
|
||||
[IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
|
||||
[IMAGE_EXTENSION] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
|
||||
[IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
|
||||
};
|
||||
_cleanup_strv_free_ char **fields = NULL;
|
||||
const char *id = NULL, *version = NULL;
|
||||
int r;
|
||||
|
||||
assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_EXTENSION));
|
||||
assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT));
|
||||
assert(!strv_isempty((char *const *)field_ids[type]));
|
||||
assert(!strv_isempty((char *const *)field_versions[type]));
|
||||
assert(field_name);
|
||||
@@ -1106,7 +1106,7 @@ static int install_chroot_dropin(
|
||||
* still be able to identify what applies to what. */
|
||||
r = append_release_log_fields(&text,
|
||||
ordered_hashmap_get(extension_releases, ext->name),
|
||||
IMAGE_EXTENSION,
|
||||
IMAGE_SYSEXT,
|
||||
"PORTABLE_EXTENSION_NAME_AND_VERSION");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -63,9 +63,14 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
|
||||
* because extension images are supposed to extend /usr/, so you get into recursive races, especially
|
||||
* with directory-based extensions, as the kernel's OverlayFS explicitly checks for this and errors
|
||||
* out with -ELOOP if it finds that a lowerdir= is a child of another lowerdir=. */
|
||||
[IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
|
||||
"/run/extensions\0" /* and here too */
|
||||
"/var/lib/extensions\0", /* the main place for images */
|
||||
[IMAGE_SYSEXT] = "/etc/extensions\0" /* only place symlinks here */
|
||||
"/run/extensions\0" /* and here too */
|
||||
"/var/lib/extensions\0", /* the main place for images */
|
||||
|
||||
[IMAGE_CONFEXT] = "/run/confexts\0" /* only place symlinks here */
|
||||
"/var/lib/confexts\0" /* the main place for images */
|
||||
"/usr/local/lib/confexts\0"
|
||||
"/usr/lib/confexts\0",
|
||||
};
|
||||
|
||||
static Image *image_free(Image *i) {
|
||||
@@ -1152,7 +1157,7 @@ int image_read_metadata(Image *i) {
|
||||
_cleanup_free_ char *hostname = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
if (i->class == IMAGE_EXTENSION) {
|
||||
if (i->class == IMAGE_SYSEXT) {
|
||||
r = extension_has_forbidden_content(i->path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1190,7 +1195,7 @@ int image_read_metadata(Image *i) {
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
|
||||
|
||||
r = load_extension_release_pairs(i->path, i->name, /* relax_extension_release_check= */ false, &extension_release);
|
||||
r = load_extension_release_pairs(i->path, i->class, i->name, /* relax_extension_release_check= */ false, &extension_release);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m");
|
||||
|
||||
@@ -1325,7 +1330,8 @@ DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
|
||||
static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
|
||||
[IMAGE_MACHINE] = "machine",
|
||||
[IMAGE_PORTABLE] = "portable",
|
||||
[IMAGE_EXTENSION] = "extension",
|
||||
[IMAGE_SYSEXT] = "extension",
|
||||
[IMAGE_CONFEXT] = "confext"
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
|
||||
|
||||
@@ -9,18 +9,11 @@
|
||||
#include "hashmap.h"
|
||||
#include "lock-util.h"
|
||||
#include "macro.h"
|
||||
#include "os-util.h"
|
||||
#include "path-util.h"
|
||||
#include "string-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef enum ImageClass {
|
||||
IMAGE_MACHINE,
|
||||
IMAGE_PORTABLE,
|
||||
IMAGE_EXTENSION,
|
||||
_IMAGE_CLASS_MAX,
|
||||
_IMAGE_CLASS_INVALID = -EINVAL,
|
||||
} ImageClass;
|
||||
|
||||
typedef enum ImageType {
|
||||
IMAGE_DIRECTORY,
|
||||
IMAGE_SUBVOLUME,
|
||||
@@ -77,9 +70,6 @@ int image_read_only(Image *i, bool b);
|
||||
const char* image_type_to_string(ImageType t) _const_;
|
||||
ImageType image_type_from_string(const char *s) _pure_;
|
||||
|
||||
const char* image_class_to_string(ImageClass cl) _const_;
|
||||
ImageClass image_class_from_string(const char *s) _pure_;
|
||||
|
||||
int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local);
|
||||
int image_name_lock(const char *name, int operation, LockFile *ret);
|
||||
|
||||
|
||||
@@ -1791,7 +1791,7 @@ int dissected_image_mount(
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
r = path_is_extension_tree(where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
|
||||
r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
@@ -3054,7 +3054,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
|
||||
* we allow a fallback that matches on the first extension-release
|
||||
* file found in the directory, if one named after the image cannot
|
||||
* be found first. */
|
||||
r = open_extension_release(t, m->image_name, /* relax_extension_release_check= */ false, NULL, &fd);
|
||||
r = open_extension_release(t, IMAGE_SYSEXT, m->image_name, /* relax_extension_release_check= */ false, NULL, &fd);
|
||||
if (r < 0)
|
||||
fd = r; /* Propagate the error. */
|
||||
break;
|
||||
@@ -3606,7 +3606,7 @@ int verity_dissect_and_mount(
|
||||
|
||||
assert(!isempty(required_host_os_release_id));
|
||||
|
||||
r = load_extension_release_pairs(dest, dissected_image->image_name, relax_extension_release_check, &extension_release);
|
||||
r = load_extension_release_pairs(dest, IMAGE_SYSEXT, dissected_image->image_name, relax_extension_release_check, &extension_release);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
|
||||
|
||||
@@ -3616,7 +3616,8 @@ int verity_dissect_and_mount(
|
||||
required_host_os_release_version_id,
|
||||
required_host_os_release_sysext_level,
|
||||
required_sysext_scope,
|
||||
extension_release);
|
||||
extension_release,
|
||||
IMAGE_SYSEXT);
|
||||
if (r == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
|
||||
if (r < 0)
|
||||
|
||||
@@ -13,39 +13,42 @@ int extension_release_validate(
|
||||
const char *name,
|
||||
const char *host_os_release_id,
|
||||
const char *host_os_release_version_id,
|
||||
const char *host_os_release_sysext_level,
|
||||
const char *host_sysext_scope,
|
||||
char **extension_release) {
|
||||
const char *host_os_extension_release_level,
|
||||
const char *host_extension_scope,
|
||||
char **extension_release,
|
||||
ImageClass image_class) {
|
||||
|
||||
const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;
|
||||
const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL;
|
||||
const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
|
||||
const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
|
||||
|
||||
assert(name);
|
||||
assert(!isempty(host_os_release_id));
|
||||
|
||||
/* Now that we can look into the extension image, let's see if the OS version is compatible */
|
||||
/* Now that we can look into the extension/confext image, let's see if the OS version is compatible */
|
||||
if (strv_isempty(extension_release)) {
|
||||
log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
|
||||
log_debug("Extension '%s' carries no release data, ignoring.", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (host_sysext_scope) {
|
||||
_cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
|
||||
const char *extension_sysext_scope;
|
||||
if (host_extension_scope) {
|
||||
_cleanup_strv_free_ char **scope_list = NULL;
|
||||
const char *scope;
|
||||
bool valid;
|
||||
|
||||
extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
|
||||
if (extension_sysext_scope) {
|
||||
extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
|
||||
if (!extension_sysext_scope_list)
|
||||
scope = strv_env_pairs_get(extension_release, extension_scope);
|
||||
if (scope) {
|
||||
scope_list = strv_split(scope, WHITESPACE);
|
||||
if (!scope_list)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* by default extension are good for attachment in portable service and on the system */
|
||||
/* By default extension are good for attachment in portable service and on the system */
|
||||
valid = strv_contains(
|
||||
extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
|
||||
host_sysext_scope);
|
||||
scope_list ?: STRV_MAKE("system", "portable"),
|
||||
host_extension_scope);
|
||||
if (!valid) {
|
||||
log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
|
||||
log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -54,21 +57,21 @@ int extension_release_validate(
|
||||
* 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)) {
|
||||
!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()));
|
||||
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' or be '_any'",
|
||||
name, host_os_release_id);
|
||||
log_debug("Extension '%s' does not contain ID in release file 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 */
|
||||
/* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match
|
||||
* '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */
|
||||
if (streq(extension_release_id, "_any")) {
|
||||
log_debug("Extension '%s' matches '_any' OS.", name);
|
||||
return 1;
|
||||
@@ -81,18 +84,18 @@ int extension_release_validate(
|
||||
}
|
||||
|
||||
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
|
||||
if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
|
||||
if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
|
||||
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If the extension has a sysext API level declared, then it must match the host API
|
||||
* level. Otherwise, compare OS version as a whole */
|
||||
extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
|
||||
if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
|
||||
if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
|
||||
log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
|
||||
name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
|
||||
extension_release_level = strv_env_pairs_get(extension_release, extension_level);
|
||||
if (!isempty(host_os_extension_release_level) && !isempty(extension_release_level)) {
|
||||
if (!streq_ptr(host_os_extension_release_level, extension_release_level)) {
|
||||
log_debug("Extension '%s' is for API level '%s', but running on API level '%s'",
|
||||
name, strna(extension_release_level), strna(host_os_extension_release_level));
|
||||
return 0;
|
||||
}
|
||||
} else if (!isempty(host_os_release_version_id)) {
|
||||
@@ -100,7 +103,7 @@ int extension_release_validate(
|
||||
|
||||
extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
|
||||
if (isempty(extension_release_version_id)) {
|
||||
log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
|
||||
log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'",
|
||||
name, strna(host_os_release_version_id));
|
||||
return 0;
|
||||
}
|
||||
@@ -110,7 +113,7 @@ int extension_release_validate(
|
||||
name, strna(extension_release_version_id), strna(host_os_release_version_id));
|
||||
return 0;
|
||||
}
|
||||
} else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
|
||||
} else if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
|
||||
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
|
||||
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
|
||||
return 1;
|
||||
@@ -120,16 +123,21 @@ int extension_release_validate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_env_extension_hierarchies(char ***ret_hierarchies) {
|
||||
int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) {
|
||||
_cleanup_free_ char **l = NULL;
|
||||
int r;
|
||||
|
||||
r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &l);
|
||||
assert(hierarchy_env);
|
||||
r = getenv_path_list(hierarchy_env, &l);
|
||||
if (r == -ENXIO) {
|
||||
/* Default when unset */
|
||||
l = strv_new("/usr", "/opt");
|
||||
if (!l)
|
||||
return -ENOMEM;
|
||||
if (streq(hierarchy_env, "SYSTEMD_CONFEXT_HIERARCHIES"))
|
||||
/* Default for confext when unset */
|
||||
l = strv_new("/etc");
|
||||
else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_HIERARCHIES"))
|
||||
/* Default for sysext when unset */
|
||||
l = strv_new("/usr", "/opt");
|
||||
else
|
||||
return -ENXIO;
|
||||
} else if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "os-util.h"
|
||||
|
||||
/* Given an image name (for logging purposes), a set of os-release values from the host and a key-value pair
|
||||
* vector of extension-release variables, check that the distro and (system extension level or distro
|
||||
* version) match and return 1, and 0 otherwise. */
|
||||
@@ -8,12 +10,13 @@ int extension_release_validate(
|
||||
const char *name,
|
||||
const char *host_os_release_id,
|
||||
const char *host_os_release_version_id,
|
||||
const char *host_os_release_sysext_level,
|
||||
const char *host_sysext_scope,
|
||||
char **extension_release);
|
||||
const char *host_os_extension_release_level,
|
||||
const char *host_extension_scope,
|
||||
char **extension_release,
|
||||
ImageClass image_class);
|
||||
|
||||
/* Parse SYSTEMD_SYSEXT_HIERARCHIES and if not set, return "/usr /opt" */
|
||||
int parse_env_extension_hierarchies(char ***ret_hierarchies);
|
||||
/* Parse hierarchy variables and if not set, return "/usr /opt" for sysext and "/etc" for confext */
|
||||
int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env);
|
||||
|
||||
/* Insist that extension images do not overwrite the underlying OS release file (it's fine if they place one
|
||||
* in /etc/os-release, i.e. where things don't matter, as they aren't merged.) */
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
systemd_sysext_sources = files('sysext.c')
|
||||
|
||||
meson.add_install_script(meson_make_symlink,
|
||||
rootbindir / 'systemd-sysext',
|
||||
rootbindir / 'systemd-confext')
|
||||
|
||||
@@ -39,16 +39,49 @@
|
||||
#include "user-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default */
|
||||
static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
|
||||
static char *arg_root = NULL;
|
||||
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static bool arg_legend = true;
|
||||
static bool arg_force = false;
|
||||
|
||||
/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
|
||||
static ImageClass arg_image_class = IMAGE_SYSEXT;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
|
||||
/* Helper struct for naming simplicity and reusability */
|
||||
static const struct {
|
||||
const char *dot_directory_name;
|
||||
const char *directory_name;
|
||||
const char *short_identifier;
|
||||
const char *short_identifier_plural;
|
||||
const char *level_env;
|
||||
const char *scope_env;
|
||||
const char *name_env;
|
||||
} image_class_info[_IMAGE_CLASS_MAX] = {
|
||||
[IMAGE_SYSEXT] = {
|
||||
.dot_directory_name = ".systemd-sysext",
|
||||
.directory_name = "systemd-sysext",
|
||||
.short_identifier = "sysext",
|
||||
.short_identifier_plural = "extensions",
|
||||
.level_env = "SYSEXT_LEVEL",
|
||||
.scope_env = "SYSEXT_SCOPE",
|
||||
.name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
|
||||
},
|
||||
[IMAGE_CONFEXT] = {
|
||||
.dot_directory_name = ".systemd-confext",
|
||||
.directory_name = "systemd-confext",
|
||||
.short_identifier = "confext",
|
||||
.short_identifier_plural = "confexts",
|
||||
.level_env = "CONFEXT_LEVEL",
|
||||
.scope_env = "CONFEXT_SCOPE",
|
||||
.name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
|
||||
}
|
||||
};
|
||||
|
||||
static int is_our_mount_point(const char *p) {
|
||||
_cleanup_free_ char *buf = NULL, *f = NULL;
|
||||
struct stat st;
|
||||
@@ -70,26 +103,26 @@ static int is_our_mount_point(const char *p) {
|
||||
/* So we know now that it's a mount point. Now let's check if it's one of ours, so that we don't
|
||||
* accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
|
||||
* check by looking into the metadata directory we place in merged mounts: if the file
|
||||
* .systemd-sysext/dev contains the major/minor device pair of the mount we have a good reason to
|
||||
* ../dev contains the major/minor device pair of the mount we have a good reason to
|
||||
* believe this is one of our mounts. This thorough check has the benefit that we aren't easily
|
||||
* confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
|
||||
* them for a live sysext tree. */
|
||||
|
||||
f = path_join(p, ".systemd-sysext/dev");
|
||||
f = path_join(p, image_class_info[arg_image_class].dot_directory_name, "dev");
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
r = read_one_line_file(f, &buf);
|
||||
if (r == -ENOENT) {
|
||||
log_debug("Hierarchy '%s' does not carry a .systemd-sysext/dev file, not a sysext merged tree.", p);
|
||||
log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[arg_image_class].dot_directory_name);
|
||||
return false;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '.systemd-sysext/dev': %m", p);
|
||||
return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[arg_image_class].dot_directory_name);
|
||||
|
||||
r = parse_devnum(buf, &dev);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse device major/minor stored in '.systemd-sysext/dev' file on '%s': %m", p);
|
||||
return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[arg_image_class].dot_directory_name, p);
|
||||
|
||||
if (lstat(p, &st) < 0)
|
||||
return log_error_errno(r, "Failed to stat %s: %m", p);
|
||||
@@ -205,7 +238,7 @@ static int verb_status(int argc, char **argv, void *userdata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f = path_join(*p, ".systemd-sysext/extensions");
|
||||
f = path_join(*p, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
@@ -272,7 +305,7 @@ static int mount_overlayfs(
|
||||
}
|
||||
|
||||
/* Now mount the actual overlayfs */
|
||||
r = mount_nofollow_verbose(LOG_ERR, "sysext", where, "overlay", MS_RDONLY, options);
|
||||
r = mount_nofollow_verbose(LOG_ERR, image_class_info[arg_image_class].short_identifier, where, "overlay", MS_RDONLY, options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -315,7 +348,7 @@ static int merge_hierarchy(
|
||||
/* Let's generate a metadata file that lists all extensions we took into account for this
|
||||
* hierarchy. We include this in the final fs, to make things nicely discoverable and
|
||||
* recognizable. */
|
||||
f = path_join(meta_path, ".systemd-sysext/extensions");
|
||||
f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
@@ -383,12 +416,12 @@ static int merge_hierarchy(
|
||||
/* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
|
||||
* available in the metadata directory. This is useful to detect whether the metadata dir actually
|
||||
* belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
|
||||
* we are looking at a live sysext tree, and not an unpacked tar or so of one. */
|
||||
* we are looking at a live tree, and not an unpacked tar or so of one. */
|
||||
if (stat(overlay_path, &st) < 0)
|
||||
return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
|
||||
|
||||
free(f);
|
||||
f = path_join(meta_path, ".systemd-sysext/dev");
|
||||
f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, "dev");
|
||||
if (!f)
|
||||
return log_oom();
|
||||
|
||||
@@ -410,7 +443,7 @@ static int strverscmp_improvedp(char *const* a, char *const* b) {
|
||||
|
||||
static int merge_subprocess(Hashmap *images, const char *workspace) {
|
||||
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL,
|
||||
*buf = NULL;
|
||||
*host_os_release_confext_level = NULL, *buf = NULL;
|
||||
_cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
|
||||
size_t n_extensions = 0;
|
||||
unsigned n_ignored = 0;
|
||||
@@ -437,11 +470,13 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
|
||||
return r;
|
||||
|
||||
/* Acquire host OS release info, so that we can compare it with the extension's data */
|
||||
char **host_os_release_level = (arg_image_class == IMAGE_CONFEXT) ? &host_os_release_confext_level : &host_os_release_sysext_level;
|
||||
r = parse_os_release(
|
||||
arg_root,
|
||||
"ID", &host_os_release_id,
|
||||
"VERSION_ID", &host_os_release_version_id,
|
||||
"SYSEXT_LEVEL", &host_os_release_sysext_level);
|
||||
image_class_info[arg_image_class].level_env,
|
||||
host_os_release_level);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root));
|
||||
if (isempty(host_os_release_id))
|
||||
@@ -453,7 +488,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
|
||||
HASHMAP_FOREACH(img, images) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
p = path_join(workspace, "extensions", img->name);
|
||||
p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
@@ -574,7 +609,8 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
|
||||
host_os_release_version_id,
|
||||
host_os_release_sysext_level,
|
||||
in_initrd() ? "initrd" : "system",
|
||||
img->extension_release);
|
||||
img->extension_release,
|
||||
arg_image_class);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
@@ -619,7 +655,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
|
||||
|
||||
assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k]));
|
||||
|
||||
p = path_join(workspace, "extensions", img->name);
|
||||
p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
@@ -695,7 +731,7 @@ static int merge(Hashmap *images) {
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
r = safe_fork("(sd-sysext)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
|
||||
r = safe_fork("(sd-merge)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to fork off child: %m");
|
||||
if (r == 0) {
|
||||
@@ -711,7 +747,7 @@ static int merge(Hashmap *images) {
|
||||
_exit(r > 0 ? EXIT_SUCCESS : 123); /* 123 means: didn't find any extensions */
|
||||
}
|
||||
|
||||
r = wait_for_terminate_and_check("(sd-sysext)", pid, WAIT_LOG_ABNORMAL);
|
||||
r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -729,9 +765,9 @@ static int image_discover_and_read_metadata(Hashmap **ret_images) {
|
||||
if (!images)
|
||||
return log_oom();
|
||||
|
||||
r = image_discover(IMAGE_EXTENSION, arg_root, images);
|
||||
r = image_discover(arg_image_class, arg_root, images);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to discover extension images: %m");
|
||||
return log_error_errno(r, "Failed to discover images: %m");
|
||||
|
||||
HASHMAP_FOREACH(img, images) {
|
||||
r = image_read_metadata(img);
|
||||
@@ -832,9 +868,9 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
if (!images)
|
||||
return log_oom();
|
||||
|
||||
r = image_discover(IMAGE_EXTENSION, arg_root, images);
|
||||
r = image_discover(arg_image_class, arg_root, images);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to discover extension images: %m");
|
||||
return log_error_errno(r, "Failed to discover images: %m");
|
||||
|
||||
if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
|
||||
log_info("No OS extensions found.");
|
||||
@@ -870,11 +906,11 @@ static int verb_help(int argc, char **argv, void *userdata) {
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND\n"
|
||||
"\n%5$sMerge extension images into /usr/ and /opt/ hierarchies.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
"\n%5$sMerge extension images into /usr/ and /opt/ hierarchies for\n"
|
||||
" sysext and into the /etc/ hierarchy for confext.%6$s\n"
|
||||
" status Show current merge status (default)\n"
|
||||
" merge Merge extensions into /usr/ and /opt/\n"
|
||||
" unmerge Unmerge extensions from /usr/ and /opt/\n"
|
||||
" merge Merge extensions into relevant hierarchies\n"
|
||||
" unmerge Unmerge extensions from relevant hierarchies\n"
|
||||
" refresh Unmerge/merge extensions again\n"
|
||||
" list List installed extensions\n"
|
||||
" -h --help Show this help\n"
|
||||
@@ -986,6 +1022,7 @@ static int sysext_main(int argc, char *argv[]) {
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
|
||||
|
||||
log_setup();
|
||||
|
||||
@@ -996,9 +1033,9 @@ static int run(int argc, char *argv[]) {
|
||||
/* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
|
||||
* /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
|
||||
* switch. */
|
||||
r = parse_env_extension_hierarchies(&arg_hierarchies);
|
||||
r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES environment variable: %m");
|
||||
return log_error_errno(r, "Failed to parse environment variable: %m");
|
||||
|
||||
return sysext_main(argc, argv);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "mkdir.h"
|
||||
#include "os-util.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "tests.h"
|
||||
#include "tmpfile-util.h"
|
||||
|
||||
TEST(path_is_os_tree) {
|
||||
assert_se(path_is_os_tree("/") > 0);
|
||||
@@ -55,6 +59,48 @@ TEST(parse_os_release) {
|
||||
assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
|
||||
}
|
||||
|
||||
TEST(parse_extension_release) {
|
||||
/* Let's assume that we have a valid extension image */
|
||||
_cleanup_free_ char *id = NULL, *version_id = NULL, *foobar = NULL, *a = NULL, *b = NULL;
|
||||
_cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
|
||||
|
||||
int r = mkdtemp_malloc("/tmp/test-os-util.XXXXXX", &tempdir);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to setup working directory: %m");
|
||||
|
||||
assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test"));
|
||||
assert_se(mkdir_parents(a, 0777) >= 0);
|
||||
|
||||
r = write_string_file(a, "ID=the-id \n VERSION_ID=the-version-id", WRITE_STRING_FILE_CREATE);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to write file: %m");
|
||||
|
||||
assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, false, "test", "ID", &id, "VERSION_ID", &version_id) == 0);
|
||||
log_info("ID: %s VERSION_ID: %s", id, version_id);
|
||||
assert_se(streq(id, "the-id"));
|
||||
assert_se(streq(version_id, "the-version-id"));
|
||||
|
||||
assert_se(b = path_join(tempdir, "/etc/extension-release.d/extension-release.tester"));
|
||||
assert_se(mkdir_parents(b, 0777) >= 0);
|
||||
|
||||
r = write_string_file(b, "ID=\"ignored\" \n ID=\"the-id\" \n VERSION_ID='the-version-id'", WRITE_STRING_FILE_CREATE);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to write file: %m");
|
||||
|
||||
assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, false, "tester", "ID", &id, "VERSION_ID", &version_id) == 0);
|
||||
log_info("ID: %s VERSION_ID: %s", id, version_id);
|
||||
assert_se(streq(id, "the-id"));
|
||||
assert_se(streq(version_id, "the-version-id"));
|
||||
|
||||
assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, false, "tester", "FOOBAR", &foobar) == 0);
|
||||
log_info("FOOBAR: %s", strnull(foobar));
|
||||
assert_se(foobar == NULL);
|
||||
|
||||
assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, false, "test", "FOOBAR", &foobar) == 0);
|
||||
log_info("FOOBAR: %s", strnull(foobar));
|
||||
assert_se(foobar == NULL);
|
||||
}
|
||||
|
||||
TEST(load_os_release_pairs) {
|
||||
_cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
|
||||
assert_se(write_tmpfile(tmpfile,
|
||||
|
||||
@@ -483,6 +483,17 @@ test ! -e "/dev/loop/by-ref/$name"
|
||||
systemd-dissect --detach "${image}.raw"
|
||||
(! systemd-dissect --detach "${image}.raw")
|
||||
|
||||
# check for confext functionality
|
||||
mkdir -p /run/confexts/test/etc/extension-release.d
|
||||
echo "ID=_any" >/run/confexts/test/etc/extension-release.d/extension-release.test
|
||||
echo "ARCHITECTURE=_any" >>/run/confexts/test/etc/extension-release.d/extension-release.test
|
||||
echo "MARKER_CONFEXT_123" >/run/confexts/test/etc/testfile
|
||||
systemd-confext merge
|
||||
grep -q -F "MARKER_CONFEXT_123" /etc/testfile
|
||||
systemd-confext status
|
||||
systemd-confext unmerge
|
||||
rm -rf /run/confexts/
|
||||
|
||||
echo OK >/testok
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -138,6 +138,7 @@ units = [
|
||||
['systemd-reboot.service', ''],
|
||||
['systemd-rfkill.socket', 'ENABLE_RFKILL'],
|
||||
['systemd-sysext.service', 'ENABLE_SYSEXT'],
|
||||
['systemd-confext.service', 'ENABLE_SYSEXT'],
|
||||
['systemd-sysupdate.timer', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-sysupdate-reboot.timer', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-sysusers.service', 'ENABLE_SYSUSERS',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user