1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

Merge pull request #18181 from poettering/sysext

systemd-sysext as a method of merging simple OS extensions into /usr and /opt
This commit is contained in:
Lennart Poettering 2021-01-19 16:02:58 +01:00 committed by GitHub
commit 656e5aa452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1501 additions and 91 deletions

7
TODO
View File

@ -45,6 +45,13 @@ Features:
in a graceful way, so that updated /usr trees automatically propagate into
updated boot loaders on reboot.
* sysext: optionally, if the merged trees allow it use bind mounts instead of
overlayfs
* nspawn: add support for sysext extensions, too. i.e. a new --extension=
switch that takes one or more arguments, and applies the extensions already
during startup.
* add "systemd-analyze debug" + AttachDebugger= in unit files: The former
specifies a command to execute; the latter specifies that an already running
"systemd-analyze debug" instance shall be contacted and execution paused

View File

@ -267,3 +267,15 @@ systemd-firstboot and localectl:
* `SYSTEMD_LIST_NON_UTF8_LOCALES=1` if set non-UTF-8 locales are listed among
the installed ones. By default non-UTF-8 locales are suppressed from the
selection, since we are living in the 21st century.
systemd-sysext:
* `SYSTEMD_SYSEXT_HIERARCHIES` if set to a colon-separated list of absolute
paths this variable may be used to override which hierarchies to manage with
`systemd-sysext`. By default only `/usr/` and `/opt/` are managed. With this
environment variable this list may be changed, in order to add or remove
directories from this list. This should only reference "real" file systems
and directories that only contain "real" file systems as submounts — 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.

View File

@ -317,6 +317,17 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SYSEXT_LEVEL=</varname></term>
<listitem><para>A lower-case string (mostly numeric, no spaces or other characters outside of 09,
az, ".", "_" and "-") identifying the operating system extensions support level, to indicate which
extension images are supported (See:
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>).
Example: <literal>SYSEXT_LEVEL=2</literal> or
<literal>SYSEXT_LEVEL=15.14</literal>.</para></listitem>
</varlistentry>
</variablelist>
<para>If you are reading this file from C code or a shell script

View File

