mirror of
https://github.com/systemd/systemd
synced 2025-09-28 16:24:45 +02:00
Compare commits
32 Commits
7b5ed18779
...
a631cbfae3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a631cbfae3 | ||
![]() |
f6c9a7ab93 | ||
![]() |
ac71ece3c6 | ||
![]() |
133b34f69a | ||
![]() |
6792cbbcf8 | ||
![]() |
8dc0291c0d | ||
![]() |
273cb07d1b | ||
![]() |
8bc3f0b89f | ||
![]() |
f14717a7e2 | ||
![]() |
c20307fd34 | ||
![]() |
c6cb8daf72 | ||
![]() |
7d41de2e94 | ||
![]() |
63ec26a4bf | ||
![]() |
c038ce4606 | ||
![]() |
656e5aa452 | ||
![]() |
a2804e3cd8 | ||
![]() |
bf108eb942 | ||
![]() |
e9155cd077 | ||
![]() |
30927a2484 | ||
![]() |
c4899ea427 | ||
![]() |
71ad75f306 | ||
![]() |
a1fd722b5d | ||
![]() |
b5e0c17654 | ||
![]() |
dfbbb4f7b0 | ||
![]() |
36b95d0440 | ||
![]() |
60bb6caaae | ||
![]() |
6ddd051193 | ||
![]() |
44bb7b0956 | ||
![]() |
d577d4a432 | ||
![]() |
7a87fb6119 | ||
![]() |
205e5bcc1c | ||
![]() |
9bca4ae4cd |
7
TODO
7
TODO
@ -45,6 +45,13 @@ Features:
|
||||
in a graceful way, so that updated /usr trees automatically propagate into
|
||||
updated boot loaders on reboot.
|
||||
|
||||
* sysext: optionally, if the merged trees allow it use bind mounts instead of
|
||||
overlayfs
|
||||
|
||||
* nspawn: add support for sysext extensions, too. i.e. a new --extension=
|
||||
switch that takes one or more arguments, and applies the extensions already
|
||||
during startup.
|
||||
|
||||
* add "systemd-analyze debug" + AttachDebugger= in unit files: The former
|
||||
specifies a command to execute; the latter specifies that an already running
|
||||
"systemd-analyze debug" instance shall be contacted and execution paused
|
||||
|
@ -267,3 +267,15 @@ systemd-firstboot and localectl:
|
||||
* `SYSTEMD_LIST_NON_UTF8_LOCALES=1` – if set non-UTF-8 locales are listed among
|
||||
the installed ones. By default non-UTF-8 locales are suppressed from the
|
||||
selection, since we are living in the 21st century.
|
||||
|
||||
systemd-sysext:
|
||||
|
||||
* `SYSTEMD_SYSEXT_HIERARCHIES` – if set to a colon-separated list of absolute
|
||||
paths this variable may be used to override which hierarchies to manage with
|
||||
`systemd-sysext`. By default only `/usr/` and `/opt/` are managed. With this
|
||||
environment variable this list may be changed, in order to add or remove
|
||||
directories from this list. This should only reference "real" file systems
|
||||
and directories that only contain "real" file systems as submounts — do not
|
||||
specify API file systems such as `/proc/` or `/sys/` here, or hierarchies
|
||||
that have them as submounts. In particular, do not specify the root directory
|
||||
`/` here.
|
||||
|
@ -70,6 +70,14 @@
|
||||
is false. Defaults to yes.</para></listitem>
|
||||
</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>
|
||||
</refsect1>
|
||||
|
||||
|
@ -317,6 +317,17 @@
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SYSEXT_LEVEL=</varname></term>
|
||||
|
||||
<listitem><para>A lower-case string (mostly numeric, no spaces or other characters outside of 0–9,
|
||||
a–z, ".", "_" and "-") identifying the operating system extensions support level, to indicate which
|
||||
extension images are supported (See:
|
||||
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>).
|
||||
Example: <literal>SYSEXT_LEVEL=2</literal> or
|
||||
<literal>SYSEXT_LEVEL=15.14</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
<para>If you are reading this file from C code or a shell script
|
||||
|
@ -954,6 +954,7 @@ manpages = [
|
||||
'systemd-suspend-then-hibernate.service'],
|
||||
''],
|
||||
['systemd-sysctl.service', '8', ['systemd-sysctl'], ''],
|
||||
['systemd-sysext', '8', ['systemd-sysext.service'], ''],
|
||||
['systemd-system-update-generator', '8', [], ''],
|
||||
['systemd-system.conf',
|
||||
'5',
|
||||
|
@ -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
|
||||
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
|
||||
are allowed, but non-printable characters are currently rejected. The total length of the environment
|
||||
block is limited to <constant>_SC_ARG_MAX</constant> value defined by
|
||||
are allowed, but the whole sequence must be valid UTF-8. (Note that control characters like newline
|
||||
(<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>.
|
||||
</para>
|
||||
|
||||
|
239
man/systemd-sysext.xml
Normal file
239
man/systemd-sysext.xml
Normal 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 && 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>
|
@ -1200,8 +1200,9 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
|
||||
<varlistentry>
|
||||
<term><varname>Table=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the routing table identifier to lookup if the rule selector matches. Takes
|
||||
one of <literal>default</literal>, <literal>main</literal>, and <literal>local</literal>,
|
||||
<para>Specifies the routing table identifier to lookup if the rule selector matches. Takes one of predefined names
|
||||
<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>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@ -1409,11 +1410,11 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
|
||||
<varlistentry>
|
||||
<term><varname>Table=</varname></term>
|
||||
<listitem>
|
||||
<para>The table identifier for the route. Takes <literal>default</literal>,
|
||||
<literal>main</literal>, <literal>local</literal> or a number between 1 and 4294967295.
|
||||
The table can be retrieved using <command>ip route show table <replaceable>num</replaceable></command>.
|
||||
If unset and <varname>Type=</varname> is <literal>local</literal>, <literal>broadcast</literal>,
|
||||
<literal>anycast</literal>, or <literal>nat</literal>, then <literal>local</literal> is used.
|
||||
<para>The table identifier for the route. Takes one of predefined names <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. The table can be retrieved using
|
||||
<command>ip route show table <replaceable>num</replaceable></command>. If unset and <varname>Type=</varname> is <literal>local</literal>,
|
||||
<literal>broadcast</literal>, <literal>anycast</literal>, or <literal>nat</literal>, then <literal>local</literal> is used.
|
||||
In other cases, defaults to <literal>main</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
17
meson.build
17
meson.build
@ -1502,6 +1502,7 @@ foreach term : ['analyze',
|
||||
'nss-myhostname',
|
||||
'nss-systemd',
|
||||
'portabled',
|
||||
'sysext',
|
||||
'pstore',
|
||||
'quotacheck',
|
||||
'randomseed',
|
||||
@ -1745,6 +1746,7 @@ subdir('src/portable')
|
||||
subdir('src/pstore')
|
||||
subdir('src/resolve')
|
||||
subdir('src/shutdown')
|
||||
subdir('src/sysext')
|
||||
subdir('src/systemctl')
|
||||
subdir('src/timedate')
|
||||
subdir('src/timesync')
|
||||
@ -2202,6 +2204,17 @@ if conf.get('ENABLE_PORTABLED') == 1
|
||||
install_dir : rootbindir)
|
||||
endif
|
||||
|
||||
if conf.get('ENABLE_SYSEXT') == 1
|
||||
public_programs += executable(
|
||||
'systemd-sysext',
|
||||
systemd_sysext_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
endif
|
||||
|
||||
if conf.get('ENABLE_USERDB') == 1
|
||||
executable(
|
||||
'systemd-userwork',
|
||||
@ -2390,8 +2403,7 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
|
||||
libopenssl,
|
||||
libp11kit],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : bindir)
|
||||
install : true)
|
||||
endif
|
||||
|
||||
if conf.get('HAVE_SYSV_COMPAT') == 1
|
||||
@ -3735,6 +3747,7 @@ foreach tuple : [
|
||||
['logind'],
|
||||
['machined'],
|
||||
['portabled'],
|
||||
['sysext'],
|
||||
['userdb'],
|
||||
['homed'],
|
||||
['importd'],
|
||||
|
@ -111,6 +111,8 @@ option('machined', type : 'boolean',
|
||||
description : 'install the systemd-machined stack')
|
||||
option('portabled', type : 'boolean',
|
||||
description : 'install the systemd-portabled stack')
|
||||
option('sysext', type : 'boolean',
|
||||
description : 'install the systemd-sysext stack')
|
||||
option('userdb', type : 'boolean',
|
||||
description : 'install the systemd-userdbd stack')
|
||||
option('homed', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
|
@ -57,16 +57,13 @@ bool env_value_is_valid(const char *e) {
|
||||
if (!utf8_is_valid(e))
|
||||
return false;
|
||||
|
||||
/* bash allows tabs and newlines in environment variables, and so
|
||||
* should we */
|
||||
if (string_has_cc(e, "\t\n"))
|
||||
return false;
|
||||
/* Note that variable *values* may contain control characters, in particular NL, TAB, BS, DEL, ESC…
|
||||
* When printing those variables with show-environment, we'll escape them. Make sure to print
|
||||
* environment variables carefully! */
|
||||
|
||||
/* POSIX says the overall size of the environment block cannot
|
||||
* be > ARG_MAX, an individual assignment hence cannot be
|
||||
* either. Discounting the shortest possible variable name of
|
||||
* length 1, the equal sign and trailing NUL this hence leaves
|
||||
* ARG_MAX-3 as longest possible variable value. */
|
||||
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, an individual assignment
|
||||
* hence cannot be either. Discounting the shortest possible variable name of 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)
|
||||
return false;
|
||||
|
||||
@ -86,10 +83,8 @@ bool env_assignment_is_valid(const char *e) {
|
||||
if (!env_value_is_valid(eq + 1))
|
||||
return false;
|
||||
|
||||
/* POSIX says the overall size of the environment block cannot
|
||||
* be > ARG_MAX, hence the individual variable assignments
|
||||
* cannot be either, but let's leave room for one trailing NUL
|
||||
* byte. */
|
||||
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, hence the individual
|
||||
* variable assignments cannot be either, but let's leave room for one trailing NUL byte. */
|
||||
if (strlen(e) > sc_arg_max() - 1)
|
||||
return false;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
/* 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;
|
||||
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
(void) rmdir(p);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
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);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
|
||||
|
||||
|
@ -18,17 +18,27 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
|
||||
int rm_rf(const char *path, RemoveFlags flags);
|
||||
|
||||
/* 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;
|
||||
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_physical_and_free);
|
||||
|
||||
/* 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;
|
||||
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rm_rf_subvolume_and_free);
|
||||
|
@ -3183,7 +3183,9 @@ static int apply_mount_namespace(
|
||||
if (context->mount_flags == MS_SHARED)
|
||||
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);
|
||||
if (!creds_path) {
|
||||
r = -ENOMEM;
|
||||
|
@ -142,6 +142,20 @@ int identity_add_token_pin(JsonVariant **v, const char *pin) {
|
||||
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) {
|
||||
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = 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);
|
||||
|
||||
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)
|
||||
return r;
|
||||
|
||||
|
@ -231,7 +231,8 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) {
|
||||
if (!c)
|
||||
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)
|
||||
return -EIO;
|
||||
|
@ -66,7 +66,7 @@ static int export_tar(int argc, char *argv[], void *userdata) {
|
||||
int r, fd;
|
||||
|
||||
if (hostname_is_valid(argv[1], 0)) {
|
||||
r = image_find(IMAGE_MACHINE, argv[1], &image);
|
||||
r = image_find(IMAGE_MACHINE, argv[1], NULL, &image);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "Machine image %s not found.", argv[1]);
|
||||
if (r < 0)
|
||||
@ -142,7 +142,7 @@ static int export_raw(int argc, char *argv[], void *userdata) {
|
||||
int r, fd;
|
||||
|
||||
if (hostname_is_valid(argv[1], 0)) {
|
||||
r = image_find(IMAGE_MACHINE, argv[1], &image);
|
||||
r = image_find(IMAGE_MACHINE, argv[1], NULL, &image);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "Machine image %s not found.", argv[1]);
|
||||
if (r < 0)
|
||||
|
@ -132,7 +132,7 @@ static int import_fs(int argc, char *argv[], void *userdata) {
|
||||
local);
|
||||
|
||||
if (!arg_force) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL);
|
||||
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
|
||||
|
@ -70,7 +70,7 @@ static int import_tar(int argc, char *argv[], void *userdata) {
|
||||
local);
|
||||
|
||||
if (!arg_force) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL);
|
||||
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
|
||||
@ -165,7 +165,7 @@ static int import_raw(int argc, char *argv[], void *userdata) {
|
||||
local);
|
||||
|
||||
if (!arg_force) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL);
|
||||
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
|
||||
|
@ -110,7 +110,7 @@ int pull_find_old_etags(
|
||||
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;
|
||||
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);
|
||||
|
||||
if (force_local)
|
||||
if (FLAGS_SET(flags, PULL_FORCE))
|
||||
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||
|
||||
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;
|
||||
int r;
|
||||
const char *chksums = NULL;
|
||||
|
||||
assert(ret_checksum_job);
|
||||
assert(ret_signature_job);
|
||||
@ -266,6 +265,7 @@ int pull_make_verification_jobs(
|
||||
|
||||
if (verify != IMPORT_VERIFY_NO) {
|
||||
_cleanup_free_ char *checksum_url = NULL, *fn = NULL;
|
||||
const char *chksums = NULL;
|
||||
|
||||
/* Queue jobs for the checksum file for the image. */
|
||||
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;
|
||||
}
|
||||
|
||||
*ret_checksum_job = checksum_job;
|
||||
*ret_signature_job = signature_job;
|
||||
|
||||
checksum_job = signature_job = NULL;
|
||||
*ret_checksum_job = TAKE_PTR(checksum_job);
|
||||
*ret_signature_job = TAKE_PTR(signature_job);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -365,70 +363,35 @@ static int verify_one(PullJob *checksum_job, PullJob *job) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pull_verify(PullJob *main_job,
|
||||
PullJob *roothash_job,
|
||||
PullJob *settings_job,
|
||||
PullJob *checksum_job,
|
||||
PullJob *signature_job) {
|
||||
static int verify_gpg(
|
||||
const void *payload, size_t payload_size,
|
||||
const void *signature, size_t signature_size) {
|
||||
|
||||
_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";
|
||||
_cleanup_(sigkill_waitp) pid_t pid = 0;
|
||||
bool gpg_home_created = false;
|
||||
int r;
|
||||
|
||||
assert(main_job);
|
||||
assert(main_job->state == PULL_JOB_DONE);
|
||||
|
||||
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.");
|
||||
assert(payload || payload_size == 0);
|
||||
assert(signature || signature_size == 0);
|
||||
|
||||
r = pipe2(gpg_pipe, O_CLOEXEC);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to create pipe for gpg: %m");
|
||||
|
||||
sig_file = mkostemp(sig_file_path, O_RDWR);
|
||||
if (sig_file < 0)
|
||||
return log_error_errno(errno, "Failed to create temporary file: %m");
|
||||
if (signature_size > 0) {
|
||||
_cleanup_close_ int sig_file = -1;
|
||||
|
||||
r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to write to temporary file: %m");
|
||||
goto finish;
|
||||
sig_file = mkostemp(sig_file_path, O_RDWR);
|
||||
if (sig_file < 0)
|
||||
return log_error_errno(errno, "Failed to create temporary file: %m");
|
||||
|
||||
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)) {
|
||||
@ -457,7 +420,7 @@ int pull_verify(PullJob *main_job,
|
||||
NULL, /* dash */
|
||||
NULL /* trailing NULL */
|
||||
};
|
||||
unsigned k = ELEMENTSOF(cmd) - 6;
|
||||
size_t k = ELEMENTSOF(cmd) - 6;
|
||||
|
||||
/* Child */
|
||||
|
||||
@ -473,8 +436,7 @@ int pull_verify(PullJob *main_job,
|
||||
|
||||
cmd[k++] = strjoina("--homedir=", gpg_home);
|
||||
|
||||
/* We add the user keyring only to the command line
|
||||
* arguments, if it's around since gpg fails
|
||||
/* We add the user keyring only to the command line arguments, if it's around since gpg fails
|
||||
* otherwise. */
|
||||
if (access(USER_KEYRING_PATH, F_OK) >= 0)
|
||||
cmd[k++] = "--keyring=" USER_KEYRING_PATH;
|
||||
@ -482,7 +444,7 @@ int pull_verify(PullJob *main_job,
|
||||
cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
|
||||
|
||||
cmd[k++] = "--verify";
|
||||
if (checksum_job->style == VERIFICATION_PER_DIRECTORY) {
|
||||
if (signature) {
|
||||
cmd[k++] = sig_file_path;
|
||||
cmd[k++] = "-";
|
||||
cmd[k++] = NULL;
|
||||
@ -496,7 +458,7 @@ int pull_verify(PullJob *main_job,
|
||||
|
||||
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) {
|
||||
log_error_errno(r, "Failed to write to pipe: %m");
|
||||
goto finish;
|
||||
@ -517,10 +479,120 @@ int pull_verify(PullJob *main_job,
|
||||
}
|
||||
|
||||
finish:
|
||||
(void) unlink(sig_file_path);
|
||||
if (signature_size > 0)
|
||||
(void) unlink(sig_file_path);
|
||||
|
||||
if (gpg_home_created)
|
||||
(void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -6,7 +6,19 @@
|
||||
#include "import-util.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);
|
||||
|
||||
@ -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_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);
|
||||
|
@ -61,22 +61,41 @@ static void pull_job_finish(PullJob *j, int ret) {
|
||||
j->on_finished(j);
|
||||
}
|
||||
|
||||
static int pull_job_restart(PullJob *j) {
|
||||
static int pull_job_restart(PullJob *j, const char *new_url) {
|
||||
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)
|
||||
return r;
|
||||
|
||||
free(j->url);
|
||||
j->url = chksum_url;
|
||||
j->state = PULL_JOB_INIT;
|
||||
j->error = 0;
|
||||
j->payload = mfree(j->payload);
|
||||
j->payload_size = 0;
|
||||
j->payload_allocated = 0;
|
||||
j->written_compressed = 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);
|
||||
if (r < 0)
|
||||
@ -114,23 +133,31 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
|
||||
r = 0;
|
||||
goto finish;
|
||||
} else if (status >= 300) {
|
||||
if (status == 404 && j->style == VERIFICATION_PER_FILE) {
|
||||
|
||||
/* retry pull job with SHA256SUMS file */
|
||||
r = pull_job_restart(j);
|
||||
if (status == 404 && j->on_not_found) {
|
||||
_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)
|
||||
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 (r > 0) { /* A new url to use */
|
||||
assert(new_url);
|
||||
|
||||
if (status == 0) {
|
||||
j->style = VERIFICATION_PER_DIRECTORY;
|
||||
return;
|
||||
r = pull_job_restart(j, new_url);
|
||||
if (r < 0)
|
||||
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) {
|
||||
PullJob *j = userdata;
|
||||
_cleanup_free_ char *length = NULL, *last_modified = NULL, *etag = NULL;
|
||||
size_t sz = size * nmemb;
|
||||
_cleanup_free_ char *length = NULL, *last_modified = NULL;
|
||||
char *etag;
|
||||
PullJob *j = userdata;
|
||||
CURLcode code;
|
||||
long status;
|
||||
int r;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
if (r < 0) {
|
||||
log_oom();
|
||||
goto fail;
|
||||
}
|
||||
if (r > 0) {
|
||||
free(j->etag);
|
||||
j->etag = etag;
|
||||
free_and_replace(j->etag, etag);
|
||||
|
||||
if (strv_contains(j->old_etags, j->etag)) {
|
||||
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),
|
||||
.compressed_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),
|
||||
};
|
||||
|
||||
|
@ -13,23 +13,18 @@ typedef void (*PullJobFinished)(PullJob *job);
|
||||
typedef int (*PullJobOpenDisk)(PullJob *job);
|
||||
typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz);
|
||||
typedef void (*PullJobProgress)(PullJob *job);
|
||||
typedef int (*PullJobNotFound)(PullJob *job, char **ret_new_url);
|
||||
|
||||
typedef enum PullJobState {
|
||||
PULL_JOB_INIT,
|
||||
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_FAILED,
|
||||
_PULL_JOB_STATE_MAX,
|
||||
_PULL_JOB_STATE_INVALID = -1,
|
||||
} 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))
|
||||
|
||||
struct PullJob {
|
||||
@ -43,6 +38,7 @@ struct PullJob {
|
||||
PullJobOpenDisk on_open_disk;
|
||||
PullJobHeader on_header;
|
||||
PullJobProgress on_progress;
|
||||
PullJobNotFound on_not_found;
|
||||
|
||||
CurlGlue *glue;
|
||||
CURL *curl;
|
||||
@ -79,8 +75,6 @@ struct PullJob {
|
||||
gcry_md_hd_t checksum_context;
|
||||
|
||||
char *checksum;
|
||||
|
||||
VerificationStyle style;
|
||||
};
|
||||
|
||||
int pull_job_new(PullJob **job, const char *url, CurlGlue *glue, void *userdata);
|
||||
|
@ -42,21 +42,22 @@ struct RawPull {
|
||||
sd_event *event;
|
||||
CurlGlue *glue;
|
||||
|
||||
PullFlags flags;
|
||||
ImportVerify verify;
|
||||
char *image_root;
|
||||
|
||||
PullJob *raw_job;
|
||||
PullJob *roothash_job;
|
||||
PullJob *settings_job;
|
||||
PullJob *checksum_job;
|
||||
PullJob *signature_job;
|
||||
PullJob *settings_job;
|
||||
PullJob *roothash_job;
|
||||
PullJob *roothash_signature_job;
|
||||
PullJob *verity_job;
|
||||
|
||||
RawPullFinished on_finished;
|
||||
void *userdata;
|
||||
|
||||
char *local;
|
||||
bool force_local;
|
||||
bool settings;
|
||||
bool roothash;
|
||||
|
||||
char *final_path;
|
||||
char *temp_path;
|
||||
@ -67,7 +68,11 @@ struct RawPull {
|
||||
char *roothash_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) {
|
||||
@ -75,34 +80,30 @@ RawPull* raw_pull_unref(RawPull *i) {
|
||||
return NULL;
|
||||
|
||||
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->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);
|
||||
sd_event_unref(i->event);
|
||||
|
||||
if (i->temp_path) {
|
||||
(void) unlink(i->temp_path);
|
||||
free(i->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);
|
||||
}
|
||||
unlink_and_free(i->temp_path);
|
||||
unlink_and_free(i->settings_temp_path);
|
||||
unlink_and_free(i->roothash_temp_path);
|
||||
unlink_and_free(i->roothash_signature_temp_path);
|
||||
unlink_and_free(i->verity_temp_path);
|
||||
|
||||
free(i->final_path);
|
||||
free(i->roothash_path);
|
||||
free(i->settings_path);
|
||||
free(i->roothash_path);
|
||||
free(i->roothash_signature_path);
|
||||
free(i->verity_path);
|
||||
free(i->image_root);
|
||||
free(i->local);
|
||||
|
||||
return mfree(i);
|
||||
}
|
||||
|
||||
@ -169,6 +170,16 @@ static void raw_pull_report_progress(RawPull *i, RawProgress p) {
|
||||
|
||||
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) {
|
||||
percent += i->settings_job->progress_percent * 5 / 100;
|
||||
remain -= 5;
|
||||
@ -179,14 +190,14 @@ static void raw_pull_report_progress(RawPull *i, RawProgress p) {
|
||||
remain -= 5;
|
||||
}
|
||||
|
||||
if (i->checksum_job) {
|
||||
percent += i->checksum_job->progress_percent * 5 / 100;
|
||||
if (i->roothash_signature_job) {
|
||||
percent += i->roothash_signature_job->progress_percent * 5 / 100;
|
||||
remain -= 5;
|
||||
}
|
||||
|
||||
if (i->signature_job) {
|
||||
percent += i->signature_job->progress_percent * 5 / 100;
|
||||
remain -= 5;
|
||||
if (i->verity_job) {
|
||||
percent += i->verity_job->progress_percent * 10 / 100;
|
||||
remain -= 10;
|
||||
}
|
||||
|
||||
if (i->raw_job)
|
||||
@ -294,7 +305,7 @@ static int raw_pull_copy_auxiliary_file(
|
||||
|
||||
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)
|
||||
log_warning_errno(r, "File %s already exists, not replacing.", local);
|
||||
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");
|
||||
|
||||
if (i->force_local)
|
||||
if (FLAGS_SET(i->flags, PULL_FORCE))
|
||||
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (i->settings) {
|
||||
r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path);
|
||||
if (FLAGS_SET(i->flags, PULL_ROOTHASH_SIGNATURE)) {
|
||||
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)
|
||||
return r;
|
||||
}
|
||||
@ -394,14 +417,18 @@ static bool raw_pull_is_done(RawPull *i) {
|
||||
|
||||
if (!PULL_JOB_IS_COMPLETE(i->raw_job))
|
||||
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))
|
||||
return false;
|
||||
if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
|
||||
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;
|
||||
}
|
||||
@ -447,12 +474,18 @@ static void raw_pull_job_on_finished(PullJob *j) {
|
||||
assert(j->userdata);
|
||||
|
||||
i = j->userdata;
|
||||
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->settings_job) {
|
||||
if (j == i->settings_job) {
|
||||
if (j->error != 0)
|
||||
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) {
|
||||
if (j == i->checksum_job)
|
||||
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;
|
||||
}
|
||||
|
||||
/* This is invoked if either the download completed
|
||||
* successfully, or the download was skipped because we
|
||||
* already have the etag. In this case ->etag_exists is
|
||||
* true.
|
||||
/* This is invoked if either the download completed successfully, or the download was skipped because
|
||||
* we already have the etag. In this case ->etag_exists is true.
|
||||
*
|
||||
* We only do something when we got all three files */
|
||||
|
||||
if (!raw_pull_is_done(i))
|
||||
return;
|
||||
|
||||
if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) {
|
||||
log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
|
||||
if (i->signature_job && i->signature_job->error != 0) {
|
||||
VerificationStyle style;
|
||||
|
||||
r = i->signature_job->error;
|
||||
goto finish;
|
||||
r = verification_style_from_url(i->checksum_job->url, &style);
|
||||
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)
|
||||
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);
|
||||
if (r < 0)
|
||||
@ -495,7 +542,7 @@ static void raw_pull_job_on_finished(PullJob *j) {
|
||||
|
||||
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)
|
||||
goto finish;
|
||||
|
||||
@ -598,6 +645,18 @@ static int raw_pull_job_on_open_disk_raw(PullJob *j) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
assert(j);
|
||||
assert(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) {
|
||||
@ -637,16 +708,15 @@ int raw_pull_start(
|
||||
RawPull *i,
|
||||
const char *url,
|
||||
const char *local,
|
||||
bool force_local,
|
||||
ImportVerify verify,
|
||||
bool settings,
|
||||
bool roothash) {
|
||||
PullFlags flags,
|
||||
ImportVerify verify) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
assert(verify < _IMPORT_VERIFY_MAX);
|
||||
assert(verify >= 0);
|
||||
assert(!(flags & ~PULL_FLAGS_MASK_RAW));
|
||||
|
||||
if (!http_url_is_valid(url))
|
||||
return -EINVAL;
|
||||
@ -661,10 +731,8 @@ int raw_pull_start(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
i->force_local = force_local;
|
||||
i->flags = flags;
|
||||
i->verify = verify;
|
||||
i->settings = settings;
|
||||
i->roothash = roothash;
|
||||
|
||||
/* Queue job for the image itself */
|
||||
r = pull_job_new(&i->raw_job, url, i->glue, i);
|
||||
@ -680,17 +748,11 @@ int raw_pull_start(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (roothash) {
|
||||
r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i);
|
||||
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 (settings) {
|
||||
if (FLAGS_SET(flags, PULL_SETTINGS)) {
|
||||
r = pull_make_auxiliary_job(&i->settings_job, url, raw_strip_suffixes, ".nspawn", i->glue, raw_pull_job_on_finished, i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -700,29 +762,43 @@ int raw_pull_start(
|
||||
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 (r < 0)
|
||||
return r;
|
||||
if (FLAGS_SET(flags, PULL_ROOTHASH)) {
|
||||
r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i);
|
||||
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);
|
||||
if (r < 0)
|
||||
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) {
|
||||
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);
|
||||
if (r < 0)
|
||||
@ -737,5 +813,29 @@ int raw_pull_start(
|
||||
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;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "import-util.h"
|
||||
#include "macro.h"
|
||||
#include "pull-common.h"
|
||||
|
||||
typedef struct RawPull RawPull;
|
||||
|
||||
@ -15,4 +16,4 @@ RawPull* raw_pull_unref(RawPull *pull);
|
||||
|
||||
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);
|
||||
|
@ -40,19 +40,19 @@ struct TarPull {
|
||||
sd_event *event;
|
||||
CurlGlue *glue;
|
||||
|
||||
PullFlags flags;
|
||||
ImportVerify verify;
|
||||
char *image_root;
|
||||
|
||||
PullJob *tar_job;
|
||||
PullJob *settings_job;
|
||||
PullJob *checksum_job;
|
||||
PullJob *signature_job;
|
||||
PullJob *settings_job;
|
||||
|
||||
TarPullFinished on_finished;
|
||||
void *userdata;
|
||||
|
||||
char *local;
|
||||
bool force_local;
|
||||
bool settings;
|
||||
|
||||
pid_t tar_pid;
|
||||
|
||||
@ -61,8 +61,6 @@ struct TarPull {
|
||||
|
||||
char *settings_path;
|
||||
char *settings_temp_path;
|
||||
|
||||
ImportVerify verify;
|
||||
};
|
||||
|
||||
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->settings_job);
|
||||
pull_job_unref(i->checksum_job);
|
||||
pull_job_unref(i->signature_job);
|
||||
pull_job_unref(i->settings_job);
|
||||
|
||||
curl_glue_unref(i->glue);
|
||||
sd_event_unref(i->event);
|
||||
|
||||
if (i->temp_path) {
|
||||
(void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||
free(i->temp_path);
|
||||
}
|
||||
|
||||
if (i->settings_temp_path) {
|
||||
(void) unlink(i->settings_temp_path);
|
||||
free(i->settings_temp_path);
|
||||
}
|
||||
rm_rf_subvolume_and_free(i->temp_path);
|
||||
unlink_and_free(i->settings_temp_path);
|
||||
|
||||
free(i->final_path);
|
||||
free(i->settings_path);
|
||||
@ -163,11 +154,6 @@ static void tar_pull_report_progress(TarPull *i, TarProgress p) {
|
||||
|
||||
percent = 0;
|
||||
|
||||
if (i->settings_job) {
|
||||
percent += i->settings_job->progress_percent * 5 / 100;
|
||||
remain -= 5;
|
||||
}
|
||||
|
||||
if (i->checksum_job) {
|
||||
percent += i->checksum_job->progress_percent * 5 / 100;
|
||||
remain -= 5;
|
||||
@ -178,6 +164,11 @@ static void tar_pull_report_progress(TarPull *i, TarProgress p) {
|
||||
remain -= 5;
|
||||
}
|
||||
|
||||
if (i->settings_job) {
|
||||
percent += i->settings_job->progress_percent * 5 / 100;
|
||||
remain -= 5;
|
||||
}
|
||||
|
||||
if (i->tar_job)
|
||||
percent += i->tar_job->progress_percent * remain / 100;
|
||||
break;
|
||||
@ -230,11 +221,11 @@ static int tar_pull_make_local_copy(TarPull *i) {
|
||||
if (!i->local)
|
||||
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)
|
||||
return r;
|
||||
|
||||
if (i->settings) {
|
||||
if (FLAGS_SET(i->flags, PULL_SETTINGS)) {
|
||||
const char *local_settings;
|
||||
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");
|
||||
|
||||
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)
|
||||
log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings);
|
||||
else if (r == -ENOENT)
|
||||
@ -264,12 +255,12 @@ static bool tar_pull_is_done(TarPull *i) {
|
||||
|
||||
if (!PULL_JOB_IS_COMPLETE(i->tar_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))
|
||||
return false;
|
||||
if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
|
||||
return false;
|
||||
if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -302,11 +293,23 @@ static void tar_pull_job_on_finished(PullJob *j) {
|
||||
if (!tar_pull_is_done(i))
|
||||
return;
|
||||
|
||||
if (i->signature_job && i->checksum_job->style == VERIFICATION_PER_DIRECTORY && i->signature_job->error != 0) {
|
||||
log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
|
||||
if (i->signature_job && i->signature_job->error != 0) {
|
||||
VerificationStyle style;
|
||||
|
||||
r = i->signature_job->error;
|
||||
goto finish;
|
||||
r = verification_style_from_url(i->checksum_job->url, &style);
|
||||
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);
|
||||
@ -333,7 +336,7 @@ static void tar_pull_job_on_finished(PullJob *j) {
|
||||
|
||||
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)
|
||||
goto finish;
|
||||
|
||||
@ -471,15 +474,15 @@ int tar_pull_start(
|
||||
TarPull *i,
|
||||
const char *url,
|
||||
const char *local,
|
||||
bool force_local,
|
||||
ImportVerify verify,
|
||||
bool settings) {
|
||||
PullFlags flags,
|
||||
ImportVerify verify) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(i);
|
||||
assert(verify < _IMPORT_VERIFY_MAX);
|
||||
assert(verify >= 0);
|
||||
assert(!(flags & ~PULL_FLAGS_MASK_TAR));
|
||||
|
||||
if (!http_url_is_valid(url))
|
||||
return -EINVAL;
|
||||
@ -494,9 +497,8 @@ int tar_pull_start(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
i->force_local = force_local;
|
||||
i->flags = flags;
|
||||
i->verify = verify;
|
||||
i->settings = settings;
|
||||
|
||||
/* Set up download job for TAR file */
|
||||
r = pull_job_new(&i->tar_job, url, i->glue, i);
|
||||
@ -512,8 +514,13 @@ int tar_pull_start(
|
||||
if (r < 0)
|
||||
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) */
|
||||
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);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -523,24 +530,13 @@ int tar_pull_start(
|
||||
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);
|
||||
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) {
|
||||
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);
|
||||
if (r < 0)
|
||||
@ -555,5 +551,11 @@ int tar_pull_start(
|
||||
return r;
|
||||
}
|
||||
|
||||
if (i->settings_job) {
|
||||
r = pull_job_begin(i->settings_job);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "import-util.h"
|
||||
#include "macro.h"
|
||||
#include "pull-common.h"
|
||||
|
||||
typedef struct TarPull TarPull;
|
||||
|
||||
@ -15,4 +16,4 @@ TarPull* tar_pull_unref(TarPull *pull);
|
||||
|
||||
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);
|
||||
|
@ -19,11 +19,9 @@
|
||||
#include "verbs.h"
|
||||
#include "web-util.h"
|
||||
|
||||
static bool arg_force = false;
|
||||
static const char *arg_image_root = "/var/lib/machines";
|
||||
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
|
||||
static bool arg_settings = true;
|
||||
static bool arg_roothash = true;
|
||||
static PullFlags arg_pull_flags = PULL_SETTINGS | PULL_ROOTHASH | PULL_ROOTHASH_SIGNATURE | PULL_VERITY;
|
||||
|
||||
static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
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);
|
||||
|
||||
if (!arg_force) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL);
|
||||
if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
|
||||
@ -105,7 +103,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
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)
|
||||
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);
|
||||
|
||||
if (!arg_force) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL);
|
||||
if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) {
|
||||
r = image_find(IMAGE_MACHINE, local, NULL, NULL);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
|
||||
@ -191,7 +189,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
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)
|
||||
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"
|
||||
" --settings=BOOL Download settings 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"
|
||||
"Commands:\n"
|
||||
" tar URL [NAME] Download a TAR image\n"
|
||||
@ -232,16 +232,20 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_VERIFY,
|
||||
ARG_SETTINGS,
|
||||
ARG_ROOTHASH,
|
||||
ARG_ROOTHASH_SIGNATURE,
|
||||
ARG_VERITY,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
|
||||
{ "verify", required_argument, NULL, ARG_VERIFY },
|
||||
{ "settings", required_argument, NULL, ARG_SETTINGS },
|
||||
{ "roothash", required_argument, NULL, ARG_ROOTHASH },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
|
||||
{ "verify", required_argument, NULL, ARG_VERIFY },
|
||||
{ "settings", required_argument, NULL, ARG_SETTINGS },
|
||||
{ "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();
|
||||
|
||||
case ARG_FORCE:
|
||||
arg_force = true;
|
||||
arg_pull_flags |= PULL_FORCE;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_ROOT:
|
||||
@ -281,7 +285,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
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;
|
||||
|
||||
case ARG_ROOTHASH:
|
||||
@ -289,7 +293,27 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
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;
|
||||
|
||||
case '?':
|
||||
|
@ -408,7 +408,7 @@ static int image_object_find(sd_bus *bus, const char *path, const char *interfac
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = image_find(IMAGE_MACHINE, e, &image);
|
||||
r = image_find(IMAGE_MACHINE, e, NULL, &image);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
@ -452,7 +452,7 @@ static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata,
|
||||
if (!images)
|
||||
return -ENOMEM;
|
||||
|
||||
r = image_discover(IMAGE_MACHINE, images);
|
||||
r = image_discover(IMAGE_MACHINE, NULL, images);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -124,7 +124,7 @@ static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_erro
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = image_find(IMAGE_MACHINE, name, NULL);
|
||||
r = image_find(IMAGE_MACHINE, name, NULL, NULL);
|
||||
if (r == -ENOENT)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
|
||||
if (r < 0)
|
||||
@ -480,7 +480,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
|
||||
if (!images)
|
||||
return -ENOMEM;
|
||||
|
||||
r = image_discover(IMAGE_MACHINE, images);
|
||||
r = image_discover(IMAGE_MACHINE, NULL, images);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -562,7 +562,7 @@ static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_
|
||||
if (!image_name_is_valid(name))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
|
||||
|
||||
r = image_find(IMAGE_MACHINE, name, &i);
|
||||
r = image_find(IMAGE_MACHINE, name, NULL, &i);
|
||||
if (r == -ENOENT)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
|
||||
if (r < 0)
|
||||
@ -755,7 +755,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
|
||||
goto child_fail;
|
||||
}
|
||||
|
||||
r = image_discover(IMAGE_MACHINE, images);
|
||||
r = image_discover(IMAGE_MACHINE, NULL, images);
|
||||
if (r < 0)
|
||||
goto child_fail;
|
||||
|
||||
|
@ -6,6 +6,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
|
||||
#include "conf-parser.h"
|
||||
#include "networkd-conf.h"
|
||||
#include "networkd-manager.h"
|
||||
#include "networkd-route.h"
|
||||
%}
|
||||
struct ConfigPerfItem;
|
||||
%null_strings
|
||||
@ -21,5 +22,6 @@ struct ConfigPerfItem;
|
||||
Network.SpeedMeter, config_parse_bool, 0, offsetof(Manager, use_speed_meter)
|
||||
Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec)
|
||||
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.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid)
|
||||
|
@ -875,6 +875,8 @@ void manager_free(Manager *m) {
|
||||
|
||||
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.
|
||||
* So, it is necessary to set NULL after the sets are freed. */
|
||||
m->rules = set_free(m->rules);
|
||||
|
@ -65,6 +65,9 @@ struct Manager {
|
||||
Set *routes;
|
||||
Set *routes_foreign;
|
||||
|
||||
/* Route table name */
|
||||
Hashmap *route_tables;
|
||||
|
||||
/* For link speed meter*/
|
||||
bool use_speed_meter;
|
||||
sd_event_source *speed_meter_event_source;
|
||||
|
@ -87,7 +87,7 @@ static const char * const route_table_table[] = {
|
||||
[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)
|
||||
static const char *format_route_table(int table, char *buf, size_t size) {
|
||||
@ -1868,6 +1868,28 @@ int config_parse_route_scope(
|
||||
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(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
@ -1899,16 +1921,11 @@ int config_parse_route_table(
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = route_table_from_string(rvalue);
|
||||
if (r >= 0)
|
||||
n->table = r;
|
||||
else {
|
||||
r = safe_atou32(rvalue, &n->table);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Could not parse route table number \"%s\", ignoring assignment: %m", rvalue);
|
||||
return 0;
|
||||
}
|
||||
r = route_table_from_string_full(network->manager, rvalue, &n->table);
|
||||
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;
|
||||
@ -2356,6 +2373,77 @@ int config_parse_multipath_route(
|
||||
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) {
|
||||
if (section_is_invalid(route->section))
|
||||
return -EINVAL;
|
||||
|
@ -86,6 +86,11 @@ int network_add_ipv4ll_route(Network *network);
|
||||
int network_add_default_route_on_device(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_preferred_src);
|
||||
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_multipath_route);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_route_table_names);
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "conf-parser.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "ip-protocol-list.h"
|
||||
#include "netlink-util.h"
|
||||
#include "networkd-manager.h"
|
||||
@ -1129,9 +1130,10 @@ int config_parse_routing_policy_rule_table(
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = safe_atou32(rvalue, &n->table);
|
||||
r = route_table_from_string_full(network->manager, rvalue, &n->table);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2940,7 +2940,7 @@ static int determine_names(void) {
|
||||
if (arg_machine) {
|
||||
_cleanup_(image_unrefp) Image *i = NULL;
|
||||
|
||||
r = image_find(IMAGE_MACHINE, arg_machine, &i);
|
||||
r = image_find(IMAGE_MACHINE, arg_machine, NULL, &i);
|
||||
if (r == -ENOENT)
|
||||
return log_error_errno(r, "No image for machine '%s'.", arg_machine);
|
||||
if (r < 0)
|
||||
|
@ -495,7 +495,7 @@ int portable_extract(
|
||||
|
||||
assert(name_or_path);
|
||||
|
||||
r = image_find_harder(IMAGE_PORTABLE, name_or_path, &image);
|
||||
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -953,7 +953,7 @@ static int install_image_symlink(
|
||||
/* If the image is outside of the image search also link it into it, so that it can be found with short image
|
||||
* names and is listed among the images. */
|
||||
|
||||
if (image_in_search_path(IMAGE_PORTABLE, image_path))
|
||||
if (image_in_search_path(IMAGE_PORTABLE, NULL, image_path))
|
||||
return 0;
|
||||
|
||||
r = image_symlink(image_path, flags, &sl);
|
||||
@ -987,7 +987,7 @@ int portable_attach(
|
||||
|
||||
assert(name_or_path);
|
||||
|
||||
r = image_find_harder(IMAGE_PORTABLE, name_or_path, &image);
|
||||
r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1193,7 +1193,7 @@ int portable_detach(
|
||||
return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name);
|
||||
|
||||
if (path_is_absolute(marker) &&
|
||||
!image_in_search_path(IMAGE_PORTABLE, marker)) {
|
||||
!image_in_search_path(IMAGE_PORTABLE, NULL, marker)) {
|
||||
|
||||
r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(marker));
|
||||
if (r < 0)
|
||||
|
@ -606,7 +606,7 @@ int bus_image_acquire(
|
||||
if (image_name_is_valid(name_or_path)) {
|
||||
|
||||
/* If it's a short name, let's search for it */
|
||||
r = image_find(IMAGE_PORTABLE, name_or_path, &loaded);
|
||||
r = image_find(IMAGE_PORTABLE, name_or_path, NULL, &loaded);
|
||||
if (r == -ENOENT)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);
|
||||
|
||||
|
@ -92,7 +92,7 @@ int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *erro
|
||||
/* A wrapper around image_discover() (for finding images in search path) and portable_discover_attached() (for
|
||||
* finding attached images). */
|
||||
|
||||
r = image_discover(IMAGE_PORTABLE, images);
|
||||
r = image_discover(IMAGE_PORTABLE, NULL, images);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -42,18 +42,24 @@
|
||||
#include "xattr-util.h"
|
||||
|
||||
static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
|
||||
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
|
||||
"/run/machines\0" /* and here too */
|
||||
"/var/lib/machines\0" /* the main place for images */
|
||||
"/var/lib/container\0" /* legacy */
|
||||
"/usr/local/lib/machines\0"
|
||||
"/usr/lib/machines\0",
|
||||
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
|
||||
"/run/machines\0" /* and here too */
|
||||
"/var/lib/machines\0" /* the main place for images */
|
||||
"/var/lib/container\0" /* legacy */
|
||||
"/usr/local/lib/machines\0"
|
||||
"/usr/lib/machines\0",
|
||||
|
||||
[IMAGE_PORTABLE] = "/etc/portables\0" /* only place symlinks here */
|
||||
"/run/portables\0" /* and here too */
|
||||
"/var/lib/portables\0" /* the main place for images */
|
||||
"/usr/local/lib/portables\0"
|
||||
"/usr/lib/portables\0",
|
||||
[IMAGE_PORTABLE] = "/etc/portables\0" /* only place symlinks here */
|
||||
"/run/portables\0" /* and here too */
|
||||
"/var/lib/portables\0" /* the main place for images */
|
||||
"/usr/local/lib/portables\0"
|
||||
"/usr/lib/portables\0",
|
||||
|
||||
[IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
|
||||
"/run/extensions\0" /* and here too */
|
||||
"/var/lib/extensions\0" /* the main place for images */
|
||||
"/usr/local/lib/extensions\0"
|
||||
"/usr/lib/extensions\0",
|
||||
};
|
||||
|
||||
static Image *image_free(Image *i) {
|
||||
@ -415,7 +421,11 @@ static int image_make(
|
||||
return -EMEDIUMTYPE;
|
||||
}
|
||||
|
||||
int image_find(ImageClass class, const char *name, Image **ret) {
|
||||
int image_find(ImageClass class,
|
||||
const char *name,
|
||||
const char *root,
|
||||
Image **ret) {
|
||||
|
||||
const char *path;
|
||||
int r;
|
||||
|
||||
@ -428,20 +438,22 @@ int image_find(ImageClass class, const char *name, Image **ret) {
|
||||
return -ENOENT;
|
||||
|
||||
NULSTR_FOREACH(path, image_search_path[class]) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
struct stat st;
|
||||
int flags;
|
||||
|
||||
d = opendir(path);
|
||||
if (!d) {
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people to
|
||||
* symlink block devices into the search path */
|
||||
if (fstatat(dirfd(d), name, &st, 0) < 0) {
|
||||
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people
|
||||
* to symlink block devices into the search path. (For now, we disable that when operating
|
||||
* relative to some root directory.) */
|
||||
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
|
||||
if (fstatat(dirfd(d), name, &st, flags) < 0) {
|
||||
_cleanup_free_ char *raw = NULL;
|
||||
|
||||
if (errno != ENOENT)
|
||||
@ -451,8 +463,7 @@ int image_find(ImageClass class, const char *name, Image **ret) {
|
||||
if (!raw)
|
||||
return -ENOMEM;
|
||||
|
||||
if (fstatat(dirfd(d), raw, &st, 0) < 0) {
|
||||
|
||||
if (fstatat(dirfd(d), raw, &st, flags) < 0) {
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
|
||||
@ -462,13 +473,13 @@ int image_find(ImageClass class, const char *name, Image **ret) {
|
||||
if (!S_ISREG(st.st_mode))
|
||||
continue;
|
||||
|
||||
r = image_make(name, dirfd(d), path, raw, &st, ret);
|
||||
r = image_make(name, dirfd(d), resolved, raw, &st, ret);
|
||||
|
||||
} else {
|
||||
if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
|
||||
continue;
|
||||
|
||||
r = image_make(name, dirfd(d), path, name, &st, ret);
|
||||
r = image_make(name, dirfd(d), resolved, name, &st, ret);
|
||||
}
|
||||
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
|
||||
continue;
|
||||
@ -482,7 +493,7 @@ int image_find(ImageClass class, const char *name, Image **ret) {
|
||||
}
|
||||
|
||||
if (class == IMAGE_MACHINE && streq(name, ".host")) {
|
||||
r = image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
|
||||
r = image_make(".host", AT_FDCWD, NULL, empty_to_root(root), NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -507,14 +518,18 @@ int image_from_path(const char *path, Image **ret) {
|
||||
return image_make(NULL, AT_FDCWD, NULL, path, NULL, ret);
|
||||
}
|
||||
|
||||
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret) {
|
||||
int image_find_harder(ImageClass class, const char *name_or_path, const char *root, Image **ret) {
|
||||
if (image_name_is_valid(name_or_path))
|
||||
return image_find(class, name_or_path, ret);
|
||||
return image_find(class, name_or_path, root, ret);
|
||||
|
||||
return image_from_path(name_or_path, ret);
|
||||
}
|
||||
|
||||
int image_discover(ImageClass class, Hashmap *h) {
|
||||
int image_discover(
|
||||
ImageClass class,
|
||||
const char *root,
|
||||
Hashmap *h) {
|
||||
|
||||
const char *path;
|
||||
int r;
|
||||
|
||||
@ -523,29 +538,30 @@ int image_discover(ImageClass class, Hashmap *h) {
|
||||
assert(h);
|
||||
|
||||
NULSTR_FOREACH(path, image_search_path[class]) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
struct dirent *de;
|
||||
|
||||
d = opendir(path);
|
||||
if (!d) {
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
|
||||
return -errno;
|
||||
}
|
||||
r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
FOREACH_DIRENT_ALL(de, d, return -errno) {
|
||||
_cleanup_(image_unrefp) Image *image = NULL;
|
||||
_cleanup_free_ char *truncated = NULL;
|
||||
const char *pretty;
|
||||
struct stat st;
|
||||
int flags;
|
||||
|
||||
if (dot_or_dot_dot(de->d_name))
|
||||
continue;
|
||||
|
||||
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people
|
||||
* to symlink block devices into the search path */
|
||||
if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
|
||||
/* As mentioned above, we follow symlinks on this fstatat(), because we want to
|
||||
* permit people to symlink block devices into the search path. */
|
||||
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
|
||||
if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) {
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
|
||||
@ -575,7 +591,7 @@ int image_discover(ImageClass class, Hashmap *h) {
|
||||
if (hashmap_contains(h, pretty))
|
||||
continue;
|
||||
|
||||
r = image_make(pretty, dirfd(d), path, de->d_name, &st, &image);
|
||||
r = image_make(pretty, dirfd(d), resolved, de->d_name, &st, &image);
|
||||
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
|
||||
continue;
|
||||
if (r < 0)
|
||||
@ -594,7 +610,7 @@ int image_discover(ImageClass class, Hashmap *h) {
|
||||
if (class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
|
||||
_cleanup_(image_unrefp) Image *image = NULL;
|
||||
|
||||
r = image_make(".host", AT_FDCWD, NULL, "/", NULL, &image);
|
||||
r = image_make(".host", AT_FDCWD, NULL, empty_to_root("/"), NULL, &image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -737,7 +753,7 @@ int image_rename(Image *i, const char *new_name) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = image_find(IMAGE_MACHINE, new_name, NULL);
|
||||
r = image_find(IMAGE_MACHINE, new_name, NULL, NULL);
|
||||
if (r >= 0)
|
||||
return -EEXIST;
|
||||
if (r != -ENOENT)
|
||||
@ -850,7 +866,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = image_find(IMAGE_MACHINE, new_name, NULL);
|
||||
r = image_find(IMAGE_MACHINE, new_name, NULL, NULL);
|
||||
if (r >= 0)
|
||||
return -EEXIST;
|
||||
if (r != -ENOENT)
|
||||
@ -1242,16 +1258,27 @@ bool image_name_is_valid(const char *s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool image_in_search_path(ImageClass class, const char *image) {
|
||||
bool image_in_search_path(
|
||||
ImageClass class,
|
||||
const char *root,
|
||||
const char *image) {
|
||||
|
||||
const char *path;
|
||||
|
||||
assert(image);
|
||||
|
||||
NULSTR_FOREACH(path, image_search_path[class]) {
|
||||
const char *p;
|
||||
const char *p, *q;
|
||||
size_t k;
|
||||
|
||||
p = path_startswith(image, path);
|
||||
if (!empty_or_root(root)) {
|
||||
q = path_startswith(path, root);
|
||||
if (!q)
|
||||
continue;
|
||||
} else
|
||||
q = path;
|
||||
|
||||
p = path_startswith(q, path);
|
||||
if (!p)
|
||||
continue;
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
typedef enum ImageClass {
|
||||
IMAGE_MACHINE,
|
||||
IMAGE_PORTABLE,
|
||||
IMAGE_EXTENSION,
|
||||
_IMAGE_CLASS_MAX,
|
||||
_IMAGE_CLASS_INVALID = -1
|
||||
} ImageClass;
|
||||
@ -61,10 +62,10 @@ Image *image_ref(Image *i);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref);
|
||||
|
||||
int image_find(ImageClass class, const char *name, Image **ret);
|
||||
int image_find(ImageClass class, const char *root, const char *name, Image **ret);
|
||||
int image_from_path(const char *path, Image **ret);
|
||||
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret);
|
||||
int image_discover(ImageClass class, Hashmap *map);
|
||||
int image_find_harder(ImageClass class, const char *root, const char *name_or_path, Image **ret);
|
||||
int image_discover(ImageClass class, const char *root, Hashmap *map);
|
||||
|
||||
int image_remove(Image *i);
|
||||
int image_rename(Image *i, const char *new_name);
|
||||
@ -83,7 +84,7 @@ int image_set_limit(Image *i, uint64_t referenced_max);
|
||||
|
||||
int image_read_metadata(Image *i);
|
||||
|
||||
bool image_in_search_path(ImageClass class, const char *image);
|
||||
bool image_in_search_path(ImageClass class, const char *root, const char *image);
|
||||
|
||||
static inline bool IMAGE_IS_HIDDEN(const struct Image *i) {
|
||||
assert(i);
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "macro.h"
|
||||
#include "machine-image.h"
|
||||
#include "os-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
@ -31,17 +32,31 @@ int path_is_os_tree(const char *path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int open_os_release(const char *root, char **ret_path, int *ret_fd) {
|
||||
int open_extension_release(const char *root, const char *extension, char **ret_path, int *ret_fd) {
|
||||
_cleanup_free_ char *q = NULL;
|
||||
const char *p;
|
||||
int r, fd;
|
||||
|
||||
FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
|
||||
r = chase_symlinks(p, root, CHASE_PREFIX_ROOT,
|
||||
ret_path ? &q : NULL,
|
||||
ret_fd ? &fd : NULL);
|
||||
if (r != -ENOENT)
|
||||
break;
|
||||
if (extension) {
|
||||
const char *extension_full_path;
|
||||
|
||||
if (!image_name_is_valid(extension))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"The extension name %s is invalid.", extension);
|
||||
|
||||
extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
|
||||
r = chase_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT,
|
||||
ret_path ? &q : NULL,
|
||||
ret_fd ? &fd : NULL);
|
||||
} else {
|
||||
const char *p;
|
||||
|
||||
FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
|
||||
r = chase_symlinks(p, root, CHASE_PREFIX_ROOT,
|
||||
ret_path ? &q : NULL,
|
||||
ret_fd ? &fd : NULL);
|
||||
if (r != -ENOENT)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -64,16 +79,16 @@ int open_os_release(const char *root, char **ret_path, int *ret_fd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
|
||||
int fopen_extension_release(const char *root, const char *extension, char **ret_path, FILE **ret_file) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
FILE *f;
|
||||
int r;
|
||||
|
||||
if (!ret_file)
|
||||
return open_os_release(root, ret_path, NULL);
|
||||
return open_extension_release(root, extension, ret_path, NULL);
|
||||
|
||||
r = open_os_release(root, ret_path ? &p : NULL, &fd);
|
||||
r = open_extension_release(root, extension, ret_path ? &p : NULL, &fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -89,18 +104,35 @@ int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_os_release(const char *root, ...) {
|
||||
static int parse_release_internal(const char *root, const char *extension, va_list ap) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
r = fopen_os_release(root, &p, &f);
|
||||
r = fopen_extension_release(root, extension, &p, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return parse_env_filev(f, p, ap);
|
||||
}
|
||||
|
||||
int parse_extension_release(const char *root, const char *extension, ...) {
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
va_start(ap, extension);
|
||||
r = parse_release_internal(root, extension, ap);
|
||||
va_end(ap);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int parse_os_release(const char *root, ...) {
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
va_start(ap, root);
|
||||
r = parse_env_filev(f, p, ap);
|
||||
r = parse_release_internal(root, NULL, ap);
|
||||
va_end(ap);
|
||||
|
||||
return r;
|
||||
|
@ -5,9 +5,19 @@
|
||||
|
||||
int path_is_os_tree(const char *path);
|
||||
|
||||
int open_os_release(const char *root, char **ret_path, int *ret_fd);
|
||||
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
|
||||
/* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
|
||||
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
|
||||
int open_extension_release(const char *root, const char *extension, char **ret_path, int *ret_fd);
|
||||
static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
|
||||
return open_extension_release(root, NULL, ret_path, ret_fd);
|
||||
}
|
||||
|
||||
int fopen_extension_release(const char *root, const char *extension, char **ret_path, FILE **ret_file);
|
||||
static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
|
||||
return fopen_extension_release(root, NULL, ret_path, ret_file);
|
||||
}
|
||||
|
||||
int parse_extension_release(const char *root, const char *extension, ...) _sentinel_;
|
||||
int parse_os_release(const char *root, ...) _sentinel_;
|
||||
int load_os_release_pairs(const char *root, char ***ret);
|
||||
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
|
||||
|
5
src/sysext/meson.build
Normal file
5
src/sysext/meson.build
Normal 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
1018
src/sysext/sysext.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -127,6 +127,12 @@ int import_environment(int argc, char *argv[], void *userdata) {
|
||||
|
||||
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);
|
||||
|
||||
} else {
|
||||
@ -139,21 +145,30 @@ int import_environment(int argc, char *argv[], void *userdata) {
|
||||
STRV_FOREACH(a, strv_skip(argv, 1)) {
|
||||
|
||||
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) {
|
||||
const char *eq;
|
||||
|
||||
eq = startswith(*b, *a);
|
||||
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);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
log_notice("Environment variable $%s not set, ignoring.", *a);
|
||||
}
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
|
@ -265,6 +265,7 @@ static void test_env_clean(void) {
|
||||
"another=one",
|
||||
"another=final one",
|
||||
"CRLF=\r\n",
|
||||
"LESS_TERMCAP_mb=\x1b[01;31m",
|
||||
"BASH_FUNC_foo%%=() { echo foo\n}");
|
||||
assert_se(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[4], "xyz=xyz\n"));
|
||||
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) {
|
||||
@ -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("tab\tcharacter"));
|
||||
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("new DOS\r\nline"));
|
||||
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("\xc5")); /* A truncated utf-8-encoded "ł".
|
||||
* We currently disallow that. */
|
||||
}
|
||||
|
||||
static void test_env_assignment_is_valid(void) {
|
||||
|
@ -933,7 +933,7 @@ install_execs() {
|
||||
# some {rc,halt}.local scripts and programs are okay to not exist, the rest should
|
||||
# also, plymouth is pulled in by rescue.service, but even there the exit code
|
||||
# is ignored; as it's not present on some distros, don't fail if it doesn't exist
|
||||
dinfo "Attempting to install $i"
|
||||
dinfo "Attempting to install $i (based on unit file reference)"
|
||||
inst $i || [ "${i%.local}" != "$i" ] || [ "${i%systemd-update-done}" != "$i" ] || [ "${i##*/}" == "plymouth" ]
|
||||
done
|
||||
)
|
||||
|
@ -211,6 +211,7 @@ in_units = [
|
||||
['systemd-oomd.service', 'ENABLE_OOMD'],
|
||||
['systemd-portabled.service', 'ENABLE_PORTABLED',
|
||||
'dbus-org.freedesktop.portable1.service'],
|
||||
['systemd-sysext.service', 'ENABLE_SYSEXT'],
|
||||
['systemd-userdbd.service', 'ENABLE_USERDB'],
|
||||
['systemd-homed.service', 'ENABLE_HOMED'],
|
||||
['systemd-quotacheck.service', 'ENABLE_QUOTACHECK'],
|
||||
|
31
units/systemd-sysext.service.in
Normal file
31
units/systemd-sysext.service.in
Normal 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
|
Loading…
x
Reference in New Issue
Block a user