1
0
mirror of https://github.com/systemd/systemd synced 2025-09-29 00:34:45 +02:00

Compare commits

..

32 Commits

Author SHA1 Message Date
Lennart Poettering
a631cbfae3 execute: for processes where creds logic is turned off, don't pass creds path to namespace logic
Otherwise, the namespace logic will try to mount a dir that doesn't
actually exist.

Fixes: #18116
2021-01-19 20:03:42 +01:00
Lennart Poettering
f6c9a7ab93
Merge pull request #18307 from poettering/import-verity-download
importd: when downloading raw image, also download .roothash.p7s and .verity along with it
2021-01-19 20:02:34 +01:00
Lennart Poettering
ac71ece3c6 import: refactor how we do gpg validation
Let's split out the actual gpg logic into a helper function, so that we
can add alternative validations later on.
2021-01-19 18:29:59 +01:00
Lennart Poettering
133b34f69a import: optionally pull .verity + .roothash.p7s data when downloading
We already had support for downlading a .nspawn and a .roothash file,
let's make the set complete, and also download .verity + roothash.p7s if
it exists, as nspawn consumes that.

Since there are now four kinds of additional resources to acquire, let's
introduce a PullFlags flags value for this instead of separate 'bool'
variables, it's just too many to always pass those around on the
function parameter list.
2021-01-19 18:29:59 +01:00
Lennart Poettering
6792cbbcf8 import: ignore non-successful HTTP codes for collecing image metadata
Previously we'd collect the data from redirects too, which wasn't
particularly terrible, since these typically don't carry the data we
were interested in, but it's still incorrect to do so.
2021-01-19 18:29:59 +01:00
Lennart Poettering
8dc0291c0d import: turn on HTTP logging in debug mode 2021-01-19 18:29:59 +01:00
Lennart Poettering
273cb07d1b import: small memory management simplification 2021-01-19 18:29:59 +01:00
Lennart Poettering
8bc3f0b89f import: reset PullJob properly
Properly reset all fields that have to do with the current GET job when
we restart things. Previously we freed/reset only some stuff, leaking
some memory even.
2021-01-19 18:29:59 +01:00
Lennart Poettering
f14717a7e2 import: rework how verification works
Previously the PullJob object took internal care of rerequested the
SHA256SUMS file, if requesting <image>.sha256 didn't work. This was a
weird a non-abstraction only used when actually getting the checksum
files.

Let's move this out of the PullJob, so that it is generic again, and
does roughly the same stuff for all resources it is used for: let's
define a generic .on_not_found() handler that can be set on a PullJob
object, and is called whenever with see HTTP 404, and may be used to
provide a new URL to try if the first didn't work.

This is also preparation for later work to support PKCS#7 signatures
instead of gpg signatures, where a similar logic is needed, and we thus
should have a generic infrastructure place.

This gets rid of the VerificationStyle field in the PullJob object:
instead of storing this non-generic field we just derive the same
information from the URL itself, which is safe, since we generated it
ourselves earlier.
2021-01-19 18:29:59 +01:00
Lennart Poettering
c20307fd34 import: use TAKE_PTR() where available 2021-01-19 18:29:59 +01:00
Lennart Poettering
c6cb8daf72 import: make scope of variable smaller 2021-01-19 18:29:59 +01:00
Lennart Poettering
7d41de2e94 import: comment indent fix 2021-01-19 18:29:59 +01:00
Lennart Poettering
63ec26a4bf fs-util/rm-rf: improve remove+free destructors to take and return NULL
Let#s make these helpers useful even without _cleanup_ logic, to destory
arbitary fields: make them OK wiht a NULL pointer as input, and always
return one as output.
2021-01-19 18:29:59 +01:00
Susant Sahani
c038ce4606 network: add support to RoutingPolicyRule lookup table name 2021-01-19 16:37:46 +00:00
Lennart Poettering
656e5aa452
Merge pull request #18181 from poettering/sysext
systemd-sysext as a method of merging simple OS extensions into /usr and /opt
2021-01-19 16:02:58 +01:00
Lennart Poettering
a2804e3cd8
Merge pull request #18129 from keszybz/envvars
Allow control characters in environment variable values
2021-01-19 16:02:27 +01:00
Florian Westphal
bf108eb942 homed: fix build without p11kit
homectl-pkcs11.c: In function 'identity_add_pkcs11_key_data':
homectl-pkcs11.c:155:13: error: implicit declaration of function 'pkcs11_acquire_certificate' [-Werror=implicit-function-declaration]

Restores the P11KIT compile-time test that was removed in 2289a78473282902db1108168df6414ae7d91b2f
("homed: move pkcs11 LUKS glue into shared code").
2021-01-19 14:38:39 +01:00
Zbigniew Jędrzejewski-Szmek
e9155cd077 systemctl: warn when importing environment variables with control characters
I don't think it is useful to warn about about environemnt variables where the user
explicitly configured some value. If they went through the effort of escaping the cc
to include it in the setting (e.g. Environment="VAR=\efoo"), and we pass this through,
there isn't anything to warn about. This also applies to 'systemctl set-environment',
where the variable name and value are passed as arguments.

The only case where the warning *might* be useful is where the user might be
surprised by the value. This occurs when importing variables from the inherited
environment, i.e. in 'systemctl import-environment'. In not convinced that this is
useful, since the user better control their shell environment anyway.

$ systemctl import-environment
Calling import-environment without a list of variable names is deprecated.
Environment variable $LESS_TERMCAP_mb contains control characters, importing anyway.
Environment variable $LESS_TERMCAP_md contains control characters, importing anyway.
Environment variable $LESS_TERMCAP_me contains control characters, importing anyway.
Environment variable $LESS_TERMCAP_se contains control characters, importing anyway.
Environment variable $LESS_TERMCAP_so contains control characters, importing anyway.
Environment variable $LESS_TERMCAP_ue contains control characters, importing anyway.
Environment variable $LESS_TERMCAP_us contains control characters, importing anyway.
Environment variable $ZZZ contains control characters, importing anyway.
2021-01-19 14:24:51 +01:00
Zbigniew Jędrzejewski-Szmek
30927a2484 Allow control characters in environment variable values
So far, we would allow certain control characters (NL since
b4346b9a77bc6129dd3e, TAB since 6294aa76d818e831de45), but not others. Having
other control characters in environment variable *value* is expected and widely
used, for various prompts like $LESS, $LESS_TERMCAP_*, and other similar
variables. The typical environment exported by bash already contains a dozen or
so such variables, so programs need to handle them.

We handle then correctly too, for example in 'systemctl show-environment',
since 804ee07c1370d49aa9a. But we would still disallow setting such variables
by the user, in unit file Environment= and in set-environment/import-environment
operations. This is unexpected and confusing and doesn't help with anything
because such variables are present in the environment through other means.

When printing such variables, 'show-environment' escapes all special
characters, so variables with control characters are plainly visible.
In other uses, e.g. 'cat -v' can be used in similar fashion. This would already
need to be done to suppress color codes starting with \[.

Note that we still forbid invalid utf-8 with this patch. (Control characters
are valid, since they are valid 7-bit ascii.) I'm not sure if we should do
that, but since people haven't been actually asking for invalid utf-8, and only
for control characters, and invalid utf-8 causes other issues, I think it's OK
to leave this unchanged.

Fixes #4446, https://gitlab.gnome.org/GNOME/gnome-session/-/issues/45.
2021-01-19 14:18:34 +01:00
Zbigniew Jędrzejewski-Szmek
c4899ea427 systemctl: print a warning when trying to import a nonexistent variable
I was quite confused what is happening:
$ XXX=xxx
$ systemctl --user import-environment XXX
$ systemctl --user show-environment | grep XXX
(nothing)

Obviously, 'export XXX' was missing. Without any indication why the
export is not happening, this can be hard to figure out.

Another option would be to error out. But so far we didn't, and doing
that could break some script which optimistically tries to export some
variables, if present.
2021-01-19 14:18:33 +01:00
Luca Boccassi
71ad75f306 sysext: install in /usr/lib/systemd/ for now
This is a brand new binary, and the CI packaging doesn't pick it up,
causing the upstream testrun to fail (sysext is pulled in by the unit).
2021-01-19 13:41:42 +01:00
Lennart Poettering
a1fd722b5d meson: bindir is the default install_dir, no need to mention it 2021-01-19 13:41:42 +01:00
Lennart Poettering
b5e0c17654 test: improve a log message while building test images 2021-01-19 13:41:42 +01:00
Lennart Poettering
dfbbb4f7b0 update TODO 2021-01-19 13:41:42 +01:00
Luca Boccassi
36b95d0440 man: mention SYSEXT_LEVEL in os-release(5) 2021-01-19 13:41:42 +01:00
Luca Boccassi
60bb6caaae sysext: use parse_extension_release and reject extension if not found 2021-01-19 13:41:42 +01:00
Luca Boccassi
6ddd051193 os-release: add support for /usr/lib/extension-release.d/
Add helpers to look for extension-release.$NAME files in
/usr/lib/extension-release.d/ following the same pattern as os-release.
2021-01-19 13:41:42 +01:00
Luca Boccassi
44bb7b0956 sysext: add verity boilerplate 2021-01-19 13:41:42 +01:00
Lennart Poettering
d577d4a432 machine-image: properly support searching for images below some --root= path
systemd-sysext supports --root= for everything but the image discovery.
Fix that.
2021-01-19 13:41:42 +01:00
Lennart Poettering
7a87fb6119 man: add man page for systemd-sysext 2021-01-19 13:41:42 +01:00
Lennart Poettering
205e5bcc1c units: add systemd-sysext.service unit for auto-activating extensions at boot
We'll leave this as opt-in (i.e. a unit that must be enabled
explicitly), since this is supposed to be a debug/developer feature
primarily, and thus no be around in regular production systems.
2021-01-19 13:41:42 +01:00
Lennart Poettering
9bca4ae4cd sysext: new tool for managing "system extensions" for /usr/ + /opt/ 2021-01-19 13:41:42 +01:00
51 changed files with 2221 additions and 390 deletions

7
TODO
View File

@ -45,6 +45,13 @@ Features:
in a graceful way, so that updated /usr trees automatically propagate into in a graceful way, so that updated /usr trees automatically propagate into
updated boot loaders on reboot. 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 * add "systemd-analyze debug" + AttachDebugger= in unit files: The former
specifies a command to execute; the latter specifies that an already running specifies a command to execute; the latter specifies that an already running
"systemd-analyze debug" instance shall be contacted and execution paused "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 * `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 the installed ones. By default non-UTF-8 locales are suppressed from the
selection, since we are living in the 21st century. 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