@ -954,6 +954,7 @@ manpages = [
'systemd-suspend-then-hibernate.service'],
''],
['systemd-sysctl.service', '8', ['systemd-sysctl'], ''],
['systemd-sysext', '8', ['systemd-sysext.service'], ''],
['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf',
'5',

239
man/systemd-sysext.xml Normal file
View File

@ -0,0 +1,239 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-sysext"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-sysext</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-sysext</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-sysext</refname>
<refname>systemd-sysext.service</refname>
<refpurpose>Activates System Extension Images</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-sysext</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
</cmdsynopsis>
<para><literallayout><filename>systemd-sysext.service</filename></literallayout></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-sysext</command> activates/deactivates system extension images. System extension
images may dynamically at runtime — extend the <filename>/usr/</filename> and
<filename>/opt/</filename> directory hierarchies with additional files. This is particularly useful on
immutable system images where a <filename>/usr/</filename> and/or <filename>/opt/</filename> hierarchy
residing on a read-only file system shall be extended temporarily at runtime without making any
persistent modifications.</para>
<para>System extension images should contain files and directories similar in fashion to regular
operating system tree. When one or more system extension images are activated, their
<filename>/usr/</filename> and <filename>/opt/</filename> hierarchies are combined via
<literal>overlayfs</literal> with the same hierarchies of the host OS, and the host
<filename>/usr/</filename> and <filename>/opt</filename> overmounted with it ("merging"). When they are
deactivated, the mount point is disassembled — again revealing the unmodified original host version of
the hierarchy ("unmerging"). Merging thus makes the extension's resources suddenly appear below the
<filename>/usr/</filename> and <filename>/opt/</filename> hierarchies as if they were included in the
base OS image itself. Unmerging makes them disappear again, leaving in place only the files that were
shipped with the base OS image itself.</para>
<para>Files and directories contained in the extension images outside of the <filename>/usr/</filename>
and <filename>/opt/</filename> hierarchies are <emphasis>not</emphasis> merged, and hence have no effect
when included in a system extension image. In particular, files in the <filename>/etc/</filename> and
<filename>/var/</filename> included in a system extension image will <emphasis>not</emphasis> appear in
the respective hierarchies after activation.</para>
<para>System extension images are strictly read-only, and the host <filename>/usr/</filename> and
<filename>/opt/</filename> hierarchies become read-only too while they are activated.</para>
<para>System extensions are supposed to be purely additive, i.e. they are supposed to include only files
that do not exist in the underlying basic OS image. However, the underlying mechanism (overlayfs) also
allows removing files, but it is recommended not to make use of this.</para>
<para>System extension images may be provided in the following formats:</para>
<orderedlist>
<listitem><para>Plain directories or btrfs subvolumes containing the OS tree</para></listitem>
<listitem><para>Disk images with a GPT disk label, following the <ulink
url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partition Specification</ulink></para></listitem>
<listitem><para>Disk images lacking a partition table, with a naked Linux file system (e.g. squashfs or ext4)</para></listitem>
</orderedlist>
<para>These image formats are the same ones that
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
supports via it's <option>--directory=</option>/<option>--image=</option> switches and those that the
service manager supports via <option>RootDirectory=</option>/<option>RootImage=</option>. Similar to
them they may optionally carry Verity authentication information.</para>
<para>System extensions are automatically looked for in the directories
<filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename>,
<filename>/var/lib/extensions/</filename>, <filename>/usr/lib/extensions/</filename> and
<filename>/usr/local/lib/extensions/</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/extensions/</filename>. Any directories found in
these search directories are considered directory based extension images, any files with the
<filename>.raw</filename> suffix are considered disk image based extension images.</para>
<para>During boot OS extension images are activated automatically, if the
<filename>systemd-sysext.service</filename> is enabled. Note that this service runs only after the
underlying file systems where system extensions are searched are mounted. This means they are not
suitable for shipping resources that are processed by subsystems running in earliest boot. Specifically,
OS extension images are not suitable for shipping system services or
<citerefentry><refentrytitle>systemd-sysusers</refentrytitle><manvolnum>8</manvolnum></citerefentry>
definitions. See <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services</ulink> for a simple
mechanism for shipping system services in disk images, in a similar fashion to OS extensions. Note the
different isolation on these two mechanisms: while system extension directly extend the underlying OS
image with additional files that appear in a way very similar to as if they were shipped in the OS image
itself and thus imply no security isolation, portable services imply service level sandboxing in one way
or another. The <filename>systemd-sysext.service</filename> service is guaranteed to finish start-up
before <filename>basic.target</filename> is reached; i.e. at the time regular services initialize (those
which do not use <varname>DefaultDependencies=no</varname>), the files and directories system extensions
provide are available in <filename>/usr/</filename> and <filename>/opt/</filename> and may be
accessed.</para>
<para>Note that there is no concept of enabling/disabling installed system extension images: all
installed extension images are automatically activated at boot.</para>
<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
content, as the <filename>os-release</filename> file of the OS, but it describes the resources carried
in the extension image.</para>
</refsect1>
<refsect1>
<title>Uses</title>
<para>The primary use case for system images are immutable environments where debugging and development
tools shall optionally be made available, but not included in the immutable base OS image itself
(e.g. <filename>strace</filename> and <filename>gdb</filename> shall be an optionally installable
addition in order to make debugging/development easier). System extension images should not be
misunderstood as a generic software packaging framework, as no dependency scheme is available: system
extensions should carry all files they need themselves, except for those already shipped in the
underlying host system image. Typically, system extension images are built at the same time as the base
OS image — within the same build system.</para>
<para>Another use case for the system extension concept is temporarily overriding OS supplied resources
with newer ones, for example to install a locally compiled development version of some low-level
component over the immutable OS image without doing a full OS rebuild or modifying the nominally
immutable image. (e.g. "install" a locally built package with <command>DESTDIR=/var/lib/extensions/mytest
make install &amp;&amp; systemd-sysext --refresh</command>, making it available in
<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>
<refsect1>
<title>Commands</title>
<para>The following command switches are understood:</para>
<variablelist>
<varlistentry>
<term><option>--merge</option></term>
<term><option>-m</option></term>
<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>
</varlistentry>
<varlistentry>
<term><option>--unmerge</option></term>
<term><option>-u</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>
prior.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--refresh</option></term>
<term><option>-R</option></term>
<listitem><para>A combination of <option>--unmerge</option> and <option>--merge</option>: if already
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>
</varlistentry>
<varlistentry>
<term><option>--list</option></term>
<term><option>-l</option></term>
<listitem><para>A brief list of installed extension images is shown.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
<para>When invoked without any command switches, the current merge status is shown, separately for both
<filename>/usr/</filename> and <filename>/opt/</filename>.</para>
</refsect1>
<refsect1>
<title>Options</title>
<variablelist>
<varlistentry>
<term><option>--root=</option></term>
<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>
</varlistentry>
<varlistentry>
<term><option>--json=</option></term>
<listitem><para>Generate JSON output, instead of human readable tabular output. Takes one of
<literal>short</literal>, <literal>pretty</literal> or <literal>off</literal> in order to control the
output style, or explicitly disabling JSON output.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -1502,6 +1502,7 @@ foreach term : ['analyze',
'nss-myhostname',
'nss-systemd',
'portabled',
'sysext',
'pstore',
'quotacheck',
'randomseed',
@ -1745,6 +1746,7 @@ subdir('src/portable')
subdir('src/pstore')
subdir('src/resolve')
subdir('src/shutdown')
subdir('src/sysext')
subdir('src/systemctl')
subdir('src/timedate')
subdir('src/timesync')
@ -2202,6 +2204,17 @@ if conf.get('ENABLE_PORTABLED') == 1
install_dir : rootbindir)
endif
if conf.get('ENABLE_SYSEXT') == 1
public_programs += executable(
'systemd-sysext',
systemd_sysext_sources,
include_directories : includes,
link_with : [libshared],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
endif
if conf.get('ENABLE_USERDB') == 1
executable(
'systemd-userwork',
@ -2390,8 +2403,7 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
libopenssl,
libp11kit],
install_rpath : rootlibexecdir,
install : true,
install_dir : bindir)
install : true)
endif
if conf.get('HAVE_SYSV_COMPAT') == 1
@ -3735,6 +3747,7 @@ foreach tuple : [
['logind'],
['machined'],
['portabled'],
['sysext'],
['userdb'],
['homed'],
['importd'],

View File

@ -111,6 +111,8 @@ option('machined', type : 'boolean',
description : 'install the systemd-machined stack')
option('portabled', type : 'boolean',
description : 'install the systemd-portabled stack')
option('sysext', type : 'boolean',
description : 'install the systemd-sysext stack')
option('userdb', type : 'boolean',
description : 'install the systemd-userdbd stack')
option('homed', type : 'combo', choices : ['auto', 'true', 'false'],

View File

@ -66,7 +66,7 @@ static int export_tar(int argc, char *argv[], void *userdata) {
int r, fd;
if (hostname_is_valid(argv[1], 0)) {
r = image_find(IMAGE_MACHINE, argv[1], &image);
r = image_find(IMAGE_MACHINE, argv[1], NULL, &image);
if (r == -ENOENT)
return log_error_errno(r, "Machine image %s not found.", argv[1]);
if (r < 0)
@ -142,7 +142,7 @@ static int export_raw(int argc, char *argv[], void *userdata) {
int r, fd;
if (hostname_is_valid(argv[1], 0)) {
r = image_find(IMAGE_MACHINE, argv[1], &image);
r = image_find(IMAGE_MACHINE, argv[1], NULL, &image);
if (r == -ENOENT)
return log_error_errno(r, "Machine image %s not found.", argv[1]);
if (r < 0)

View File

@ -132,7 +132,7 @@ static int import_fs(int argc, char *argv[], void *userdata) {
local);
if (!arg_force) {
r = image_find(IMAGE_MACHINE, local, NULL);
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);

View File

@ -70,7 +70,7 @@ static int import_tar(int argc, char *argv[], void *userdata) {
local);
if (!arg_force) {
r = image_find(IMAGE_MACHINE, local, NULL);
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
@ -165,7 +165,7 @@ static int import_raw(int argc, char *argv[], void *userdata) {
local);
if (!arg_force) {
r = image_find(IMAGE_MACHINE, local, NULL);
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);

View File

@ -78,7 +78,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
local);
if (!arg_force) {
r = image_find(IMAGE_MACHINE, local, NULL);
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
@ -164,7 +164,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
local);
if (!arg_force) {
r = image_find(IMAGE_MACHINE, local, NULL);
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);

View File

@ -408,7 +408,7 @@ static int image_object_find(sd_bus *bus, const char *path, const char *interfac
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, e, &image);
r = image_find(IMAGE_MACHINE, e, NULL, &image);
if (r == -ENOENT)
return 0;
if (r < 0)
@ -452,7 +452,7 @@ static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata,
if (!images)
return -ENOMEM;
r = image_discover(IMAGE_MACHINE, images);
r = image_discover(IMAGE_MACHINE, NULL, images);
if (r < 0)
return r;

View File

@ -124,7 +124,7 @@ static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_erro
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, name, NULL);
r = image_find(IMAGE_MACHINE, name, NULL, NULL);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0)
@ -480,7 +480,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
if (!images)
return -ENOMEM;
r = image_discover(IMAGE_MACHINE, images);
r = image_discover(IMAGE_MACHINE, NULL, images);
if (r < 0)
return r;
@ -562,7 +562,7 @@ static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(IMAGE_MACHINE, name, &i);
r = image_find(IMAGE_MACHINE, name, NULL, &i);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0)
@ -755,7 +755,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
goto child_fail;
}
r = image_discover(IMAGE_MACHINE, images);
r = image_discover(IMAGE_MACHINE, NULL, images);
if (r < 0)
goto child_fail;

View File

@ -2940,7 +2940,7 @@ static int determine_names(void) {
if (arg_machine) {
_cleanup_(image_unrefp) Image *i = NULL;
r = image_find(IMAGE_MACHINE, arg_machine, &i);
r = image_find(IMAGE_MACHINE, arg_machine, NULL, &i);
if (r == -ENOENT)
return log_error_errno(r, "No image for machine '%s'.", arg_machine);
if (r < 0)

View File

@ -495,7 +495,7 @@ int portable_extract(
assert(name_or_path);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, &image);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0)
return r;
@ -953,7 +953,7 @@ static int install_image_symlink(
/* If the image is outside of the image search also link it into it, so that it can be found with short image
* names and is listed among the images. */
if (image_in_search_path(IMAGE_PORTABLE, image_path))
if (image_in_search_path(IMAGE_PORTABLE, NULL, image_path))
return 0;
r = image_symlink(image_path, flags, &sl);
@ -987,7 +987,7 @@ int portable_attach(
assert(name_or_path);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, &image);
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
if (r < 0)
return r;
@ -1193,7 +1193,7 @@ int portable_detach(
return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name);
if (path_is_absolute(marker) &&
!image_in_search_path(IMAGE_PORTABLE, marker)) {
!image_in_search_path(IMAGE_PORTABLE, NULL, marker)) {
r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(marker));
if (r < 0)

View File

@ -606,7 +606,7 @@ int bus_image_acquire(
if (image_name_is_valid(name_or_path)) {
/* If it's a short name, let's search for it */
r = image_find(IMAGE_PORTABLE, name_or_path, &loaded);
r = image_find(IMAGE_PORTABLE, name_or_path, NULL, &loaded);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);

View File

@ -92,7 +92,7 @@ int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *erro
/* A wrapper around image_discover() (for finding images in search path) and portable_discover_attached() (for
* finding attached images). */
r = image_discover(IMAGE_PORTABLE, images);
r = image_discover(IMAGE_PORTABLE, NULL, images);
if (r < 0)
return r;

View File

@ -42,18 +42,24 @@
#include "xattr-util.h"
static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
"/run/machines\0" /* and here too */
"/var/lib/machines\0" /* the main place for images */
"/var/lib/container\0" /* legacy */
"/usr/local/lib/machines\0"
"/usr/lib/machines\0",
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
"/run/machines\0" /* and here too */
"/var/lib/machines\0" /* the main place for images */
"/var/lib/container\0" /* legacy */
"/usr/local/lib/machines\0"
"/usr/lib/machines\0",
[IMAGE_PORTABLE] = "/etc/portables\0" /* only place symlinks here */
"/run/portables\0" /* and here too */
"/var/lib/portables\0" /* the main place for images */
"/usr/local/lib/portables\0"
"/usr/lib/portables\0",
[IMAGE_PORTABLE] = "/etc/portables\0" /* only place symlinks here */
"/run/portables\0" /* and here too */
"/var/lib/portables\0" /* the main place for images */
"/usr/local/lib/portables\0"
"/usr/lib/portables\0",
[IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
"/run/extensions\0" /* and here too */
"/var/lib/extensions\0" /* the main place for images */
"/usr/local/lib/extensions\0"
"/usr/lib/extensions\0",
};
static Image *image_free(Image *i) {
@ -415,7 +421,11 @@ static int image_make(
return -EMEDIUMTYPE;
}
int image_find(ImageClass class, const char *name, Image **ret) {
int image_find(ImageClass class,
const char *name,
const char *root,
Image **ret) {
const char *path;
int r;
@ -428,20 +438,22 @@ int image_find(ImageClass class, const char *name, Image **ret) {
return -ENOENT;
NULSTR_FOREACH(path, image_search_path[class]) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
struct stat st;
int flags;
d = opendir(path);
if (!d) {
if (errno == ENOENT)
continue;
r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
if (r == -ENOENT)
continue;
if (r < 0)
return r;
return -errno;
}
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people to
* symlink block devices into the search path */
if (fstatat(dirfd(d), name, &st, 0) < 0) {
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people
* to symlink block devices into the search path. (For now, we disable that when operating
* relative to some root directory.) */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
if (fstatat(dirfd(d), name, &st, flags) < 0) {
_cleanup_free_ char *raw = NULL;
if (errno != ENOENT)
@ -451,8 +463,7 @@ int image_find(ImageClass class, const char *name, Image **ret) {
if (!raw)
return -ENOMEM;
if (fstatat(dirfd(d), raw, &st, 0) < 0) {
if (fstatat(dirfd(d), raw, &st, flags) < 0) {
if (errno == ENOENT)
continue;
@ -462,13 +473,13 @@ int image_find(ImageClass class, const char *name, Image **ret) {
if (!S_ISREG(st.st_mode))
continue;
r = image_make(name, dirfd(d), path, raw, &st, ret);
r = image_make(name, dirfd(d), resolved, raw, &st, ret);
} else {
if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
continue;
r = image_make(name, dirfd(d), path, name, &st, ret);
r = image_make(name, dirfd(d), resolved, name, &st, ret);
}
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
@ -482,7 +493,7 @@ int image_find(ImageClass class, const char *name, Image **ret) {
}
if (class == IMAGE_MACHINE && streq(name, ".host")) {
r = image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
r = image_make(".host", AT_FDCWD, NULL, empty_to_root(root), NULL, ret);
if (r < 0)
return r;
@ -507,14 +518,18 @@ int image_from_path(const char *path, Image **ret) {
return image_make(NULL, AT_FDCWD, NULL, path, NULL, ret);
}
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret) {
int image_find_harder(ImageClass class, const char *name_or_path, const char *root, Image **ret) {
if (image_name_is_valid(name_or_path))
return image_find(class, name_or_path, ret);
return image_find(class, name_or_path, root, ret);
return image_from_path(name_or_path, ret);
}
int image_discover(ImageClass class, Hashmap *h) {
int image_discover(
ImageClass class,
const char *root,
Hashmap *h) {
const char *path;
int r;
@ -523,29 +538,30 @@ int image_discover(ImageClass class, Hashmap *h) {
assert(h);
NULSTR_FOREACH(path, image_search_path[class]) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
d = opendir(path);
if (!d) {
if (errno == ENOENT)
continue;
return -errno;
}
r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
if (r == -ENOENT)
continue;
if (r < 0)
return r;
FOREACH_DIRENT_ALL(de, d, return -errno) {
_cleanup_(image_unrefp) Image *image = NULL;
_cleanup_free_ char *truncated = NULL;
const char *pretty;
struct stat st;
int flags;
if (dot_or_dot_dot(de->d_name))
continue;
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people
* to symlink block devices into the search path */
if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
/* As mentioned above, we follow symlinks on this fstatat(), because we want to
* permit people to symlink block devices into the search path. */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) {
if (errno == ENOENT)
continue;
@ -575,7 +591,7 @@ int image_discover(ImageClass class, Hashmap *h) {
if (hashmap_contains(h, pretty))
continue;
r = image_make(pretty, dirfd(d), path, de->d_name, &st, &image);
r = image_make(pretty, dirfd(d), resolved, de->d_name, &st, &image);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
@ -594,7 +610,7 @@ int image_discover(ImageClass class, Hashmap *h) {
if (class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
_cleanup_(image_unrefp) Image *image = NULL;
r = image_make(".host", AT_FDCWD, NULL, "/", NULL, &image);
r = image_make(".host", AT_FDCWD, NULL, empty_to_root("/"), NULL, &image);
if (r < 0)
return r;
@ -737,7 +753,7 @@ int image_rename(Image *i, const char *new_name) {
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, new_name, NULL);
r = image_find(IMAGE_MACHINE, new_name, NULL, NULL);
if (r >= 0)
return -EEXIST;
if (r != -ENOENT)
@ -850,7 +866,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
if (r < 0)
return r;
r = image_find(IMAGE_MACHINE, new_name, NULL);
r = image_find(IMAGE_MACHINE, new_name, NULL, NULL);
if (r >= 0)
return -EEXIST;
if (r != -ENOENT)
@ -1242,16 +1258,27 @@ bool image_name_is_valid(const char *s) {
return true;
}
bool image_in_search_path(ImageClass class, const char *image) {
bool image_in_search_path(
ImageClass class,
const char *root,
const char *image) {
const char *path;
assert(image);
NULSTR_FOREACH(path, image_search_path[class]) {
const char *p;
const char *p, *q;
size_t k;
p = path_startswith(image, path);
if (!empty_or_root(root)) {
q = path_startswith(path, root);
if (!q)
continue;
} else
q = path;
p = path_startswith(q, path);
if (!p)
continue;

View File

@ -16,6 +16,7 @@
typedef enum ImageClass {
IMAGE_MACHINE,
IMAGE_PORTABLE,
IMAGE_EXTENSION,
_IMAGE_CLASS_MAX,
_IMAGE_CLASS_INVALID = -1
} ImageClass;
@ -61,10 +62,10 @@ Image *image_ref(Image *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref);
int image_find(ImageClass class, const char *name, Image **ret);
int image_find(ImageClass class, const char *root, const char *name, Image **ret);
int image_from_path(const char *path, Image **ret);
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret);
int image_discover(ImageClass class, Hashmap *map);
int image_find_harder(ImageClass class, const char *root, const char *name_or_path, Image **ret);
int image_discover(ImageClass class, const char *root, Hashmap *map);
int image_remove(Image *i);
int image_rename(Image *i, const char *new_name);
@ -83,7 +84,7 @@ int image_set_limit(Image *i, uint64_t referenced_max);
int image_read_metadata(Image *i);
bool image_in_search_path(ImageClass class, const char *image);
bool image_in_search_path(ImageClass class, const char *root, const char *image);
static inline bool IMAGE_IS_HIDDEN(const struct Image *i) {
assert(i);

View File

@ -6,6 +6,7 @@
#include "fileio.h"
#include "fs-util.h"
#include "macro.h"
#include "machine-image.h"
#include "os-util.h"
#include "string-util.h"
#include "strv.h"
@ -31,17 +32,31 @@ int path_is_os_tree(const char *path) {
return 1;
}
int open_os_release(const char *root, char **ret_path, int *ret_fd) {
int open_extension_release(const char *root, const char *extension, char **ret_path, int *ret_fd) {
_cleanup_free_ char *q = NULL;
const char *p;
int r, fd;
FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
r = chase_symlinks(p, root, CHASE_PREFIX_ROOT,
ret_path ? &q : NULL,
ret_fd ? &fd : NULL);
if (r != -ENOENT)
break;
if (extension) {
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);
r = chase_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT,
ret_path ? &q : NULL,
ret_fd ? &fd : NULL);
} else {
const char *p;
FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
r = chase_symlinks(p, root, CHASE_PREFIX_ROOT,
ret_path ? &q : NULL,
ret_fd ? &fd : NULL);
if (r != -ENOENT)
break;
}
}
if (r < 0)
return r;
@ -64,16 +79,16 @@ int open_os_release(const char *root, char **ret_path, int *ret_fd) {
return 0;
}
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
int fopen_extension_release(const char *root, const char *extension, char **ret_path, FILE **ret_file) {
_cleanup_free_ char *p = NULL;
_cleanup_close_ int fd = -1;
FILE *f;
int r;
if (!ret_file)
return open_os_release(root, ret_path, NULL);
return open_extension_release(root, extension, ret_path, NULL);
r = open_os_release(root, ret_path ? &p : NULL, &fd);
r = open_extension_release(root, extension, ret_path ? &p : NULL, &fd);
if (r < 0)
return r;
@ -89,18 +104,35 @@ int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
return 0;
}
int parse_os_release(const char *root, ...) {
static int parse_release_internal(const char *root, const char *extension, va_list ap) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
va_list ap;
int r;
r = fopen_os_release(root, &p, &f);
r = fopen_extension_release(root, extension, &p, &f);
if (r < 0)
return r;
return parse_env_filev(f, p, ap);
}
int parse_extension_release(const char *root, const char *extension, ...) {
va_list ap;
int r;
va_start(ap, extension);
r = parse_release_internal(root, extension, ap);
va_end(ap);
return r;
}
int parse_os_release(const char *root, ...) {
va_list ap;
int r;
va_start(ap, root);
r = parse_env_filev(f, p, ap);
r = parse_release_internal(root, NULL, ap);
va_end(ap);
return r;

View File

@ -5,9 +5,19 @@
int path_is_os_tree(const char *path);
int open_os_release(const char *root, char **ret_path, int *ret_fd);
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
/* 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. */
int open_extension_release(const char *root, const char *extension, 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, ret_path, ret_fd);
}
int fopen_extension_release(const char *root, const char *extension, 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, ret_path, ret_file);
}
int parse_extension_release(const char *root, const char *extension, ...) _sentinel_;
int parse_os_release(const char *root, ...) _sentinel_;
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);

5
src/sysext/meson.build Normal file
View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
systemd_sysext_sources = files('''
sysext.c
'''.split())

1018
src/sysext/sysext.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -933,7 +933,7 @@ install_execs() {
# some {rc,halt}.local scripts and programs are okay to not exist, the rest should
# also, plymouth is pulled in by rescue.service, but even there the exit code
# is ignored; as it's not present on some distros, don't fail if it doesn't exist
dinfo "Attempting to install $i"
dinfo "Attempting to install $i (based on unit file reference)"
inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "${i##*/}" == "plymouth" ]
done
)

View File

@ -211,6 +211,7 @@ in_units = [
['systemd-oomd.service', 'ENABLE_OOMD'],
['systemd-portabled.service', 'ENABLE_PORTABLED',
'dbus-org.freedesktop.portable1.service'],
['systemd-sysext.service', 'ENABLE_SYSEXT'],
['systemd-userdbd.service', 'ENABLE_USERDB'],
['systemd-homed.service', 'ENABLE_HOMED'],
['systemd-quotacheck.service', 'ENABLE_QUOTACHECK'],

View File

@ -0,0 +1,31 @@
# 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.
[Unit]
Description=Merge System Extension Images into /usr/ and /opt/
Documentation=man:systemd-sysext.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=local-fs.target
Before=sysinit.target shutdown.target systemd-tmpfiles.service
ConditionCapability=CAP_SYS_ADMIN
ConditionDirectoryNotEmpty=|/etc/extensions
ConditionDirectoryNotEmpty=|/run/extensions
ConditionDirectoryNotEmpty=|/var/lib/extensions
ConditionDirectoryNotEmpty=|/usr/local/lib/extensions
ConditionDirectoryNotEmpty=|/usr/lib/extensions
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=@rootlibexecdir@/systemd-sysext --merge
ExecStop=@rootlibexecdir@/systemd-sysext --unmerge
[Install]
WantedBy=sysinit.target