@ -70,6 +70,14 @@
is false. Defaults to yes.</para></listitem> is false. Defaults to yes.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>RouteTable=</varname></term>
<listitem><para>Specifies the route table name. Takes a route name and table number separated with a colon.
(<literal><replaceable>name</replaceable>:<replaceable>integer</replaceable></literal>. The route table number
must be an integer in the range 1..4294967295. This setting can be specified multiple times. If an empty string
is specified, then all options specified earlier are cleared. Defaults to unset.</para></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -317,6 +317,17 @@
</para></listitem> </para></listitem>
</varlistentry> </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> </variablelist>
<para>If you are reading this file from C code or a shell script <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-suspend-then-hibernate.service'],
''], ''],
['systemd-sysctl.service', '8', ['systemd-sysctl'], ''], ['systemd-sysctl.service', '8', ['systemd-sysctl'], ''],
['systemd-sysext', '8', ['systemd-sysext.service'], ''],
['systemd-system-update-generator', '8', [], ''], ['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf', ['systemd-system.conf',
'5', '5',

View File

@ -1080,8 +1080,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<para><command>systemd</command> supports an environment block that is passed to processes the manager <para><command>systemd</command> supports an environment block that is passed to processes the manager
spawns. The names of the variables can contain ASCII letters, digits, and the underscore spawns. The names of the variables can contain ASCII letters, digits, and the underscore
character. Variable names cannot be empty or start with a digit. In variable values, most characters character. Variable names cannot be empty or start with a digit. In variable values, most characters
are allowed, but non-printable characters are currently rejected. The total length of the environment are allowed, but the whole sequence must be valid UTF-8. (Note that control characters like newline
block is limited to <constant>_SC_ARG_MAX</constant> value defined by (<constant>NL</constant>), tab (<constant>TAB</constant>), or the escape character
(<constant>ESC</constant>), <emphasis>are</emphasis> valid ASCII and thus valid UTF-8). The total
length of the environment block is limited to <constant>_SC_ARG_MAX</constant> value defined by
<citerefentry project='man-pages'><refentrytitle>sysconf</refentrytitle><manvolnum>3</manvolnum></citerefentry>. <citerefentry project='man-pages'><refentrytitle>sysconf</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
</para> </para>

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

@ -1200,8 +1200,9 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
<varlistentry> <varlistentry>
<term><varname>Table=</varname></term> <term><varname>Table=</varname></term>
<listitem> <listitem>
<para>Specifies the routing table identifier to lookup if the rule selector matches. Takes <para>Specifies the routing table identifier to lookup if the rule selector matches. Takes one of predefined names
one of <literal>default</literal>, <literal>main</literal>, and <literal>local</literal>, <literal>default</literal>, <literal>main</literal>, and <literal>local</literal>, and names defined in <varname>RouteTable=</varname>
in <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
or a number between 1 and 4294967295. Defaults to <literal>main</literal>.</para> or a number between 1 and 4294967295. Defaults to <literal>main</literal>.</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -1409,11 +1410,11 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
<varlistentry> <varlistentry>
<term><varname>Table=</varname></term> <term><varname>Table=</varname></term>
<listitem> <listitem>
<para>The table identifier for the route. Takes <literal>default</literal>, <para>The table identifier for the route. Takes one of predefined names <literal>default</literal>, <literal>main</literal>,
<literal>main</literal>, <literal>local</literal> or a number between 1 and 4294967295. and <literal>local</literal>, and names defined in <varname>RouteTable=</varname> in <citerefentry><refentrytitle>networkd.conf</refentrytitle>
The table can be retrieved using <command>ip route show table <replaceable>num</replaceable></command>. <manvolnum>5</manvolnum></citerefentry>, or a number between 1 and 4294967295. The table can be retrieved using
If unset and <varname>Type=</varname> is <literal>local</literal>, <literal>broadcast</literal>, <command>ip route show table <replaceable>num</replaceable></command>. If unset and <varname>Type=</varname> is <literal>local</literal>,
<literal>anycast</literal>, or <literal>nat</literal>, then <literal>local</literal> is used. <literal>broadcast</literal>, <literal>anycast</literal>, or <literal>nat</literal>, then <literal>local</literal> is used.
In other cases, defaults to <literal>main</literal>. In other cases, defaults to <literal>main</literal>.
</para> </para>
</listitem> </listitem>

View File

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

View File

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

View File

@ -57,16 +57,13 @@ bool env_value_is_valid(const char *e) {
if (!utf8_is_valid(e)) if (!utf8_is_valid(e))
return false; return false;
/* bash allows tabs and newlines in environment variables, and so /* Note that variable *values* may contain control characters, in particular NL, TAB, BS, DEL, ESC…
* should we */ * When printing those variables with show-environment, we'll escape them. Make sure to print
if (string_has_cc(e, "\t\n")) * environment variables carefully! */
return false;
/* POSIX says the overall size of the environment block cannot /* POSIX says the overall size of the environment block cannot be > ARG_MAX, an individual assignment
* be > ARG_MAX, an individual assignment hence cannot be * hence cannot be either. Discounting the shortest possible variable name of length 1, the equal
* either. Discounting the shortest possible variable name of * sign and trailing NUL this hence leaves ARG_MAX-3 as longest possible variable value. */
* length 1, the equal sign and trailing NUL this hence leaves
* ARG_MAX-3 as longest possible variable value. */
if (strlen(e) > sc_arg_max() - 3) if (strlen(e) > sc_arg_max() - 3)
return false; return false;
@ -86,10 +83,8 @@ bool env_assignment_is_valid(const char *e) {
if (!env_value_is_valid(eq + 1)) if (!env_value_is_valid(eq + 1))
return false; return false;
/* POSIX says the overall size of the environment block cannot /* POSIX says the overall size of the environment block cannot be > ARG_MAX, hence the individual
* be > ARG_MAX, hence the individual variable assignments * variable assignments cannot be either, but let's leave room for one trailing NUL byte. */
* cannot be either, but let's leave room for one trailing NUL
* byte. */
if (strlen(e) > sc_arg_max() - 1) if (strlen(e) > sc_arg_max() - 1)
return false; return false;

View File

@ -100,16 +100,25 @@ int chase_symlinks_and_opendir(const char *path, const char *root, unsigned chas
int chase_symlinks_and_stat(const char *path, const char *root, unsigned chase_flags, char **ret_path, struct stat *ret_stat, int *ret_fd); int chase_symlinks_and_stat(const char *path, const char *root, unsigned chase_flags, char **ret_path, struct stat *ret_stat, int *ret_fd);
/* Useful for usage with _cleanup_(), removes a directory and frees the pointer */ /* Useful for usage with _cleanup_(), removes a directory and frees the pointer */
static inline void rmdir_and_free(char *p) { static inline char *rmdir_and_free(char *p) {
PROTECT_ERRNO; PROTECT_ERRNO;
if (!p)
return NULL;
(void) rmdir(p); (void) rmdir(p);
free(p); free(p);
return NULL;
} }
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free); DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free);
static inline void unlink_and_free(char *p) { static inline char* unlink_and_free(char *p) {
if (!p)
return NULL;
(void) unlink_noerrno(p); (void) unlink_noerrno(p);
free(p); free(p);
return NULL;
} }
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);

View File

@ -18,17 +18,27 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
int rm_rf(const char *path, RemoveFlags flags); int rm_rf(const char *path, RemoveFlags flags);
/* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */
static inline void rm_rf_physical_and_free(char *p) { static inline char *rm_rf_physical_and_free(char *p) {
PROTECT_ERRNO; PROTECT_ERRNO;
if (!p)
return NULL;
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
free(p); free(p);
return NULL;
} }
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_physical_and_free); DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_physical_and_free);
/* Similar as above, but also has magic btrfs subvolume powers */ /* Similar as above, but also has magic btrfs subvolume powers */
static inline void rm_rf_subvolume_and_free(char *p) { static inline char *rm_rf_subvolume_and_free(char *p) {
PROTECT_ERRNO; PROTECT_ERRNO;
if (!p)
return NULL;
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
free(p); free(p);
return NULL;
} }
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_subvolume_and_free); DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_subvolume_and_free);

View File

@ -3183,7 +3183,9 @@ static int apply_mount_namespace(
if (context->mount_flags == MS_SHARED) if (context->mount_flags == MS_SHARED)
log_unit_debug(u, "shared mount propagation hidden by other fs namespacing unit settings: ignoring"); log_unit_debug(u, "shared mount propagation hidden by other fs namespacing unit settings: ignoring");
if (exec_context_has_credentials(context) && params->prefix[EXEC_DIRECTORY_RUNTIME]) { if (exec_context_has_credentials(context) &&
params->prefix[EXEC_DIRECTORY_RUNTIME] &&
FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
creds_path = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id); creds_path = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id);
if (!creds_path) { if (!creds_path) {
r = -ENOMEM; r = -ENOMEM;

View File

@ -142,6 +142,20 @@ int identity_add_token_pin(JsonVariant **v, const char *pin) {
return 1; return 1;
} }
static int acquire_pkcs11_certificate(
const char *uri,
const char *askpw_friendly_name,
const char *askpw_icon_name,
X509 **ret_cert,
char **ret_pin_used) {
#if HAVE_P11KIT
return pkcs11_acquire_certificate(uri, askpw_friendly_name, askpw_icon_name, ret_cert, ret_pin_used);
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 tokens not supported on this build.");
#endif
}
int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) { int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL; _cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
_cleanup_(erase_and_freep) char *pin = NULL; _cleanup_(erase_and_freep) char *pin = NULL;
@ -152,7 +166,7 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
assert(v); assert(v);
r = pkcs11_acquire_certificate(uri, "home directory operation", "user-home", &cert, &pin); r = acquire_pkcs11_certificate(uri, "home directory operation", "user-home", &cert, &pin);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -231,7 +231,8 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) {
if (!c) if (!c)
return -ENOMEM; return -ENOMEM;
/* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */ if (DEBUG_LOGGING)
(void) curl_easy_setopt(c, CURLOPT_VERBOSE, 1L);
if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK)
return -EIO; return -EIO;

View File

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

View File

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

View File

@ -110,7 +110,7 @@ int pull_find_old_etags(
return 0; return 0;
} }
int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) { int pull_make_local_copy(const char *final, const char *image_root, const char *local, PullFlags flags) {
const char *p; const char *p;
int r; int r;
@ -122,7 +122,7 @@ int pull_make_local_copy(const char *final, const char *image_root, const char *
p = prefix_roota(image_root, local); p = prefix_roota(image_root, local);
if (force_local) if (FLAGS_SET(flags, PULL_FORCE))
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
r = btrfs_subvol_snapshot(final, p, r = btrfs_subvol_snapshot(final, p,
@ -255,7 +255,6 @@ int pull_make_verification_jobs(
_cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL; _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
int r; int r;
const char *chksums = NULL;
assert(ret_checksum_job); assert(ret_checksum_job);
assert(ret_signature_job); assert(ret_signature_job);
@ -266,6 +265,7 @@ int pull_make_verification_jobs(
if (verify != IMPORT_VERIFY_NO) { if (verify != IMPORT_VERIFY_NO) {
_cleanup_free_ char *checksum_url = NULL, *fn = NULL; _cleanup_free_ char *checksum_url = NULL, *fn = NULL;
const char *chksums = NULL;
/* Queue jobs for the checksum file for the image. */ /* Queue jobs for the checksum file for the image. */
r = import_url_last_component(url, &fn); r = import_url_last_component(url, &fn);
@ -302,10 +302,8 @@ int pull_make_verification_jobs(
signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL; signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
} }
*ret_checksum_job = checksum_job; *ret_checksum_job = TAKE_PTR(checksum_job);
*ret_signature_job = signature_job; *ret_signature_job = TAKE_PTR(signature_job);
checksum_job = signature_job = NULL;
return 0; return 0;
} }
@ -365,70 +363,35 @@ static int verify_one(PullJob *checksum_job, PullJob *job) {
return 1; return 1;
} }
int pull_verify(PullJob *main_job, static int verify_gpg(
PullJob *roothash_job, const void *payload, size_t payload_size,
PullJob *settings_job, const void *signature, size_t signature_size) {
PullJob *checksum_job,
PullJob *signature_job) {
_cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 }; _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
_cleanup_close_ int sig_file = -1;
char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX"; char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
_cleanup_(sigkill_waitp) pid_t pid = 0; _cleanup_(sigkill_waitp) pid_t pid = 0;
bool gpg_home_created = false; bool gpg_home_created = false;
int r; int r;
assert(main_job); assert(payload || payload_size == 0);
assert(main_job->state == PULL_JOB_DONE); assert(signature || signature_size == 0);
if (!checksum_job)
return 0;
assert(main_job->calc_checksum);
assert(main_job->checksum);
assert(checksum_job->state == PULL_JOB_DONE);
if (!checksum_job->payload || checksum_job->payload_size <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"Checksum is empty, cannot verify.");
r = verify_one(checksum_job, main_job);
if (r < 0)
return r;
r = verify_one(checksum_job, roothash_job);
if (r < 0)
return r;
r = verify_one(checksum_job, settings_job);
if (r < 0)
return r;
if (!signature_job)
return 0;
if (checksum_job->style == VERIFICATION_PER_FILE)
signature_job = checksum_job;
assert(signature_job->state == PULL_JOB_DONE);
if (!signature_job->payload || signature_job->payload_size <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"Signature is empty, cannot verify.");
r = pipe2(gpg_pipe, O_CLOEXEC); r = pipe2(gpg_pipe, O_CLOEXEC);
if (r < 0) if (r < 0)
return log_error_errno(errno, "Failed to create pipe for gpg: %m"); return log_error_errno(errno, "Failed to create pipe for gpg: %m");
sig_file = mkostemp(sig_file_path, O_RDWR); if (signature_size > 0) {
if (sig_file < 0) _cleanup_close_ int sig_file = -1;
return log_error_errno(errno, "Failed to create temporary file: %m");
r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false); sig_file = mkostemp(sig_file_path, O_RDWR);
if (r < 0) { if (sig_file < 0)
log_error_errno(r, "Failed to write to temporary file: %m"); return log_error_errno(errno, "Failed to create temporary file: %m");
goto finish;
r = loop_write(sig_file, signature, signature_size, false);
if (r < 0) {
log_error_errno(r, "Failed to write to temporary file: %m");
goto finish;
}
} }
if (!mkdtemp(gpg_home)) { if (!mkdtemp(gpg_home)) {
@ -457,7 +420,7 @@ int pull_verify(PullJob *main_job,
NULL, /* dash */ NULL, /* dash */
NULL /* trailing NULL */ NULL /* trailing NULL */
}; };
unsigned k = ELEMENTSOF(cmd) - 6; size_t k = ELEMENTSOF(cmd) - 6;
/* Child */ /* Child */
@ -473,8 +436,7 @@ int pull_verify(PullJob *main_job,
cmd[k++] = strjoina("--homedir=", gpg_home); cmd[k++] = strjoina("--homedir=", gpg_home);
/* We add the user keyring only to the command line /* We add the user keyring only to the command line arguments, if it's around since gpg fails
* arguments, if it's around since gpg fails
* otherwise. */ * otherwise. */
if (access(USER_KEYRING_PATH, F_OK) >= 0) if (access(USER_KEYRING_PATH, F_OK) >= 0)
cmd[k++] = "--keyring=" USER_KEYRING_PATH; cmd[k++] = "--keyring=" USER_KEYRING_PATH;
@ -482,7 +444,7 @@ int pull_verify(PullJob *main_job,
cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH; cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
cmd[k++] = "--verify"; cmd[k++] = "--verify";
if (checksum_job->style == VERIFICATION_PER_DIRECTORY) { if (signature) {
cmd[k++] = sig_file_path; cmd[k++] = sig_file_path;
cmd[k++] = "-"; cmd[k++] = "-";
cmd[k++] = NULL; cmd[k++] = NULL;
@ -496,7 +458,7 @@ int pull_verify(PullJob *main_job,
gpg_pipe[0] = safe_close(gpg_pipe[0]); gpg_pipe[0] = safe_close(gpg_pipe[0]);
r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false); r = loop_write(gpg_pipe[1], payload, payload_size, false);
if (r < 0) { if (r < 0) {
log_error_errno(r, "Failed to write to pipe: %m"); log_error_errno(r, "Failed to write to pipe: %m");
goto finish; goto finish;
@ -517,10 +479,120 @@ int pull_verify(PullJob *main_job,
} }
finish: finish:
(void) unlink(sig_file_path); if (signature_size > 0)
(void) unlink(sig_file_path);
if (gpg_home_created) if (gpg_home_created)
(void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
return r; return r;
} }
int pull_verify(ImportVerify verify,
PullJob *main_job,
PullJob *roothash_job,
PullJob *settings_job,
PullJob *checksum_job,
PullJob *signature_job) {
VerificationStyle style;
int r;
assert(main_job);
assert(main_job->state == PULL_JOB_DONE);
if (verify == IMPORT_VERIFY_NO)
return 0;
assert(main_job->calc_checksum);
assert(main_job->checksum);
assert(checksum_job);
assert(checksum_job->state == PULL_JOB_DONE);
if (!checksum_job->payload || checksum_job->payload_size <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"Checksum is empty, cannot verify.");
r = verify_one(checksum_job, main_job);
if (r < 0)
return r;
r = verify_one(checksum_job, roothash_job);
if (r < 0)
return r;
r = verify_one(checksum_job, settings_job);
if (r < 0)
return r;
if (verify == IMPORT_VERIFY_CHECKSUM)
return 0;
r = verification_style_from_url(checksum_job->url, &style);
if (r < 0)
return log_error_errno(r, "Failed to determine verification style from URL '%s': %m", checksum_job->url);
if (style == VERIFICATION_PER_DIRECTORY) {
assert(signature_job);
assert(signature_job->state == PULL_JOB_DONE);
if (!signature_job->payload || signature_job->payload_size <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
"Signature is empty, cannot verify.");
return verify_gpg(checksum_job->payload, checksum_job->payload_size, signature_job->payload, signature_job->payload_size);
} else
return verify_gpg(checksum_job->payload, checksum_job->payload_size, NULL, 0);
}
int verification_style_from_url(const char *url, VerificationStyle *ret) {
_cleanup_free_ char *last = NULL;
int r;
assert(url);
assert(ret);
/* Determines which kind of verification style is appropriate for this url */
r = import_url_last_component(url, &last);
if (r < 0)
return r;
if (streq(last, "SHA256SUMS")) {
*ret = VERIFICATION_PER_DIRECTORY;
return 0;
}
if (endswith(last, ".sha256")) {
*ret = VERIFICATION_PER_FILE;
return 0;
}
return -EINVAL;
}
int pull_job_restart_with_sha256sum(PullJob *j, char **ret) {
VerificationStyle style;
int r;
assert(j);
/* Generic implementation of a PullJobNotFound handler, that restarts the job requesting SHA256SUMS */
r = verification_style_from_url(j->url, &style);
if (r < 0)
return log_error_errno(r, "Failed to determine verification style of URL '%s': %m", j->url);
if (style == VERIFICATION_PER_DIRECTORY) /* Nothing to do anymore */
return 0;
assert(style == VERIFICATION_PER_FILE); /* This must have been .sha256 style URL before */
log_debug("Got 404 for %s, now trying to get SHA256SUMS instead.", j->url);
r = import_url_change_last_component(j->url, "SHA256SUMS", ret);
if (r < 0)
return log_error_errno(r, "Failed to replace SHA256SUMS suffix: %m");
return 1;
}

View File

@ -6,7 +6,19 @@
#include "import-util.h" #include "import-util.h"
#include "pull-job.h" #include "pull-job.h"
int pull_make_local_copy(const char *final, const char *root, const char *local, bool force_local); typedef enum PullFlags {
PULL_FORCE = 1 << 0, /* replace existing image */
PULL_SETTINGS = 1 << 1, /* .nspawn settings file */
PULL_ROOTHASH = 1 << 2, /* only for raw: .roothash file for verity */
PULL_ROOTHASH_SIGNATURE = 1 << 3, /* only for raw: .roothash.p7s file for verity */
PULL_VERITY = 1 << 4, /* only for raw: .verity file for verity */
/* The supported flags for the tar and the raw pulling */
PULL_FLAGS_MASK_TAR = PULL_FORCE|PULL_SETTINGS,
PULL_FLAGS_MASK_RAW = PULL_FORCE|PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY,
} PullFlags;
int pull_make_local_copy(const char *final, const char *root, const char *local, PullFlags flags);
int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags);
@ -15,4 +27,15 @@ int pull_make_path(const char *url, const char *etag, const char *image_root, co
int pull_make_auxiliary_job(PullJob **ret, const char *url, int (*strip_suffixes)(const char *name, char **ret), const char *suffix, CurlGlue *glue, PullJobFinished on_finished, void *userdata); int pull_make_auxiliary_job(PullJob **ret, const char *url, int (*strip_suffixes)(const char *name, char **ret), const char *suffix, CurlGlue *glue, PullJobFinished on_finished, void *userdata);
int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata);
int pull_verify(PullJob *main_job, PullJob *roothash_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job); int pull_verify(ImportVerify verify, PullJob *main_job, PullJob *roothash_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job);
typedef enum VerificationStyle {
VERIFICATION_PER_FILE, /* SuSE-style ".sha256" files with inline gpg signature */
VERIFICATION_PER_DIRECTORY, /* Ubuntu-style SHA256SUM files with detached SHA256SUM.gpg signatures */
_VERIFICATION_STYLE_MAX,
_VERIFICATION_STYLE_INVALID = -1,
} VerificationStyle;
int verification_style_from_url(const char *url, VerificationStyle *style);
int pull_job_restart_with_sha256sum(PullJob *job, char **ret);

View File

@ -61,22 +61,41 @@ static void pull_job_finish(PullJob *j, int ret) {
j->on_finished(j); j->on_finished(j);
} }
static int pull_job_restart(PullJob *j) { static int pull_job_restart(PullJob *j, const char *new_url) {
int r; int r;
char *chksum_url = NULL;
r = import_url_change_last_component(j->url, "SHA256SUMS", &chksum_url); assert(j);
assert(new_url);
r = free_and_strdup(&j->url, new_url);
if (r < 0) if (r < 0)
return r; return r;
free(j->url);
j->url = chksum_url;
j->state = PULL_JOB_INIT; j->state = PULL_JOB_INIT;
j->error = 0;
j->payload = mfree(j->payload); j->payload = mfree(j->payload);
j->payload_size = 0; j->payload_size = 0;
j->payload_allocated = 0; j->payload_allocated = 0;
j->written_compressed = 0; j->written_compressed = 0;
j->written_uncompressed = 0; j->written_uncompressed = 0;
j->content_length = UINT64_MAX;
j->etag = mfree(j->etag);
j->etag_exists = false;
j->mtime = 0;
j->checksum = mfree(j->checksum);
curl_glue_remove_and_free(j->glue, j->curl);
j->curl = NULL;
curl_slist_free_all(j->request_header);
j->request_header = NULL;
import_compress_free(&j->compress);
if (j->checksum_context) {
gcry_md_close(j->checksum_context);
j->checksum_context = NULL;
}
r = pull_job_begin(j); r = pull_job_begin(j);
if (r < 0) if (r < 0)
@ -114,23 +133,31 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
r = 0; r = 0;
goto finish; goto finish;
} else if (status >= 300) { } else if (status >= 300) {
if (status == 404 && j->style == VERIFICATION_PER_FILE) {
/* retry pull job with SHA256SUMS file */ if (status == 404 && j->on_not_found) {
r = pull_job_restart(j); _cleanup_free_ char *new_url = NULL;
/* This resource wasn't found, but the implementor wants to maybe let us know a new URL, query for it. */
r = j->on_not_found(j, &new_url);
if (r < 0) if (r < 0)
goto finish; goto finish;
code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); if (r > 0) { /* A new url to use */
if (code != CURLE_OK) { assert(new_url);
log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
r = -EIO;
goto finish;
}
if (status == 0) { r = pull_job_restart(j, new_url);
j->style = VERIFICATION_PER_DIRECTORY; if (r < 0)
return; goto finish;
code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status);
if (code != CURLE_OK) {
log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
r = -EIO;
goto finish;
}
if (status == 0)
return;
} }
} }
@ -407,10 +434,11 @@ fail:
} }
static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) { static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
PullJob *j = userdata; _cleanup_free_ char *length = NULL, *last_modified = NULL, *etag = NULL;
size_t sz = size * nmemb; size_t sz = size * nmemb;
_cleanup_free_ char *length = NULL, *last_modified = NULL; PullJob *j = userdata;
char *etag; CURLcode code;
long status;
int r; int r;
assert(contents); assert(contents);
@ -423,14 +451,25 @@ static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb
assert(j->state == PULL_JOB_ANALYZING); assert(j->state == PULL_JOB_ANALYZING);
code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status);
if (code != CURLE_OK) {
log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
r = -EIO;
goto fail;
}
if (status < 200 || status >= 300)
/* If this is not HTTP 2xx, let's skip these headers, they are probably for
* some redirect or so, and we are not interested in the headers of those. */
return sz;
r = curl_header_strdup(contents, sz, "ETag:", &etag); r = curl_header_strdup(contents, sz, "ETag:", &etag);
if (r < 0) { if (r < 0) {
log_oom(); log_oom();
goto fail; goto fail;
} }
if (r > 0) { if (r > 0) {
free(j->etag); free_and_replace(j->etag, etag);
j->etag = etag;
if (strv_contains(j->old_etags, j->etag)) { if (strv_contains(j->old_etags, j->etag)) {
log_info("Image already downloaded. Skipping download."); log_info("Image already downloaded. Skipping download.");
@ -556,7 +595,6 @@ int pull_job_new(PullJob **ret, const char *url, CurlGlue *glue, void *userdata)
.start_usec = now(CLOCK_MONOTONIC), .start_usec = now(CLOCK_MONOTONIC),
.compressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU, /* 64GB safety limit */ .compressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU, /* 64GB safety limit */
.uncompressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU, /* 64GB safety limit */ .uncompressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU, /* 64GB safety limit */
.style = VERIFICATION_STYLE_UNSET,
.url = TAKE_PTR(u), .url = TAKE_PTR(u),
}; };

View File

@ -13,23 +13,18 @@ typedef void (*PullJobFinished)(PullJob *job);
typedef int (*PullJobOpenDisk)(PullJob *job); typedef int (*PullJobOpenDisk)(PullJob *job);
typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz); typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz);
typedef void (*PullJobProgress)(PullJob *job); typedef void (*PullJobProgress)(PullJob *job);
typedef int (*PullJobNotFound)(PullJob *job, char **ret_new_url);
typedef enum PullJobState { typedef enum PullJobState {
PULL_JOB_INIT, PULL_JOB_INIT,
PULL_JOB_ANALYZING, /* Still reading into ->payload, to figure out what we have */ PULL_JOB_ANALYZING, /* Still reading into ->payload, to figure out what we have */
PULL_JOB_RUNNING, /* Writing to destination */ PULL_JOB_RUNNING, /* Writing to destination */
PULL_JOB_DONE, PULL_JOB_DONE,
PULL_JOB_FAILED, PULL_JOB_FAILED,
_PULL_JOB_STATE_MAX, _PULL_JOB_STATE_MAX,
_PULL_JOB_STATE_INVALID = -1, _PULL_JOB_STATE_INVALID = -1,
} PullJobState; } PullJobState;
typedef enum VerificationStyle {
VERIFICATION_STYLE_UNSET,
VERIFICATION_PER_FILE, /* SuSE-style ".sha256" files with inline signature */
VERIFICATION_PER_DIRECTORY, /* Ubuntu-style SHA256SUM files with detach SHA256SUM.gpg signatures */
} VerificationStyle;
#define PULL_JOB_IS_COMPLETE(j) (IN_SET((j)->state, PULL_JOB_DONE, PULL_JOB_FAILED)) #define PULL_JOB_IS_COMPLETE(j) (IN_SET((j)->state, PULL_JOB_DONE, PULL_JOB_FAILED))
struct PullJob { struct PullJob {
@ -43,6 +38,7 @@ struct PullJob {
PullJobOpenDisk on_open_disk; PullJobOpenDisk on_open_disk;
PullJobHeader on_header; PullJobHeader on_header;
PullJobProgress on_progress; PullJobProgress on_progress;
PullJobNotFound on_not_found;
CurlGlue *glue; CurlGlue *glue;
CURL *curl; CURL *curl;
@ -79,8 +75,6 @@ struct PullJob {
gcry_md_hd_t checksum_context; gcry_md_hd_t checksum_context;
char *checksum; char *checksum;
VerificationStyle style;
}; };
int pull_job_new(PullJob **job, const char *url, CurlGlue *glue, void *userdata); int pull_job_new(PullJob **job, const char *url, CurlGlue *glue, void *userdata);

View File

@ -42,21 +42,22 @@ struct RawPull {
sd_event *event; sd_event *event;
CurlGlue *glue; CurlGlue *glue;
PullFlags flags;
ImportVerify verify;
char *image_root; char *image_root;
PullJob *raw_job; PullJob *raw_job;
PullJob *roothash_job;
PullJob *settings_job;
PullJob *checksum_job; PullJob *checksum_job;
PullJob *signature_job; PullJob *signature_job;
PullJob *settings_job;
PullJob *roothash_job;
PullJob *roothash_signature_job;
PullJob *verity_job;
RawPullFinished on_finished; RawPullFinished on_finished;
void *userdata; void *userdata;
char *local; char *local;
bool force_local;
bool settings;
bool roothash;
char *final_path; char *final_path;
char *temp_path; char *temp_path;
@ -67,7 +68,11 @@ struct RawPull {
char *roothash_path; char *roothash_path;
char *roothash_temp_path; char *roothash_temp_path;
ImportVerify verify; char *roothash_signature_path;
char *roothash_signature_temp_path;
char *verity_path;
char *verity_temp_path;
}; };
RawPull* raw_pull_unref(RawPull *i) { RawPull* raw_pull_unref(RawPull *i) {
@ -75,34 +80,30 @@ RawPull* raw_pull_unref(RawPull *i) {
return NULL; return NULL;
pull_job_unref(i->raw_job); pull_job_unref(i->raw_job);
pull_job_unref(i->settings_job);
pull_job_unref(i->roothash_job);
pull_job_unref(i->checksum_job); pull_job_unref(i->checksum_job);
pull_job_unref(i->signature_job); pull_job_unref(i->signature_job);
pull_job_unref(i->settings_job);
pull_job_unref(i->roothash_job);
pull_job_unref(i->roothash_signature_job);
pull_job_unref(i->verity_job);
curl_glue_unref(i->glue); curl_glue_unref(i->glue);
sd_event_unref(i->event); sd_event_unref(i->event);
if (i->temp_path) { unlink_and_free(i->temp_path);
(void) unlink(i->temp_path); unlink_and_free(i->settings_temp_path);
free(i->temp_path); unlink_and_free(i->roothash_temp_path);
} unlink_and_free(i->roothash_signature_temp_path);
unlink_and_free(i->verity_temp_path);
if (i->roothash_temp_path) {
(void) unlink(i->roothash_temp_path);
free(i->roothash_temp_path);
}
if (i->settings_temp_path) {
(void) unlink(i->settings_temp_path);
free(i->settings_temp_path);
}
free(i->final_path); free(i->final_path);
free(i->roothash_path);
free(i->settings_path); free(i->settings_path);
free(i->roothash_path);
free(i->roothash_signature_path);
free(i->verity_path);
free(i->image_root); free(i->image_root);
free(i->local); free(i->local);
return mfree(i); return mfree(i);
} }
@ -169,6 +170,16 @@ static void raw_pull_report_progress(RawPull *i, RawProgress p) {
percent = 0; percent = 0;
if (i->checksum_job) {
percent += i->checksum_job->progress_percent * 5 / 100;
remain -= 5;
}
if (i->signature_job) {
percent += i->signature_job->progress_percent * 5 / 100;
remain -= 5;
}
if (i->settings_job) { if (i->settings_job) {
percent += i->settings_job->progress_percent * 5 / 100; percent += i->settings_job->progress_percent * 5 / 100;
remain -= 5; remain -= 5;
@ -179,14 +190,14 @@ static void raw_pull_report_progress(RawPull *i, RawProgress p) {
remain -= 5; remain -= 5;
} }
if (i->checksum_job) { if (i->roothash_signature_job) {
percent += i->checksum_job->progress_percent * 5 / 100; percent += i->roothash_signature_job->progress_percent * 5 / 100;
remain -= 5; remain -= 5;
} }
if (i->signature_job) { if (i->verity_job) {
percent += i->signature_job->progress_percent * 5 / 100; percent += i->verity_job->progress_percent * 10 / 100;
remain -= 5; remain -= 10;
} }
if (i->raw_job) if (i->raw_job)
@ -294,7 +305,7 @@ static int raw_pull_copy_auxiliary_file(
local = strjoina(i->image_root, "/", i->local, suffix); local = strjoina(i->image_root, "/", i->local, suffix);
r = copy_file_atomic(*path, local, 0644, 0, 0, COPY_REFLINK | (i->force_local ? COPY_REPLACE : 0)); r = copy_file_atomic(*path, local, 0644, 0, 0, COPY_REFLINK | (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0));
if (r == -EEXIST) if (r == -EEXIST)
log_warning_errno(r, "File %s already exists, not replacing.", local); log_warning_errno(r, "File %s already exists, not replacing.", local);
else if (r == -ENOENT) else if (r == -ENOENT)
@ -338,7 +349,7 @@ static int raw_pull_make_local_copy(RawPull *i) {
p = strjoina(i->image_root, "/", i->local, ".raw"); p = strjoina(i->image_root, "/", i->local, ".raw");
if (i->force_local) if (FLAGS_SET(i->flags, PULL_FORCE))
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
r = tempfn_random(p, NULL, &tp); r = tempfn_random(p, NULL, &tp);
@ -373,14 +384,26 @@ static int raw_pull_make_local_copy(RawPull *i) {
log_info("Created new local image '%s'.", i->local); log_info("Created new local image '%s'.", i->local);
if (i->roothash) { if (FLAGS_SET(i->flags, PULL_SETTINGS)) {
r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path);
if (r < 0)
return r;
}
if (FLAGS_SET(i->flags, PULL_ROOTHASH)) {
r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path); r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path);
if (r < 0) if (r < 0)
return r; return r;
} }
if (i->settings) { if (FLAGS_SET(i->flags, PULL_ROOTHASH_SIGNATURE)) {
r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path); r = raw_pull_copy_auxiliary_file(i, ".roothash.p7s", &i->roothash_signature_path);
if (r < 0)
return r;
}
if (FLAGS_SET(i->flags, PULL_VERITY)) {
r = raw_pull_copy_auxiliary_file(i, ".verity", &i->verity_path);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -394,14 +417,18 @@ static bool raw_pull_is_done(RawPull *i) {
if (!PULL_JOB_IS_COMPLETE(i->raw_job)) if (!PULL_JOB_IS_COMPLETE(i->raw_job))
return false; return false;
if (i->roothash_job && !PULL_JOB_IS_COMPLETE(i->roothash_job))
return false;
if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
return false;
if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
return false; return false;
if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job)) if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
return false; return false;
if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
return false;
if (i->roothash_job && !PULL_JOB_IS_COMPLETE(i->roothash_job))
return false;
if (i->roothash_signature_job && !PULL_JOB_IS_COMPLETE(i->roothash_signature_job))
return false;
if (i->verity_job && !PULL_JOB_IS_COMPLETE(i->verity_job))
return false;
return true; return true;
} }
@ -447,12 +474,18 @@ static void raw_pull_job_on_finished(PullJob *j) {
assert(j->userdata); assert(j->userdata);
i = j->userdata; i = j->userdata;
if (j == i->roothash_job) { if (j == i->settings_job) {
if (j->error != 0)
log_info_errno(j->error, "Root hash file could not be retrieved, proceeding without.");
} else if (j == i->settings_job) {
if (j->error != 0) if (j->error != 0)
log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); log_info_errno(j->error, "Settings file could not be retrieved, proceeding without.");
} else if (j == i->roothash_job) {
if (j->error != 0)
log_info_errno(j->error, "Root hash file could not be retrieved, proceeding without.");
} else if (j == i->roothash_signature_job) {
if (j->error != 0)
log_info_errno(j->error, "Root hash signature file could not be retrieved, proceeding without.");
} else if (j == i->verity_job) {
if (j->error != 0)
log_info_errno(j->error, "Verity integrity file could not be retrieved, proceeding without. %s", j->url);
} else if (j->error != 0 && j != i->signature_job) { } else if (j->error != 0 && j != i->signature_job) {
if (j == i->checksum_job) if (j == i->checksum_job)
log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)"); log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
@ -463,27 +496,41 @@ static void raw_pull_job_on_finished(PullJob *j) {
goto finish; goto finish;
} }
/* This is invoked if either the download completed /* This is invoked if either the download completed successfully, or the download was skipped because
* successfully, or the download was skipped because we * we already have the etag. In this case ->etag_exists is true.
* already have the etag. In this case ->etag_exists is
* true.
* *
* We only do something when we got all three files */ * We only do something when we got all three files */
if (!raw_pull_is_done(i)) if (!raw_pull_is_done(i))
return; return;
if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) { if (i->signature_job && i->signature_job->error != 0) {
log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); VerificationStyle style;
r = i->signature_job->error; r = verification_style_from_url(i->checksum_job->url, &style);
goto finish; if (r < 0) {
log_error_errno(r, "Failed to determine verification style from checksum URL: %m");
goto finish;
}
if (style == VERIFICATION_PER_DIRECTORY) { /* A failed signature file download only matters
* in per-directory verification mode, since only
* then the signature is detached, and thus a file
* of its own. */
log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
r = i->signature_job->error;
goto finish;
}
} }
if (i->roothash_job)
i->roothash_job->disk_fd = safe_close(i->roothash_job->disk_fd);
if (i->settings_job) if (i->settings_job)
i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd);
if (i->roothash_job)
i->roothash_job->disk_fd = safe_close(i->roothash_job->disk_fd);
if (i->roothash_signature_job)
i->roothash_signature_job->disk_fd = safe_close(i->roothash_signature_job->disk_fd);
if (i->verity_job)
i->verity_job->disk_fd = safe_close(i->verity_job->disk_fd);
r = raw_pull_determine_path(i, ".raw", &i->final_path); r = raw_pull_determine_path(i, ".raw", &i->final_path);
if (r < 0) if (r < 0)
@ -495,7 +542,7 @@ static void raw_pull_job_on_finished(PullJob *j) {
raw_pull_report_progress(i, RAW_VERIFYING); raw_pull_report_progress(i, RAW_VERIFYING);
r = pull_verify(i->raw_job, i->roothash_job, i->settings_job, i->checksum_job, i->signature_job); r = pull_verify(i->verify, i->raw_job, i->roothash_job, i->settings_job, i->checksum_job, i->signature_job);
if (r < 0) if (r < 0)
goto finish; goto finish;
@ -598,6 +645,18 @@ static int raw_pull_job_on_open_disk_raw(PullJob *j) {
return 0; return 0;
} }
static int raw_pull_job_on_open_disk_settings(PullJob *j) {
RawPull *i;
assert(j);
assert(j->userdata);
i = j->userdata;
assert(i->settings_job == j);
return raw_pull_job_on_open_disk_generic(i, j, "settings", &i->settings_temp_path);
}
static int raw_pull_job_on_open_disk_roothash(PullJob *j) { static int raw_pull_job_on_open_disk_roothash(PullJob *j) {
RawPull *i; RawPull *i;
@ -610,16 +669,28 @@ static int raw_pull_job_on_open_disk_roothash(PullJob *j) {
return raw_pull_job_on_open_disk_generic(i, j, "roothash", &i->roothash_temp_path); return raw_pull_job_on_open_disk_generic(i, j, "roothash", &i->roothash_temp_path);
} }
static int raw_pull_job_on_open_disk_settings(PullJob *j) { static int raw_pull_job_on_open_disk_roothash_signature(PullJob *j) {
RawPull *i; RawPull *i;
assert(j); assert(j);
assert(j->userdata); assert(j->userdata);
i = j->userdata; i = j->userdata;
assert(i->settings_job == j); assert(i->roothash_signature_job == j);
return raw_pull_job_on_open_disk_generic(i, j, "settings", &i->settings_temp_path); return raw_pull_job_on_open_disk_generic(i, j, "roothash.p7s", &i->roothash_signature_temp_path);
}
static int raw_pull_job_on_open_disk_verity(PullJob *j) {
RawPull *i;
assert(j);
assert(j->userdata);
i = j->userdata;
assert(i->verity_job == j);
return raw_pull_job_on_open_disk_generic(i, j, "verity", &i->verity_temp_path);
} }
static void raw_pull_job_on_progress(PullJob *j) { static void raw_pull_job_on_progress(PullJob *j) {
@ -637,16 +708,15 @@ int raw_pull_start(
RawPull *i, RawPull *i,
const char *url, const char *url,
const char *local, const char *local,
bool force_local, PullFlags flags,
ImportVerify verify, ImportVerify verify) {
bool settings,
bool roothash) {
int r; int r;
assert(i); assert(i);
assert(verify < _IMPORT_VERIFY_MAX); assert(verify < _IMPORT_VERIFY_MAX);
assert(verify >= 0); assert(verify >= 0);
assert(!(flags & ~PULL_FLAGS_MASK_RAW));
if (!http_url_is_valid(url)) if (!http_url_is_valid(url))
return -EINVAL; return -EINVAL;
@ -661,10 +731,8 @@ int raw_pull_start(
if (r < 0) if (r < 0)
return r; return r;
i->force_local = force_local; i->flags = flags;
i->verify = verify; i->verify = verify;
i->settings = settings;
i->roothash = roothash;
/* Queue job for the image itself */ /* Queue job for the image itself */
r = pull_job_new(&i->raw_job, url, i->glue, i); r = pull_job_new(&i->raw_job, url, i->glue, i);
@ -680,17 +748,11 @@ int raw_pull_start(
if (r < 0) if (r < 0)
return r; return r;
if (roothash) { r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i);
r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i); if (r < 0)
if (r < 0) return r;
return r;
i->roothash_job->on_open_disk = raw_pull_job_on_open_disk_roothash; if (FLAGS_SET(flags, PULL_SETTINGS)) {
i->roothash_job->on_progress = raw_pull_job_on_progress;
i->roothash_job->calc_checksum = verify != IMPORT_VERIFY_NO;
}
if (settings) {
r = pull_make_auxiliary_job(&i->settings_job, url, raw_strip_suffixes, ".nspawn", i->glue, raw_pull_job_on_finished, i); r = pull_make_auxiliary_job(&i->settings_job, url, raw_strip_suffixes, ".nspawn", i->glue, raw_pull_job_on_finished, i);
if (r < 0) if (r < 0)
return r; return r;
@ -700,29 +762,43 @@ int raw_pull_start(
i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO;
} }
r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i); if (FLAGS_SET(flags, PULL_ROOTHASH)) {
if (r < 0) r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i);
return r; if (r < 0)
return r;
i->roothash_job->on_open_disk = raw_pull_job_on_open_disk_roothash;
i->roothash_job->on_progress = raw_pull_job_on_progress;
i->roothash_job->calc_checksum = verify != IMPORT_VERIFY_NO;
}
if (FLAGS_SET(flags, PULL_ROOTHASH_SIGNATURE)) {
r = pull_make_auxiliary_job(&i->roothash_signature_job, url, raw_strip_suffixes, ".roothash.p7s", i->glue, raw_pull_job_on_finished, i);
if (r < 0)
return r;
i->roothash_signature_job->on_open_disk = raw_pull_job_on_open_disk_roothash_signature;
i->roothash_signature_job->on_progress = raw_pull_job_on_progress;
i->roothash_signature_job->calc_checksum = verify != IMPORT_VERIFY_NO;
}
if (FLAGS_SET(flags, PULL_VERITY)) {
r = pull_make_auxiliary_job(&i->verity_job, url, raw_strip_suffixes, ".verity", i->glue, raw_pull_job_on_finished, i);
if (r < 0)
return r;
i->verity_job->on_open_disk = raw_pull_job_on_open_disk_verity;
i->verity_job->on_progress = raw_pull_job_on_progress;
i->verity_job->calc_checksum = verify != IMPORT_VERIFY_NO;
}
r = pull_job_begin(i->raw_job); r = pull_job_begin(i->raw_job);
if (r < 0) if (r < 0)
return r; return r;
if (i->roothash_job) {
r = pull_job_begin(i->roothash_job);
if (r < 0)
return r;
}
if (i->settings_job) {
r = pull_job_begin(i->settings_job);
if (r < 0)
return r;
}
if (i->checksum_job) { if (i->checksum_job) {
i->checksum_job->on_progress = raw_pull_job_on_progress; i->checksum_job->on_progress = raw_pull_job_on_progress;
i->checksum_job->style = VERIFICATION_PER_FILE; i->checksum_job->on_not_found = pull_job_restart_with_sha256sum;
r = pull_job_begin(i->checksum_job); r = pull_job_begin(i->checksum_job);
if (r < 0) if (r < 0)
@ -737,5 +813,29 @@ int raw_pull_start(
return r; return r;
} }
if (i->settings_job) {
r = pull_job_begin(i->settings_job);
if (r < 0)
return r;
}
if (i->roothash_job) {
r = pull_job_begin(i->roothash_job);
if (r < 0)
return r;
}
if (i->roothash_signature_job) {
r = pull_job_begin(i->roothash_signature_job);
if (r < 0)
return r;
}
if (i->verity_job) {
r = pull_job_begin(i->verity_job);
if (r < 0)
return r;
}
return 0; return 0;
} }

View File

@ -5,6 +5,7 @@
#include "import-util.h" #include "import-util.h"
#include "macro.h" #include "macro.h"
#include "pull-common.h"
typedef struct RawPull RawPull; typedef struct RawPull RawPull;
@ -15,4 +16,4 @@ RawPull* raw_pull_unref(RawPull *pull);
DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref);
int raw_pull_start(RawPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings, bool roothash); int raw_pull_start(RawPull *pull, const char *url, const char *local, PullFlags flags, ImportVerify verify);

View File

@ -40,19 +40,19 @@ struct TarPull {
sd_event *event; sd_event *event;
CurlGlue *glue; CurlGlue *glue;
PullFlags flags;
ImportVerify verify;
char *image_root; char *image_root;
PullJob *tar_job; PullJob *tar_job;
PullJob *settings_job;
PullJob *checksum_job; PullJob *checksum_job;
PullJob *signature_job; PullJob *signature_job;
PullJob *settings_job;
TarPullFinished on_finished; TarPullFinished on_finished;
void *userdata; void *userdata;
char *local; char *local;
bool force_local;
bool settings;
pid_t tar_pid; pid_t tar_pid;
@ -61,8 +61,6 @@ struct TarPull {
char *settings_path; char *settings_path;
char *settings_temp_path; char *settings_temp_path;
ImportVerify verify;
}; };
TarPull* tar_pull_unref(TarPull *i) { TarPull* tar_pull_unref(TarPull *i) {
@ -75,22 +73,15 @@ TarPull* tar_pull_unref(TarPull *i) {
} }
pull_job_unref(i->tar_job); pull_job_unref(i->tar_job);
pull_job_unref(i->settings_job);
pull_job_unref(i->checksum_job); pull_job_unref(i->checksum_job);
pull_job_unref(i->signature_job); pull_job_unref(i->signature_job);
pull_job_unref(i->settings_job);
curl_glue_unref(i->glue); curl_glue_unref(i->glue);
sd_event_unref(i->event); sd_event_unref(i->event);
if (i->temp_path) { rm_rf_subvolume_and_free(i->temp_path);
(void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); unlink_and_free(i->settings_temp_path);
free(i->temp_path);
}
if (i->settings_temp_path) {
(void) unlink(i->settings_temp_path);
free(i->settings_temp_path);
}
free(i->final_path); free(i->final_path);
free(i->settings_path); free(i->settings_path);
@ -163,11 +154,6 @@ static void tar_pull_report_progress(TarPull *i, TarProgress p) {
percent = 0; percent = 0;
if (i->settings_job) {
percent += i->settings_job->progress_percent * 5 / 100;
remain -= 5;
}
if (i->checksum_job) { if (i->checksum_job) {
percent += i->checksum_job->progress_percent * 5 / 100; percent += i->checksum_job->progress_percent * 5 / 100;
remain -= 5; remain -= 5;
@ -178,6 +164,11 @@ static void tar_pull_report_progress(TarPull *i, TarProgress p) {
remain -= 5; remain -= 5;
} }
if (i->settings_job) {
percent += i->settings_job->progress_percent * 5 / 100;
remain -= 5;
}
if (i->tar_job) if (i->tar_job)
percent += i->tar_job->progress_percent * remain / 100; percent += i->tar_job->progress_percent * remain / 100;
break; break;
@ -230,11 +221,11 @@ static int tar_pull_make_local_copy(TarPull *i) {
if (!i->local) if (!i->local)
return 0; return 0;
r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->force_local); r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->flags);
if (r < 0) if (r < 0)
return r; return r;
if (i->settings) { if (FLAGS_SET(i->flags, PULL_SETTINGS)) {
const char *local_settings; const char *local_settings;
assert(i->settings_job); assert(i->settings_job);
@ -244,7 +235,7 @@ static int tar_pull_make_local_copy(TarPull *i) {
local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); local_settings = strjoina(i->image_root, "/", i->local, ".nspawn");
r = copy_file_atomic(i->settings_path, local_settings, 0664, 0, 0, COPY_REFLINK | (i->force_local ? COPY_REPLACE : 0)); r = copy_file_atomic(i->settings_path, local_settings, 0664, 0, 0, COPY_REFLINK | (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0));
if (r == -EEXIST) if (r == -EEXIST)
log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings);
else if (r == -ENOENT) else if (r == -ENOENT)
@ -264,12 +255,12 @@ static bool tar_pull_is_done(TarPull *i) {
if (!PULL_JOB_IS_COMPLETE(i->tar_job)) if (!PULL_JOB_IS_COMPLETE(i->tar_job))
return false; return false;
if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
return false;
if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
return false; return false;
if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job)) if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
return false; return false;
if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
return false;
return true; return true;
} }
@ -302,11 +293,23 @@ static void tar_pull_job_on_finished(PullJob *j) {
if (!tar_pull_is_done(i)) if (!tar_pull_is_done(i))
return; return;
if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) { if (i->signature_job && i->signature_job->error != 0) {
log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); VerificationStyle style;
r = i->signature_job->error; r = verification_style_from_url(i->checksum_job->url, &style);
goto finish; if (r < 0) {
log_error_errno(r, "Failed to determine verification style from checksum URL: %m");
goto finish;
}
if (style == VERIFICATION_PER_DIRECTORY) { /* A failed signature file download only matters
* in per-directory verification mode, since only
* then the signature is detached, and thus a file
* of its own. */
log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
r = i->signature_job->error;
goto finish;
}
} }
i->tar_job->disk_fd = safe_close(i->tar_job->disk_fd); i->tar_job->disk_fd = safe_close(i->tar_job->disk_fd);
@ -333,7 +336,7 @@ static void tar_pull_job_on_finished(PullJob *j) {
tar_pull_report_progress(i, TAR_VERIFYING); tar_pull_report_progress(i, TAR_VERIFYING);
r = pull_verify(i->tar_job, NULL, i->settings_job, i->checksum_job, i->signature_job); r = pull_verify(i->verify, i->tar_job, NULL, i->settings_job, i->checksum_job, i->signature_job);
if (r < 0) if (r < 0)
goto finish; goto finish;
@ -471,15 +474,15 @@ int tar_pull_start(
TarPull *i, TarPull *i,
const char *url, const char *url,
const char *local, const char *local,
bool force_local, PullFlags flags,
ImportVerify verify, ImportVerify verify) {
bool settings) {
int r; int r;
assert(i); assert(i);
assert(verify < _IMPORT_VERIFY_MAX); assert(verify < _IMPORT_VERIFY_MAX);
assert(verify >= 0); assert(verify >= 0);
assert(!(flags & ~PULL_FLAGS_MASK_TAR));
if (!http_url_is_valid(url)) if (!http_url_is_valid(url))
return -EINVAL; return -EINVAL;
@ -494,9 +497,8 @@ int tar_pull_start(
if (r < 0) if (r < 0)
return r; return r;
i->force_local = force_local; i->flags = flags;
i->verify = verify; i->verify = verify;
i->settings = settings;
/* Set up download job for TAR file */ /* Set up download job for TAR file */
r = pull_job_new(&i->tar_job, url, i->glue, i); r = pull_job_new(&i->tar_job, url, i->glue, i);
@ -512,8 +514,13 @@ int tar_pull_start(
if (r < 0) if (r < 0)
return r; return r;
/* Set up download of checksum/signature files */
r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_pull_job_on_finished, i);
if (r < 0)
return r;
/* Set up download job for the settings file (.nspawn) */ /* Set up download job for the settings file (.nspawn) */
if (settings) { if (FLAGS_SET(flags, PULL_SETTINGS)) {
r = pull_make_auxiliary_job(&i->settings_job, url, tar_strip_suffixes, ".nspawn", i->glue, tar_pull_job_on_finished, i); r = pull_make_auxiliary_job(&i->settings_job, url, tar_strip_suffixes, ".nspawn", i->glue, tar_pull_job_on_finished, i);
if (r < 0) if (r < 0)
return r; return r;
@ -523,24 +530,13 @@ int tar_pull_start(
i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO;
} }
/* Set up download of checksum/signature files */
r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_pull_job_on_finished, i);
if (r < 0)
return r;
r = pull_job_begin(i->tar_job); r = pull_job_begin(i->tar_job);
if (r < 0) if (r < 0)
return r; return r;
if (i->settings_job) {
r = pull_job_begin(i->settings_job);
if (r < 0)
return r;
}
if (i->checksum_job) { if (i->checksum_job) {
i->checksum_job->on_progress = tar_pull_job_on_progress; i->checksum_job->on_progress = tar_pull_job_on_progress;
i->checksum_job->style = VERIFICATION_PER_FILE; i->checksum_job->on_not_found = pull_job_restart_with_sha256sum;
r = pull_job_begin(i->checksum_job); r = pull_job_begin(i->checksum_job);
if (r < 0) if (r < 0)
@ -555,5 +551,11 @@ int tar_pull_start(
return r; return r;
} }
if (i->settings_job) {
r = pull_job_begin(i->settings_job);
if (r < 0)
return r;
}
return 0; return 0;
} }

View File

@ -5,6 +5,7 @@
#include "import-util.h" #include "import-util.h"
#include "macro.h" #include "macro.h"
#include "pull-common.h"
typedef struct TarPull TarPull; typedef struct TarPull TarPull;
@ -15,4 +16,4 @@ TarPull* tar_pull_unref(TarPull *pull);
DEFINE_TRIVIAL_CLEANUP_FUNC(TarPull*, tar_pull_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(TarPull*, tar_pull_unref);
int tar_pull_start(TarPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings); int tar_pull_start(TarPull *pull, const char *url, const char *local, PullFlags flags, ImportVerify verify);

View File

@ -19,11 +19,9 @@
#include "verbs.h" #include "verbs.h"
#include "web-util.h" #include "web-util.h"
static bool arg_force = false;
static const char *arg_image_root = "/var/lib/machines"; static const char *arg_image_root = "/var/lib/machines";
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
static bool arg_settings = true; static PullFlags arg_pull_flags = PULL_SETTINGS | PULL_ROOTHASH | PULL_ROOTHASH_SIGNATURE | PULL_VERITY;
static bool arg_roothash = true;
static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
log_notice("Transfer aborted."); log_notice("Transfer aborted.");
@ -77,8 +75,8 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
"Local image name '%s' is not valid.", "Local image name '%s' is not valid.",
local); local);
if (!arg_force) { if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) {
r = image_find(IMAGE_MACHINE, local, NULL); r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) { if (r < 0) {
if (r != -ENOENT) if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
@ -105,7 +103,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to allocate puller: %m"); return log_error_errno(r, "Failed to allocate puller: %m");
r = tar_pull_start(pull, url, local, arg_force, arg_verify, arg_settings); r = tar_pull_start(pull, url, local, arg_pull_flags & PULL_FLAGS_MASK_TAR, arg_verify);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to pull image: %m"); return log_error_errno(r, "Failed to pull image: %m");
@ -163,8 +161,8 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
"Local image name '%s' is not valid.", "Local image name '%s' is not valid.",
local); local);
if (!arg_force) { if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) {
r = image_find(IMAGE_MACHINE, local, NULL); r = image_find(IMAGE_MACHINE, local, NULL, NULL);
if (r < 0) { if (r < 0) {
if (r != -ENOENT) if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
@ -191,7 +189,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to allocate puller: %m"); return log_error_errno(r, "Failed to allocate puller: %m");
r = raw_pull_start(pull, url, local, arg_force, arg_verify, arg_settings, arg_roothash); r = raw_pull_start(pull, url, local, arg_pull_flags & PULL_FLAGS_MASK_RAW, arg_verify);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to pull image: %m"); return log_error_errno(r, "Failed to pull image: %m");
@ -214,6 +212,8 @@ static int help(int argc, char *argv[], void *userdata) {
" 'checksum', 'signature'\n" " 'checksum', 'signature'\n"
" --settings=BOOL Download settings file with image\n" " --settings=BOOL Download settings file with image\n"
" --roothash=BOOL Download root hash file with image\n" " --roothash=BOOL Download root hash file with image\n"
" --roothash-sigature=BOOL Download root hash signature file with image\n"
" --verity=BOOL Download verity file with image\n"
" --image-root=PATH Image root directory\n\n" " --image-root=PATH Image root directory\n\n"
"Commands:\n" "Commands:\n"
" tar URL [NAME] Download a TAR image\n" " tar URL [NAME] Download a TAR image\n"
@ -232,16 +232,20 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VERIFY, ARG_VERIFY,
ARG_SETTINGS, ARG_SETTINGS,
ARG_ROOTHASH, ARG_ROOTHASH,
ARG_ROOTHASH_SIGNATURE,
ARG_VERITY,
}; };
static const struct option options[] = { static const struct option options[] = {
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION }, { "version", no_argument, NULL, ARG_VERSION },
{ "force", no_argument, NULL, ARG_FORCE }, { "force", no_argument, NULL, ARG_FORCE },
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, { "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
{ "verify", required_argument, NULL, ARG_VERIFY }, { "verify", required_argument, NULL, ARG_VERIFY },
{ "settings", required_argument, NULL, ARG_SETTINGS }, { "settings", required_argument, NULL, ARG_SETTINGS },
{ "roothash", required_argument, NULL, ARG_ROOTHASH }, { "roothash", required_argument, NULL, ARG_ROOTHASH },
{ "roothash-signature", required_argument, NULL, ARG_ROOTHASH_SIGNATURE },
{ "verity", required_argument, NULL, ARG_VERITY },
{} {}
}; };
@ -261,7 +265,7 @@ static int parse_argv(int argc, char *argv[]) {
return version(); return version();
case ARG_FORCE: case ARG_FORCE:
arg_force = true; arg_pull_flags |= PULL_FORCE;
break; break;
case ARG_IMAGE_ROOT: case ARG_IMAGE_ROOT:
@ -281,7 +285,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to parse --settings= parameter '%s': %m", optarg); return log_error_errno(r, "Failed to parse --settings= parameter '%s': %m", optarg);
arg_settings = r; SET_FLAG(arg_pull_flags, PULL_SETTINGS, r);
break; break;
case ARG_ROOTHASH: case ARG_ROOTHASH:
@ -289,7 +293,27 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to parse --roothash= parameter '%s': %m", optarg); return log_error_errno(r, "Failed to parse --roothash= parameter '%s': %m", optarg);
arg_roothash = r; SET_FLAG(arg_pull_flags, PULL_ROOTHASH, r);
/* If we were asked to turn off the root hash, implicitly also turn off the root hash signature */
if (!r)
SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, false);
break;
case ARG_ROOTHASH_SIGNATURE:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse --roothash-signature= parameter '%s': %m", optarg);
SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, r);
break;
case ARG_VERITY:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse --verity= parameter '%s': %m", optarg);
SET_FLAG(arg_pull_flags, PULL_VERITY, r);
break; break;
case '?': case '?':

View File

@ -408,7 +408,7 @@ static int image_object_find(sd_bus *bus, const char *path, const char *interfac
if (r < 0) if (r < 0)
return r; return r;
r = image_find(IMAGE_MACHINE, e, &image); r = image_find(IMAGE_MACHINE, e, NULL, &image);
if (r == -ENOENT) if (r == -ENOENT)
return 0; return 0;
if (r < 0) if (r < 0)
@ -452,7 +452,7 @@ static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata,
if (!images) if (!images)
return -ENOMEM; return -ENOMEM;
r = image_discover(IMAGE_MACHINE, images); r = image_discover(IMAGE_MACHINE, NULL, images);
if (r < 0) if (r < 0)
return r; 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) if (r < 0)
return r; return r;
r = image_find(IMAGE_MACHINE, name, NULL); r = image_find(IMAGE_MACHINE, name, NULL, NULL);
if (r == -ENOENT) if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0) if (r < 0)
@ -480,7 +480,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
if (!images) if (!images)
return -ENOMEM; return -ENOMEM;
r = image_discover(IMAGE_MACHINE, images); r = image_discover(IMAGE_MACHINE, NULL, images);
if (r < 0) if (r < 0)
return r; 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)) if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", 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) if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0) if (r < 0)
@ -755,7 +755,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
goto child_fail; goto child_fail;
} }
r = image_discover(IMAGE_MACHINE, images); r = image_discover(IMAGE_MACHINE, NULL, images);
if (r < 0) if (r < 0)
goto child_fail; goto child_fail;

View File

@ -6,6 +6,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
#include "conf-parser.h" #include "conf-parser.h"
#include "networkd-conf.h" #include "networkd-conf.h"
#include "networkd-manager.h" #include "networkd-manager.h"
#include "networkd-route.h"
%} %}
struct ConfigPerfItem; struct ConfigPerfItem;
%null_strings %null_strings
@ -21,5 +22,6 @@ struct ConfigPerfItem;
Network.SpeedMeter, config_parse_bool, 0, offsetof(Manager, use_speed_meter) Network.SpeedMeter, config_parse_bool, 0, offsetof(Manager, use_speed_meter)
Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec) Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec)
Network.ManageForeignRoutes, config_parse_bool, 0, offsetof(Manager, manage_foreign_routes) Network.ManageForeignRoutes, config_parse_bool, 0, offsetof(Manager, manage_foreign_routes)
Network.RouteTable, config_parse_route_table_names, 0, offsetof(Manager, route_tables)
DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid) DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid)
DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid) DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid)

View File

@ -875,6 +875,8 @@ void manager_free(Manager *m) {
ordered_set_free_free(m->address_pools); ordered_set_free_free(m->address_pools);
m->route_tables = hashmap_free_free_key(m->route_tables);
/* routing_policy_rule_free() access m->rules and m->rules_foreign. /* routing_policy_rule_free() access m->rules and m->rules_foreign.
* So, it is necessary to set NULL after the sets are freed. */ * So, it is necessary to set NULL after the sets are freed. */
m->rules = set_free(m->rules); m->rules = set_free(m->rules);

View File

@ -65,6 +65,9 @@ struct Manager {
Set *routes; Set *routes;
Set *routes_foreign; Set *routes_foreign;
/* Route table name */
Hashmap *route_tables;
/* For link speed meter*/ /* For link speed meter*/
bool use_speed_meter; bool use_speed_meter;
sd_event_source *speed_meter_event_source; sd_event_source *speed_meter_event_source;

View File

@ -87,7 +87,7 @@ static const char * const route_table_table[] = {
[RT_TABLE_LOCAL] = "local", [RT_TABLE_LOCAL] = "local",
}; };
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int); DEFINE_STRING_TABLE_LOOKUP(route_table, int);
#define ROUTE_TABLE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("default") + 1) #define ROUTE_TABLE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("default") + 1)
static const char *format_route_table(int table, char *buf, size_t size) { static const char *format_route_table(int table, char *buf, size_t size) {
@ -1868,6 +1868,28 @@ int config_parse_route_scope(
return 0; return 0;
} }
int route_table_from_string_full(Manager *m, const char *s, uint32_t *ret) {
int r;
assert(s);
assert(m);
assert(ret);
r = route_table_from_string(s);
if (r >= 0) {
*ret = (uint32_t) r;
return 0;
}
uint32_t t = PTR_TO_UINT32(hashmap_get(m->route_tables, s));
if (t != 0) {
*ret = t;
return 0;
}
return safe_atou32(s, ret);
}
int config_parse_route_table( int config_parse_route_table(
const char *unit, const char *unit,
const char *filename, const char *filename,
@ -1899,16 +1921,11 @@ int config_parse_route_table(
return 0; return 0;
} }
r = route_table_from_string(rvalue); r = route_table_from_string_full(network->manager, rvalue, &n->table);
if (r >= 0) if (r < 0) {
n->table = r; log_syntax(unit, LOG_WARNING, filename, line, r,
else { "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue);
r = safe_atou32(rvalue, &n->table); return 0;
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Could not parse route table number \"%s\", ignoring assignment: %m", rvalue);
return 0;
}
} }
n->table_set = true; n->table_set = true;
@ -2356,6 +2373,77 @@ int config_parse_multipath_route(
return 0; return 0;
} }
int config_parse_route_table_names(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *name = NULL;
Hashmap **s = data;
uint32_t table;
const char *p;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
*s = hashmap_free_free_key(*s);
return 0;
}
p = rvalue;
r = extract_first_word(&p, &name, ":", 0);
if (r == -ENOMEM)
return log_oom();
if (r <= 0 || isempty(p)) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Invalid RouteTable=, ignoring assignment: %s", rvalue);
return 0;
}
if (STR_IN_SET(name, "default", "main","local")) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Route table name %s already preconfigured. Ignoring assignment: %s", name, rvalue);
return 0;
}
r = safe_atou32(p, &table);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse RouteTable=, ignoring assignment: %s", p);
return 0;
}
if (table == 0) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid RouteTable=, ignoring assignment: %s", p);
return 0;
}
r = hashmap_ensure_put(s, &string_hash_ops, name, UINT32_TO_PTR(table));
if (r == -ENOMEM)
return log_oom();
if (r == -EEXIST) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Specified RouteTable= name and value pair conflicts with others, ignoring assignment: %s", rvalue);
return 0;
}
if (r > 0)
TAKE_PTR(name);
return 0;
}
static int route_section_verify(Route *route, Network *network) { static int route_section_verify(Route *route, Network *network) {
if (section_is_invalid(route->section)) if (section_is_invalid(route->section))
return -EINVAL; return -EINVAL;

View File

@ -86,6 +86,11 @@ int network_add_ipv4ll_route(Network *network);
int network_add_default_route_on_device(Network *network); int network_add_default_route_on_device(Network *network);
void network_drop_invalid_routes(Network *network); void network_drop_invalid_routes(Network *network);
int route_table_from_string_full(Manager *m, const char *table, uint32_t *ret);
const char *route_table_to_string(int d) _const_;
int route_table_from_string(const char *d) _pure_;
CONFIG_PARSER_PROTOTYPE(config_parse_gateway); CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src); CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src);
CONFIG_PARSER_PROTOTYPE(config_parse_destination); CONFIG_PARSER_PROTOTYPE(config_parse_destination);
@ -100,3 +105,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window);
CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu); CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu);
CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss); CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss);
CONFIG_PARSER_PROTOTYPE(config_parse_route_table_names);

View File

@ -8,6 +8,7 @@
#include "conf-parser.h" #include "conf-parser.h"
#include "fileio.h" #include "fileio.h"
#include "format-util.h" #include "format-util.h"
#include "hashmap.h"
#include "ip-protocol-list.h" #include "ip-protocol-list.h"
#include "netlink-util.h" #include "netlink-util.h"
#include "networkd-manager.h" #include "networkd-manager.h"
@ -1129,9 +1130,10 @@ int config_parse_routing_policy_rule_table(
if (r < 0) if (r < 0)
return log_oom(); return log_oom();
r = safe_atou32(rvalue, &n->table); r = route_table_from_string_full(network->manager, rvalue, &n->table);
if (r < 0) { if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule table, ignoring: %s", rvalue); log_syntax(unit, LOG_WARNING, filename, line, r,
"Could not parse RPDB rule route table number \"%s\", ignoring assignment: %m", rvalue);
return 0; return 0;
} }

View File

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

View File

@ -495,7 +495,7 @@ int portable_extract(
assert(name_or_path); 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) if (r < 0)
return r; 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 /* 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. */ * 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; return 0;
r = image_symlink(image_path, flags, &sl); r = image_symlink(image_path, flags, &sl);
@ -987,7 +987,7 @@ int portable_attach(
assert(name_or_path); 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) if (r < 0)
return r; 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); return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name);
if (path_is_absolute(marker) && 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)); r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(marker));
if (r < 0) if (r < 0)

View File

@ -606,7 +606,7 @@ int bus_image_acquire(
if (image_name_is_valid(name_or_path)) { if (image_name_is_valid(name_or_path)) {
/* If it's a short name, let's search for it */ /* 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) if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path); 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 /* A wrapper around image_discover() (for finding images in search path) and portable_discover_attached() (for
* finding attached images). */ * finding attached images). */
r = image_discover(IMAGE_PORTABLE, images); r = image_discover(IMAGE_PORTABLE, NULL, images);
if (r < 0) if (r < 0)
return r; return r;

View File

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

View File

@ -16,6 +16,7 @@
typedef enum ImageClass { typedef enum ImageClass {
IMAGE_MACHINE, IMAGE_MACHINE,
IMAGE_PORTABLE, IMAGE_PORTABLE,
IMAGE_EXTENSION,
_IMAGE_CLASS_MAX, _IMAGE_CLASS_MAX,
_IMAGE_CLASS_INVALID = -1 _IMAGE_CLASS_INVALID = -1
} ImageClass; } ImageClass;
@ -61,10 +62,10 @@ Image *image_ref(Image *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref); 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_from_path(const char *path, Image **ret);
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret); int image_find_harder(ImageClass class, const char *root, const char *name_or_path, Image **ret);
int image_discover(ImageClass class, Hashmap *map); int image_discover(ImageClass class, const char *root, Hashmap *map);
int image_remove(Image *i); int image_remove(Image *i);
int image_rename(Image *i, const char *new_name); 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); 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) { static inline bool IMAGE_IS_HIDDEN(const struct Image *i) {
assert(i); assert(i);

View File

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

View File

@ -5,9 +5,19 @@
int path_is_os_tree(const char *path); int path_is_os_tree(const char *path);
int open_os_release(const char *root, char **ret_path, int *ret_fd); /* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file); * 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 parse_os_release(const char *root, ...) _sentinel_;
int load_os_release_pairs(const char *root, 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); 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

@ -127,6 +127,12 @@ int import_environment(int argc, char *argv[], void *userdata) {
strv_env_clean_with_callback(copy, invalid_callback, NULL); strv_env_clean_with_callback(copy, invalid_callback, NULL);
char **e;
STRV_FOREACH(e, copy)
if (string_has_cc(*e, NULL))
log_notice("Environment variable $%.*s contains control characters, importing anyway.",
(int) strcspn(*e, "="), *e);
r = sd_bus_message_append_strv(m, copy); r = sd_bus_message_append_strv(m, copy);
} else { } else {
@ -139,21 +145,30 @@ int import_environment(int argc, char *argv[], void *userdata) {
STRV_FOREACH(a, strv_skip(argv, 1)) { STRV_FOREACH(a, strv_skip(argv, 1)) {
if (!env_name_is_valid(*a)) if (!env_name_is_valid(*a))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid environment variable name: %s", *a); return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Not a valid environment variable name: %s", *a);
bool found = false;
STRV_FOREACH(b, environ) { STRV_FOREACH(b, environ) {
const char *eq; const char *eq;
eq = startswith(*b, *a); eq = startswith(*b, *a);
if (eq && *eq == '=') { if (eq && *eq == '=') {
if (string_has_cc(eq + 1, NULL))
log_notice("Environment variable $%.*s contains control characters, importing anyway.",
(int) (eq - *b), *b);
r = sd_bus_message_append(m, "s", *b); r = sd_bus_message_append(m, "s", *b);
if (r < 0) if (r < 0)
return bus_log_create_error(r); return bus_log_create_error(r);
found = true;
break; break;
} }
} }
if (!found)
log_notice("Environment variable $%s not set, ignoring.", *a);
} }
r = sd_bus_message_close_container(m); r = sd_bus_message_close_container(m);

View File

@ -265,6 +265,7 @@ static void test_env_clean(void) {
"another=one", "another=one",
"another=final one", "another=final one",
"CRLF=\r\n", "CRLF=\r\n",
"LESS_TERMCAP_mb=\x1b[01;31m",
"BASH_FUNC_foo%%=() { echo foo\n}"); "BASH_FUNC_foo%%=() { echo foo\n}");
assert_se(e); assert_se(e);
assert_se(!strv_env_is_valid(e)); assert_se(!strv_env_is_valid(e));
@ -277,7 +278,9 @@ static void test_env_clean(void) {
assert_se(streq(e[3], "abcd=äöüß")); assert_se(streq(e[3], "abcd=äöüß"));
assert_se(streq(e[4], "xyz=xyz\n")); assert_se(streq(e[4], "xyz=xyz\n"));
assert_se(streq(e[5], "another=final one")); assert_se(streq(e[5], "another=final one"));
assert_se(e[6] == NULL); assert_se(streq(e[6], "CRLF=\r\n"));
assert_se(streq(e[7], "LESS_TERMCAP_mb=\x1b[01;31m"));
assert_se(e[8] == NULL);
} }
static void test_env_name_is_valid(void) { static void test_env_name_is_valid(void) {
@ -302,8 +305,11 @@ static void test_env_value_is_valid(void) {
assert_se(env_value_is_valid("printf \"\\x1b]0;<mock-chroot>\\x07<mock-chroot>\"")); assert_se(env_value_is_valid("printf \"\\x1b]0;<mock-chroot>\\x07<mock-chroot>\""));
assert_se(env_value_is_valid("tab\tcharacter")); assert_se(env_value_is_valid("tab\tcharacter"));
assert_se(env_value_is_valid("new\nline")); assert_se(env_value_is_valid("new\nline"));
assert_se(!env_value_is_valid("Show this?\rNope. Show that!")); assert_se(env_value_is_valid("Show this?\rNope. Show that!"));
assert_se(!env_value_is_valid("new DOS\r\nline")); assert_se(env_value_is_valid("new DOS\r\nline"));
assert_se(!env_value_is_valid("\xc5")); /* A truncated utf-8-encoded "ł".
* We currently disallow that. */
} }
static void test_env_assignment_is_valid(void) { static void test_env_assignment_is_valid(void) {

View File

@ -933,7 +933,7 @@ install_execs() {
# some {rc,halt}.local scripts and programs are okay to not exist, the rest should # 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 # 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 # 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" ] inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "${i##*/}" == "plymouth" ]
done done
) )

View File

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