mirror of
https://github.com/systemd/systemd.git
synced 2025-03-19 22:50:17 +03:00
Merge pull request #20156 from poettering/sysupdate
new "systemd-sysupdate" component
This commit is contained in:
commit
7ff9846956
17
TODO
17
TODO
@ -251,11 +251,26 @@ Features:
|
||||
that images cannot be misused.
|
||||
|
||||
* New udev block device symlink names:
|
||||
/dev/disk/by-parttypelabel/<pttype>/<ptlabel>. Use case: if pt label is used
|
||||
/dev/disk/by-parttypelabel/<pttype>-<ptlabel>. Use case: if pt label is used
|
||||
as partition image version string, this is a safe way to reference a specific
|
||||
version of a specific partition type, in particular where related partitions
|
||||
are processed (e.g. verity + rootfs both named "LennartOS_0.7").
|
||||
|
||||
* sysupdate:
|
||||
- add fuzzing to the pattern parser
|
||||
- support casync as download mechanism
|
||||
- direct TPM2 PCR change handling, possible renrolling LUKS2 media if needed.
|
||||
- "systemd-sysupdate update --all" support, that iterates through all components
|
||||
defined on the host, plus all images installed into /var/lib/machines/,
|
||||
/var/lib/portable/ and so on.
|
||||
- figure out what to do about system extensions (i.e. they need to imply an
|
||||
update component, since otherwise system extenion' sysupdate.d/ files would
|
||||
override the host's update files.)
|
||||
- Allow invocation with a single transfer definition, i.e. with
|
||||
--definitions= pointing to a file rather than a dir.
|
||||
- add ability to disable implicit decompression of downloaded artifacts,
|
||||
i.e. a Compress=no option in the transfer definitions
|
||||
|
||||
* in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix)
|
||||
|
||||
* DynamicUser= + StateDirectory= → use uid mapping mounts, too, in order to
|
||||
|
@ -987,6 +987,13 @@ manpages = [
|
||||
'5',
|
||||
['system.conf.d', 'systemd-user.conf', 'user.conf.d'],
|
||||
''],
|
||||
['systemd-sysupdate',
|
||||
'8',
|
||||
['systemd-sysupdate-reboot.service',
|
||||
'systemd-sysupdate-reboot.timer',
|
||||
'systemd-sysupdate.service',
|
||||
'systemd-sysupdate.timer'],
|
||||
'ENABLE_SYSUPDATE'],
|
||||
['systemd-sysusers', '8', ['systemd-sysusers.service'], ''],
|
||||
['systemd-sysv-generator', '8', [], 'HAVE_SYSV_COMPAT'],
|
||||
['systemd-time-wait-sync.service',
|
||||
@ -1058,6 +1065,7 @@ manpages = [
|
||||
['systemd.time', '7', [], ''],
|
||||
['systemd.timer', '5', [], ''],
|
||||
['systemd.unit', '5', [], ''],
|
||||
['sysupdate.d', '5', [], 'ENABLE_SYSUPDATE'],
|
||||
['sysusers.d', '5', [], 'ENABLE_SYSUSERS'],
|
||||
['telinit', '8', [], 'HAVE_SYSV_COMPAT'],
|
||||
['timedatectl', '1', [], 'ENABLE_TIMEDATECTL'],
|
||||
|
287
man/systemd-sysupdate.xml
Normal file
287
man/systemd-sysupdate.xml
Normal file
@ -0,0 +1,287 @@
|
||||
<?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-sysupdate" conditional='ENABLE_SYSUPDATE'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-sysupdate</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-sysupdate</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-sysupdate</refname>
|
||||
<refname>systemd-sysupdate.service</refname>
|
||||
<refname>systemd-sysupdate.timer</refname>
|
||||
<refname>systemd-sysupdate-reboot.service</refname>
|
||||
<refname>systemd-sysupdate-reboot.timer</refname>
|
||||
<refpurpose>Automatically Update OS or Other Resources</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-sysupdate</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
<para><filename>systemd-sysupdate.service</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-sysupdate</command> atomically updates the host OS, container images, portable
|
||||
service images or other sources, based on the transfer configuration files described in
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>This tool implements file, directory, or partition based update schemes, supporting multiple
|
||||
parallel installed versions of specific resources in an A/B (or even: A/B/C, A/B/C/D/, …) style. A/B
|
||||
updating means that when one version of a resource is currently being used, the next version can be
|
||||
downloaded, unpacked, and prepared in an entirely separate location, indepdently of the first, and — once
|
||||
complete — be activated, swapping the roles so that it becomes the used one and the previously used one
|
||||
becomes the the one that is replaced by the next update, and so on. The resources to update are defined
|
||||
in transfer files, one for each resource to be updated. For example, resources that may be updated with
|
||||
this tool could be: a root file system partition, a matching Verity partition plus one kernel image. The
|
||||
combination of the three would be considered a complete OS update.</para>
|
||||
|
||||
<para>The tool updates partitions, files or directory trees always in whole, and operates with at least
|
||||
two versions of each of these resources: the <emphasis>current</emphasis> version, plus the
|
||||
<emphasis>next</emphasis> version: the one that is being updated to, and which is initially incomplete as
|
||||
the downloaded data is written to it; plus optionally more versions. Once the download of a newer version
|
||||
is complete it becomes the current version, releasing the version previously considered current for
|
||||
deletion/replacement/updating.</para>
|
||||
|
||||
<para>When installing new versions the tool will directly download, decompress, unpack and write the new
|
||||
version into the destination. This is done in a robust fashion so that an incomplete download can be
|
||||
recognized on next invocation, and flushed out before a new attempt is initiated.</para>
|
||||
|
||||
<para>Note that when writing updates to a partition, the partition has to exist already, as
|
||||
<command>systemd-sysupdate</command> will not automatically create new partitions. Use a tool such as
|
||||
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry> to
|
||||
automatically create additional partitions to be used with <command>systemd-sysupdate</command> on
|
||||
boot.</para>
|
||||
|
||||
<para>The tool can both be used on the running OS, to update the OS in "online" state from within itself,
|
||||
and on "offline" disk images, to update them from the outside based on transfer files
|
||||
embedded in the disk images. For the latter, see <option>--image=</option> below. The latter is
|
||||
particularly interesting to update container images or portable service images.</para>
|
||||
|
||||
<para>The <filename>systemd-sysupdate.service</filename> system service will automatically update the
|
||||
host OS based on the installed transfer files. It is triggered in regular intervals via
|
||||
<filename>systemd-sysupdate.timer</filename>. The <filename>systemd-sysupdate-reboot.service</filename>
|
||||
will automatically reboot the system after a new version is installed. It is triggered via
|
||||
<filename>systemd-sysupdate-reboot.timer</filename>. The two services are separate from each other as it
|
||||
is typically advisable to download updates regularly while the system is up, but delay reboots until the
|
||||
appropriate time (i.e. typically at night). The two sets of service/timer units may be enabled
|
||||
separately.</para>
|
||||
|
||||
<para>For details about transfer files and examples see
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Command</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>list</option> <optional><replaceable>VERSION</replaceable></optional></term>
|
||||
|
||||
<listitem><para>If invoked without an argument, enumerates downloadable and installed versions, and
|
||||
shows a summarizing table with the discovered versions and their properties, including whether
|
||||
there's a newer candidate version to update to. If a version argument is specified, shows details
|
||||
about the specific version, including the individual files that need to be transferred to acquire the
|
||||
version.</para>
|
||||
|
||||
<para>If no command is explicitly specified this command is implied.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>check-new</option></term>
|
||||
|
||||
<listitem><para>Checks if there's a new version available. This internally enumerates downloadable and
|
||||
installed versions and returns exit status 0 if there's a new version to update to, non-zero
|
||||
otherwise. If there is a new version to update to, its version identifier is written to standard
|
||||
output.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>update</option> <optional><replaceable>VERSION</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Installs (updates to) the specified version, or if none is specified to the newest
|
||||
version available. If the version is already installed or no newer version available, no operation is
|
||||
executed.</para>
|
||||
|
||||
<para>If a new version to install/update to is found, old installed versions are deleted until at
|
||||
least one new version can be installed, as configured via <varname>InstanceMax=</varname> in
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, or
|
||||
via the available partition slots of the right type. This implicit operation can also be invoked
|
||||
explicitly via the <command>vacuum</command> command described below.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>vacuum</option></term>
|
||||
|
||||
<listitem><para>Deletes old installed versions until the limits configured via
|
||||
<varname>InstanceMax=</varname> in
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> are
|
||||
met again. Normally, it should not be necessary to invoke this command explicitly, since it is
|
||||
implicitly invoked whenever a new update is initiated.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>pending</option></term>
|
||||
|
||||
<listitem><para>Checks whether a newer version of the OS is installed than the one currently
|
||||
running. Returns zero if so, non-zero otherwise. This compares the newest installed version's
|
||||
identifier with the OS image version as reported by the <varname>IMAGE_VERSION=</varname> field in
|
||||
<filename>/etc/os-release</filename>. If the former is newer than the latter, an update was
|
||||
apparently completed but not activated (i.e. rebooted into) yet.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>reboot</option></term>
|
||||
|
||||
<listitem><para>Similar to the <option>pending</option> command but immediately reboots in case a
|
||||
newer version of the OS has been installed than the one currently running. This operation can be done
|
||||
implicitly together with the <command>update</command> command, after a completed update via the
|
||||
<option>--reboot</option> switch, see below. This command will execute no operation (and return
|
||||
success) if no update has been installed, and thus the system was not rebooted.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>components</option></term>
|
||||
|
||||
<listitem><para>Lists components that can be updated. This enumerates the
|
||||
<filename>/etc/sysupdate.*.d/</filename>, <filename>/run/sysupdate.*.d/</filename> and
|
||||
<filename>/usr/lib/sysupdate.*.d/</filename> directories that contain transfer files. This command is
|
||||
useful to list possible parameters for <option>--component=</option> (see below).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--component=</option></term>
|
||||
<term><option>-C</option></term>
|
||||
|
||||
<listitem><para>Selects the component to update. Takes a component name as argument. This has the
|
||||
effect of slightly altering the search logic for transfer files. If this switch is not used, the
|
||||
transfer files are loaded from <filename>/etc/sysupdate.d/*.conf</filename>,
|
||||
<filename>/run/sysupdate.d/*.conf</filename> and <filename>/usr/lib/sysupdate.d/*.conf</filename>. If
|
||||
this switch is used, the specified component name is used to alter the directories to look in to be
|
||||
<filename>/etc/sysupdate.<replaceable>component</replaceable>.d/*.conf</filename>,
|
||||
<filename>/run/sysupdate.<replaceable>component</replaceable>.d/*.conf</filename> and
|
||||
<filename>/usr/lib/sysupdate.<replaceable>component</replaceable>.d/*.conf</filename>, each time with
|
||||
the <filename><replaceable>component</replaceable></filename> string replaced with the specified
|
||||
component name.</para>
|
||||
|
||||
<para>Use the <command>components</command> command to list available components to update. This enumerates
|
||||
the directories matching this naming rule.</para>
|
||||
|
||||
<para>Components may be used to define a separate set of transfer files for different components of
|
||||
the OS that shall be updated separately. Do not use this concept for resources that shall always be
|
||||
updated together in a synchronous fashion. Simply define multiple transfer files within the same
|
||||
<filename>sysupdate.d/</filename> directory for these cases.</para>
|
||||
|
||||
<para>This option may not be combined with <option>--definitions=</option>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--definitions=</option></term>
|
||||
|
||||
<listitem><para>A path to a directory. If specified, the transfer <filename>*.conf</filename> files
|
||||
are read from this directory instead of <filename>/usr/lib/sysupdate.d/*.conf</filename>,
|
||||
<filename>/etc/sysupdate.d/*.conf</filename>, and <filename>/run/sysupdate.d/*.conf</filename>.</para>
|
||||
|
||||
<para>This option may not be combined with <option>--component=</option>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--root=</option></term>
|
||||
|
||||
<listitem><para>Takes a path to a directory to use as root file system when searching for
|
||||
<filename>sysupdate.d/*.conf</filename> files.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--image=</option></term>
|
||||
|
||||
<listitem><para>Takes a path to a disk image file or device to mount and use in a similar fashion to
|
||||
<option>--root=</option>, see above. If this is used and partition resources are updated this is done
|
||||
inside the specified disk image.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--instances-max=</option></term>
|
||||
<term><option>-m</option></term>
|
||||
|
||||
<listitem><para>Takes a decimal integer greater than or equal to 2. Controls how many versions to
|
||||
keep at any time. This option may also be configured inside the transfer files, via the
|
||||
<varname>InstancesMax=</varname> setting, see
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--sync=</option></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument, defaults to yes. This may be used to specify whether the
|
||||
newly updated resource versions shall be synchronized to disk when appropriate (i.e. after the
|
||||
download is complete, before it is finalized, and again after finalization). This should not be
|
||||
turned off, except to improve runtime performance in testing environments.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--verify=</option></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument, defaults to yes. Controls whether to cryptographically
|
||||
verify downloads. Do not turn this off, except in testing environments.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--reboot</option></term>
|
||||
|
||||
<listitem><para>When used in combination with the <command>update</command> command and a new version is
|
||||
installed, automatically reboots the system immediately afterwards.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="json" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
885
man/sysupdate.d.xml
Normal file
885
man/sysupdate.d.xml
Normal file
@ -0,0 +1,885 @@
|
||||
<?xml version='1.0'?>
|
||||
<!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="sysupdate.d" conditional='ENABLE_SYSUPDATE'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>sysupdate.d</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>sysupdate.d</refentrytitle>
|
||||
<manvolnum>5</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>sysupdate.d</refname>
|
||||
<refpurpose>Transfer Definition Files for Automatic Updates</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><literallayout><filename>/etc/sysupdate.d/*.conf</filename>
|
||||
<filename>/run/sysupdate.d/*.conf</filename>
|
||||
<filename>/usr/lib/sysupdate.d/*.conf</filename>
|
||||
</literallayout></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><filename>sysupdate.d/*.conf</filename> files describe how specific resources on the local system
|
||||
shall be updated from a remote source. Each such file defines one such transfer: typically a remote
|
||||
HTTP/HTTPS resource as source; and a local file, directory or partition as target. This may be used as a
|
||||
simple, automatic, atomic update mechanism for the OS itself, for containers, portable services or system
|
||||
extension images — but in fact may be used to update any kind of file from a remote source.</para>
|
||||
|
||||
<para>The
|
||||
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
command reads these files and uses them to determine which local resources should be updated, and then
|
||||
executes the update.</para>
|
||||
|
||||
<para>Both the remote HTTP/HTTPS source and the local target typically exist in multiple, concurrent
|
||||
versions, in order to implement flexible update schemes, e.g. A/B updating (or a superset thereof,
|
||||
e.g. A/B/C, A/B/C/D, …).</para>
|
||||
|
||||
<para>Each <filename>*.conf</filename> file defines one transfer, i.e. describes one resource to
|
||||
update. Typically, multiple of these files (i.e. multiple of such transfers) are defined together, and
|
||||
are bound together by a common version identifier in order to update multiple resources at once on each
|
||||
update operation, for example to update a kernel, a root file system and a Verity partition in a single,
|
||||
combined, synchronized operation, so that only a combined update of all three together constitutes a
|
||||
complete update.</para>
|
||||
|
||||
<para>Each <filename>*.conf</filename> file contains three sections: [Transfer], [Source] and [Target].</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Basic Mode of Operation</title>
|
||||
|
||||
<para>Disk-image based OS updates typically consist of multiple different resources that need to be
|
||||
updated together, for example a secure OS update might consist of a root file system image to drop into a
|
||||
partition, a matching Verity integrity data partition image, and a kernel image prepared to boot into the
|
||||
combination of the two partitions. The first two resources are files that are downloaded and placed in a
|
||||
disk partition, the latter is a file that is downloaded and placed in a regular file in the boot file
|
||||
system (e.g. EFI system partition). Hence, during an update of a hypothetical operating system "foobarOS"
|
||||
to a hypothetical version 47 the following operations should take place:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem><para>A file <literal>https://download.example.com/foobarOS_47.root.xz</literal> should be
|
||||
downloaded, decompressed and written to a previously unused partition with GPT partition type UUID
|
||||
4f68bce3-e8cd-4db1-96e7-fbcaf984b709 for x86-64, as per <ulink
|
||||
url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions
|
||||
Specification</ulink>.</para></listitem>
|
||||
|
||||
<listitem><para>Similarly, a file <literal>https://download.example.com/foobarOS_47.verity.xz</literal>
|
||||
should be downloaded, decompressed and written to a previously empty partition with GPT partition type
|
||||
UUID of 2c7357ed-ebd2-46d9-aec1-23d437ec2bf5 (i.e the partition type for Verity integrity information
|
||||
for x86-64 root file systems).</para></listitem>
|
||||
|
||||
<listitem><para>Finally, a file <literal>https://download.example.com/foobarOS_47.efi.xz</literal> (a
|
||||
unified kernel, as per <ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader
|
||||
Specification</ulink> Type #2) should be downloaded, decompressed and written to the ESP file system,
|
||||
i.e. to <filename>EFI/Linux/foobarOS_47.efi</filename> in the ESP.</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>The version-independent generalization of this would be (using the special marker
|
||||
<literal>@v</literal> as wildcard for the version identifier):</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem><para>A transfer of a file <literal>https://download.example.com/foobarOS_@v.root.xz</literal>
|
||||
→ a local, previously empty GPT partition of type 4f68bce3-e8cd-4db1-96e7-fbcaf984b709, with the label to
|
||||
be set to <literal>foobarOS_@v</literal>.</para></listitem>
|
||||
|
||||
<listitem><para>A transfer of a file <literal>https://download.example.com/foobarOS_@v.verity.xz</literal>
|
||||
→ a local, previously empty GPT partition of type 2c7357ed-ebd2-46d9-aec1-23d437ec2bf5, with the label to be
|
||||
set to <literal>foobarOS_@v_verity</literal>.</para></listitem>
|
||||
|
||||
<listitem><para>A transfer of a file <literal>https://download.example.com/foobarOS_@v.efi.xz</literal>
|
||||
→ a local file <filename>/efi/EFI/Linux/foobarOS_@v.efi</filename>.</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>An update can only complete if the relevant URLs provide their resources for the same version,
|
||||
i.e. for the same value of <literal>@v</literal>.</para>
|
||||
|
||||
<para>The above may be translated into three <filename>*.conf</filename> files in
|
||||
<filename>sysupdate.d/</filename>, one for each resource to transfer. The <filename>*.conf</filename>
|
||||
files configure the type of download, and what place to write the download to (i.e. whether to a
|
||||
partition or a file in the file system). Most importantly these files contain the URL, partition name and
|
||||
filename patterns shown above that describe how these resources are called on the source and how they
|
||||
shall be called on the target.</para>
|
||||
|
||||
<para>In order to enumerate available versions and figuring out candidates to update to, a mechanism is
|
||||
necessary to list suitable files:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>For partitions: the surrounding GPT partition table contains a list of defined
|
||||
partitions, including a partition type UUID and a partition label (in this scheme the partition label
|
||||
plays a role for the partition similar to the filename for a regular file)</para></listitem>
|
||||
|
||||
<listitem><para>For regular files: the directory listing of the directory the files are contained in
|
||||
provides a list of existing files in a straightforward way.</para></listitem>
|
||||
|
||||
<listitem><para>For HTTP/HTTPS sources a simple scheme is used: a manifest file
|
||||
<filename>SHA256SUMS</filename>, following the format defined by <citerefentry
|
||||
project='man-pages'><refentrytitle>sha256sum</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
lists file names and their SHA256 hashes.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>Transfers are done in the alphabetical order of the <filename>.conf</filename> file names they are
|
||||
defined in. First, the resource data is downloaded directly into a target file/directory/partition. Once
|
||||
this is completed for all defined transfers, in a second step the files/directories/partitions are
|
||||
renamed to their final names as defined by the target <varname>MatchPattern=</varname>, again in the
|
||||
order the <filename>.conf</filename> transfer file names dictate. This step is not atomic, however it is
|
||||
guaranteed to be executed strictly in order with suitable disk synchronization in place. Typically, when
|
||||
updating an OS one of the transfers defines the entry point when booting. Thus it is generally a good idea
|
||||
to order the resources via the transfer configuration file names so that the entry point is written
|
||||
last, ensuring that any abnormal termination does not leave an entry point around whose backing is not
|
||||
established yet. In the example above it would hence make sense to establish the EFI kernel image last
|
||||
and thus give its transfer configuration file the alphabetically last name.</para>
|
||||
|
||||
<para>See below for an extended, more specific example based on the above.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Resource Types</title>
|
||||
|
||||
<para>Each transfer file defines one source resource to transfer to one target resource. The following
|
||||
resource types are supported:</para>
|
||||
|
||||
<orderedlist>
|
||||
|
||||
<listitem><para>Resources of type <literal>url-file</literal> encapsulate a file on a web server,
|
||||
referenced via a HTTP or HTTPS URL. When an update takes place, the file is downloaded and decompressed
|
||||
and then written to the target file or partition. This resource type is only available for sources, not
|
||||
for targets. The list of available versions of resources of this type is encoded in
|
||||
<filename>SHA256SUMS</filename> manifest files, accompanied by
|
||||
<filename>SHA256SUMS.gpg</filename> detached signatures.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>url-tar</literal> resource type is similar, but the file must be a
|
||||
<filename>.tar</filename> archive. When an update takes place, the file is decompressed and unpacked
|
||||
into a directory or btrfs subvolume. This resource type is only available for sources, not for
|
||||
targets. Just like <literal>url-file</literal>, <literal>url-tar</literal> version enumeration makes
|
||||
use of <filename>SHA256SUMS</filename> files, authenticated via
|
||||
<filename>SHA256SUMS.gpg</filename>.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>regular-file</literal> resource type encapsulates a local regular file on
|
||||
disk. During updates the file is uncompressed and written to the target file or partition. This
|
||||
resource type is available both as source and as target. When updating no integrity or authentication
|
||||
verification is done for resources of this type.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>partition</literal> resource type is similar to
|
||||
<literal>regular-file</literal>, and encapsulates a GPT partition on disk. When updating, the partition
|
||||
must exist already, and have the correct GPT partition type. A partition whose GPT partition label is
|
||||
set to <literal>_empty</literal> is considered empty, and a candidate to place a newly downloaded
|
||||
resource in. The GPT partition label is used to store version information, once a partition is
|
||||
updated. This resource type is only available for target resources.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>tar</literal> resource type encapsulates local <filename>.tar</filename>
|
||||
archive files. When an update takes place, the files are uncompressed and unpacked into a target
|
||||
directory or btrfs subvolume. Behaviour of <literal>tar</literal> and <literal>url-tar</literal> is
|
||||
generally similar, but the latter downloads from remote sources, and does integrity and authentication
|
||||
checks while the former does not. The <literal>tar</literal> resource type is only available for source
|
||||
resources.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>directory</literal> resource type encapsulates local directory trees. This
|
||||
type is available both for source and target resources. If an update takes place on a source resource
|
||||
of this type, a recursive copy of the directory is done.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>subvolume</literal> resource type is identical to
|
||||
<literal>directory</literal>, except when used as the target, in which case the file tree is placed in
|
||||
a btrfs subvolume instead of a plain directory, if the backing file system supports it (i.e. is
|
||||
btrfs).</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>As already indicated, only a subset of source and target resource type combinations are
|
||||
supported:</para>
|
||||
|
||||
<table>
|
||||
<title>Resource Types</title>
|
||||
|
||||
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
|
||||
<colspec colname="name" />
|
||||
<colspec colname="explanation" />
|
||||
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Identifier</entry>
|
||||
<entry>Description</entry>
|
||||
<entry>Usable as Source</entry>
|
||||
<entry>When Used as Source: Compatible Targets</entry>
|
||||
<entry>When Used as Source: Integrity + Authentication</entry>
|
||||
<entry>When Used as Source: Decompression</entry>
|
||||
<entry>Usable as Target</entry>
|
||||
<entry>When Used as Target: Compatible Sources</entry>
|
||||
</row>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><constant>url-file</constant></entry>
|
||||
<entry>HTTP/HTTPS files</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>regular-file</constant>, <constant>partition</constant></entry>
|
||||
<entry>yes</entry>
|
||||
<entry>yes</entry>
|
||||
<entry>no</entry>
|
||||
<entry>-</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><constant>url-tar</constant></entry>
|
||||
<entry>HTTP/HTTPS <filename>.tar</filename> archives</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>directory</constant>, <constant>subvolume</constant></entry>
|
||||
<entry>yes</entry>
|
||||
<entry>yes</entry>
|
||||
<entry>no</entry>
|
||||
<entry>-</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><constant>regular-file</constant></entry>
|
||||
<entry>Local files</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>regular-file</constant>, <constant>partition</constant></entry>
|
||||
<entry>no</entry>
|
||||
<entry>yes</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>url-file</constant>, <constant>regular-file</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><constant>partition</constant></entry>
|
||||
<entry>Local GPT partitions</entry>
|
||||
<entry>no</entry>
|
||||
<entry>-</entry>
|
||||
<entry>-</entry>
|
||||
<entry>-</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>url-file</constant>, <constant>regular-file</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><constant>tar</constant></entry>
|
||||
<entry>Local <filename>.tar</filename> archives</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>directory</constant>, <constant>subvolume</constant></entry>
|
||||
<entry>no</entry>
|
||||
<entry>yes</entry>
|
||||
<entry>no</entry>
|
||||
<entry>-</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><constant>directory</constant></entry>
|
||||
<entry>Local directories</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>directory</constant>, <constant>subvolume</constant></entry>
|
||||
<entry>no</entry>
|
||||
<entry>no</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>url-tar</constant>, <constant>tar</constant>, <constant>directory</constant>, <constant>subvolume</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><constant>subvolume</constant></entry>
|
||||
<entry>Local btrfs subvolumes</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>directory</constant>, <constant>subvolume</constant></entry>
|
||||
<entry>no</entry>
|
||||
<entry>no</entry>
|
||||
<entry>yes</entry>
|
||||
<entry><constant>url-tar</constant>, <constant>tar</constant>, <constant>directory</constant>, <constant>subvolume</constant></entry>
|
||||
</row>
|
||||
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Match Patterns</title>
|
||||
|
||||
<para>Both the source and target resources typically exist in multiple versions concurrently. An update
|
||||
operation is done whenever the newest of the source versions is newer than the newest of the target
|
||||
versions. To determine the newest version of the resources a directory listing, partition listing or
|
||||
manifest listing is used, a subset of qualifying entries selected from that, and the version identifier
|
||||
extracted from the file names or partition labels of these selected entries. Subset selection and
|
||||
extraction of the version identifier (plus potentially other metadata) is done via match patterns,
|
||||
configured in <varname>MatchPattern=</varname> in the [Source] and [Target] sections. These patterns are
|
||||
strings that describe how files or partitions are named, with named wildcards for specific fields such as
|
||||
the version identifier. The following wildcards are defined:</para>
|
||||
|
||||
<table>
|
||||
<title>Match Pattern Wildcards</title>
|
||||
|
||||
<tgroup cols='2' align='left' colsep='1' rowsep='1'>
|
||||
<colspec colname="name" />
|
||||
<colspec colname="explanation" />
|
||||
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Wildcard</entry>
|
||||
<entry>Description</entry>
|
||||
<entry>Format</entry>
|
||||
<entry>Notes</entry>
|
||||
</row>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>@v</literal></entry>
|
||||
<entry>Version identifier</entry>
|
||||
<entry>Valid version string</entry>
|
||||
<entry>Mandatory</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@u</literal></entry>
|
||||
<entry>GPT partition UUID</entry>
|
||||
<entry>Valid 128-Bit UUID string</entry>
|
||||
<entry>Only relevant if target resource type chosen as <constant>partition</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@f</literal></entry>
|
||||
<entry>GPT partition flags</entry>
|
||||
<entry>Formatted hexadecimal integer</entry>
|
||||
<entry>Only relevant if target resource type chosen as <constant>partition</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@a</literal></entry>
|
||||
<entry>GPT partition flag NoAuto</entry>
|
||||
<entry>Either <literal>0</literal> or <literal>1</literal></entry>
|
||||
<entry>Controls NoAuto bit of the GPT partition flags, as per <ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions Specification</ulink>; only relevant if target resource type chosen as <constant>partition</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@g</literal></entry>
|
||||
<entry>GPT partition flag GrowFileSystem</entry>
|
||||
<entry>Either <literal>0</literal> or <literal>1</literal></entry>
|
||||
<entry>Controls GrowFileSystem bit of the GPT partition flags, as per <ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions Specification</ulink>; only relevant if target resource type chosen as <constant>partition</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@r</literal></entry>
|
||||
<entry>Read-only flag</entry>
|
||||
<entry>Either <literal>0</literal> or <literal>1</literal></entry>
|
||||
<entry>Controls ReadOnly bit of the GPT partition flags, as per <ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions Specification</ulink> and other output read-only flags, see <varname>ReadOnly=</varname> below.</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@t</literal></entry>
|
||||
<entry>File modification time</entry>
|
||||
<entry>Formatted decimal integer, µs since UNIX epoch Jan 1st 1970</entry>
|
||||
<entry>Only relevant if target resource type chosen as <constant>regular-file</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@m</literal></entry>
|
||||
<entry>File access mode</entry>
|
||||
<entry>Formatted octal integer, in UNIX fashion</entry>
|
||||
<entry>Only relevant if target resource type chosen as <constant>regular-file</constant></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@s</literal></entry>
|
||||
<entry>File size after decompression</entry>
|
||||
<entry>Formatted decimal integer</entry>
|
||||
<entry>Useful for measuring progress and to improve partition allocation logic</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@d</literal></entry>
|
||||
<entry>Tries done</entry>
|
||||
<entry>Formatted decimal integer</entry>
|
||||
<entry>Useful when operating with kernel image files, as per <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot Assessment</ulink></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@l</literal></entry>
|
||||
<entry>Tries left</entry>
|
||||
<entry>Formatted decimal integer</entry>
|
||||
<entry>Useful when operating with kernel images, as per <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot Assessment</ulink></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><literal>@h</literal></entry>
|
||||
<entry>SHA256 hash of compressed file</entry>
|
||||
<entry>64 hexadecimal characters</entry>
|
||||
<entry>The SHA256 hash of the compressed file; not useful for <constant>url-file</constant> or <constant>url-tar</constant> where the SHA256 hash is already included in the manifest file anyway.</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>Of these wildcards only <literal>@v</literal> must be present in a valid pattern, all other
|
||||
wildcards are optional. Each wildcard may be used at most once in each pattern. A typical wildcard
|
||||
matching a file system source image could be <literal>MatchPattern=foobar_@v.raw.xz</literal>, i.e. any file
|
||||
whose name begins with <literal>foobar_</literal>, followed by a version ID and suffixed by
|
||||
<literal>.raw.xz</literal>.</para>
|
||||
|
||||
<para>Do not confuse the <literal>@</literal> pattern matching wildcard prefix with the
|
||||
<literal>%</literal> specifier expansion prefix. The former encapsulate a variable part of a match
|
||||
pattern string, the latter are simple shortcuts that are expanded while the drop-in files are
|
||||
parsed. For details about specifiers, see below.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>[Transfer] Section Options</title>
|
||||
|
||||
<para>This section defines general properties of this transfer.</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>MinVersion=</varname></term>
|
||||
|
||||
<listitem><para>Specifies the minimum version to require for this transfer to take place. If the
|
||||
source or target patterns in this transfer definition match files older than this version they will
|
||||
be considered obsolete, and never be considered for the update operation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ProtectVersion=</varname></term>
|
||||
|
||||
<listitem><para>Takes one or more version strings to mark as "protected". Protected versions are
|
||||
never removed while making room for new, updated versions. This is useful to ensure that the
|
||||
currently booted OS version (or auxiliary resources associated with it) is not replaced/overwritten
|
||||
during updates, in order to avoid runtime file system corruptions.</para>
|
||||
|
||||
<para>Like many of the settings in these configuration files this setting supports specifier
|
||||
expansion. It's particularly useful to set this setting to one of the <literal>%A</literal>,
|
||||
<literal>%B</literal> or <literal>%w</literal> specifiers to automatically refer to the current OS
|
||||
version of the running system. See below for details on supported specifiers.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Verify=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean, defaults to yes. Controls whether to cryptographically verify
|
||||
downloaded resources (specifically: validate the GPG signatures for downloaded
|
||||
<filename>SHA256SUMS</filename> manifest files, via their detached signature files
|
||||
<filename>SHA256SUMS.gpg</filename> in combination with the system keyring
|
||||
<filename>/usr/lib/systemd/import-pubring.gpg</filename> or
|
||||
<filename>/etc/systemd/import-pubring.gpg</filename>).</para>
|
||||
|
||||
<para>This option is essential to provide integrity guarantees for downloaded resources and thus
|
||||
should be left enabled, outside of test environments.</para>
|
||||
|
||||
<para>Note that the downloaded payload files are unconditionally checked against the SHA256 hashes
|
||||
listed in the manifest. This option only controls whether the signatures of these manifests are
|
||||
verified.</para>
|
||||
|
||||
<para>This option only has an effect if the source resource type is selected as
|
||||
<constant>url-file</constant> or <constant>url-tar</constant>, as integrity and authentication
|
||||
checking is only available for transfers from remote sources.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>[Source] Section Options</title>
|
||||
|
||||
<para>This section defines properties of the transfer source:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>Type=</varname></term>
|
||||
|
||||
<listitem><para>Specifies the resource type of the source for the transfer. Takes one of
|
||||
<constant>url-file</constant>, <constant>url-tar</constant>, <constant>tar</constant>,
|
||||
<constant>regular-file</constant>, <constant>directory</constant> or
|
||||
<constant>subvolume</constant>. For details about the resource types, see above. This option is
|
||||
mandatory.</para>
|
||||
|
||||
<para>Note that only some combinations of source and target resource types are supported, see
|
||||
above.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>Path=</varname></term>
|
||||
|
||||
<listitem><para>Specifies where to find source versions of this resource.</para>
|
||||
|
||||
<para>If the source type is selected as <constant>url-file</constant> or
|
||||
<constant>url-tar</constant> this must be a HTTP/HTTPS URL. The URL is suffixed with
|
||||
<filename>/SHA256SUMS</filename> to acquire the manifest file, with
|
||||
<filename>/SHA256SUMS.gpg</filename> to acquire the detached signature file for it, and with the file
|
||||
names listed in the manifest file in case an update is executed and a resource shall be
|
||||
downloaded.</para>
|
||||
|
||||
<para>For all other source resource types this must be a local path in the file system, referring to
|
||||
a local directory to find the versions of this resource in.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>MatchPattern=</varname></term>
|
||||
|
||||
<listitem><para>Specifies one or more file name match patterns that select the subset of files that
|
||||
are update candidates as source for this transfer. See above for details on match patterns.</para>
|
||||
|
||||
<para>This option is mandatory. Any pattern listed must contain at least the <literal>@v</literal>
|
||||
wildcard, so that a version identifier may be extracted from the filename. All other wildcards are
|
||||
optional.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>[Target] Section Options</title>
|
||||
|
||||
<para>This section defines properties of the transfer target:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>Type=</varname></term>
|
||||
|
||||
<listitem><para>Specifies the resource type of the target for the transfer. Takes one of
|
||||
<constant>partition</constant>, <constant>regular-file</constant>, <constant>directory</constant> or
|
||||
<constant>subvolume</constant>. For details about the resource types, see above. This option is
|
||||
mandatory.</para>
|
||||
|
||||
<para>Note that only some combinations of source and target resource types are supported, see above.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Path=</varname></term>
|
||||
|
||||
<listitem><para>Specifies a file system path where to look for already installed versions or place
|
||||
newly downloaded versions of this configured resource. If <varname>Type=</varname> is set to
|
||||
<constant>partition</constant>, expects a path to a (whole) block device node, or the special string
|
||||
<literal>auto</literal> in which case the block device the root file system of the currently booted
|
||||
system is automatically determined and used. If <varname>Type=</varname> is set to
|
||||
<constant>regular-file</constant>, <constant>directory</constant> or <constant>subvolume</constant>,
|
||||
must refer to a path in the local file system referencing the directory to find or place the version
|
||||
files or directories under.</para>
|
||||
|
||||
<para>Note that this mechanism cannot be used to create or remove partitions, in case
|
||||
<varname>Type=</varname> is set to <constant>partition</constant>. Partitions must exist already, and
|
||||
a special partition label <literal>_empty</literal> is used to indicate empty partitions. To
|
||||
automatically generate suitable partitions on first boot, use a tool such as
|
||||
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>MatchPattern=</varname></term>
|
||||
|
||||
<listitem><para>Specifies one or more file name or partition label match patterns that select the
|
||||
subset of files or partitions that are update candidates as targets for this transfer. See above for
|
||||
details on match patterns.</para>
|
||||
|
||||
<para>This option is mandatory. Any pattern listed must contain at least the <literal>@v</literal>
|
||||
wildcard, so that a version identifier may be extracted from the filename. All other wildcards are
|
||||
optional.</para>
|
||||
|
||||
<para>This pattern is both used for matching existing installed versions and for determining the name
|
||||
of new versions to install. If multiple patterns are specified, the first specified is used for
|
||||
naming newly installed versions.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>MatchPartitionType=</varname></term>
|
||||
|
||||
<listitem><para>When the target <varname>Type=</varname> is chosen as <constant>partition</constant>,
|
||||
specifies the GPT partition type to look for. Only partitions of this type are considered, all other
|
||||
partitions are ignored. If not specified, the GPT partition type <constant>linux-generic</constant>
|
||||
is used. Accepts either a literal type UUID or a symbolic type identifier. For a list of supported
|
||||
type identifiers, see the <varname>Type=</varname> setting in
|
||||
<citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PartitionUUID=</varname></term>
|
||||
<term><varname>PartitionFlags=</varname></term>
|
||||
<term><varname>PartitionNoAuto=</varname></term>
|
||||
<term><varname>PartitionGrowFileSystem=</varname></term>
|
||||
|
||||
<listitem><para>When the target <varname>Type=</varname> is picked as <constant>partition</constant>,
|
||||
selects the GPT partition UUID and partition flags to use for the updated partition. Expects a valid
|
||||
UUID string, a hexadecimal integer, or booleans, respectively. If not set, but the source match
|
||||
pattern includes wildcards for these fields (i.e. <literal>@u</literal>, <literal>@f</literal>,
|
||||
<literal>@a</literal>, or <literal>@g</literal>), the values from the patterns are used. If neither
|
||||
configured with wildcards or these explicit settings, the values are left untouched. If both the
|
||||
overall <varname>PartitionFlags=</varname> flags setting and the individual flag settings
|
||||
<varname>PartitionNoAuto=</varname> and <varname>PartitionGrowFileSystem=</varname> are used (or the
|
||||
wildcards for them), then the latter override the former, i.e. the individual flag bit overrides the
|
||||
overall flags value. See <ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable
|
||||
Partitions Specification</ulink> for details about these flags.</para>
|
||||
|
||||
<para>Note that these settings are not used for matching, they only have effect on newly written
|
||||
partitions in case a transfer takes place.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ReadOnly=</varname></term>
|
||||
|
||||
<listitem><para>Controls whether to mark the resulting file, subvolume or partition read-only. If the
|
||||
target type is <constant>partition</constant> this controls the ReadOnly partition flag, as per
|
||||
<ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions
|
||||
Specification</ulink>, similar to the <varname>PartitionNoAuto=</varname> and
|
||||
<varname>PartitionGrowFileSystem=</varname> flags described above. If the target type is
|
||||
<constant>regular-file</constant>, the writable bit is removed from the access mode. If the the
|
||||
target type is <constant>subvolume</constant>, the subvolume will be marked read-only as a
|
||||
whole. Finally, if the target <varname>Type=</varname> is selected as <constant>directory</constant>,
|
||||
the "immutable" file attribute is set, see <citerefentry
|
||||
project='man-pages'><refentrytitle>chattr</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
|
||||
details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Mode=</varname></term>
|
||||
|
||||
<listitem><para>The UNIX file access mode to use for newly created files in case the target resource
|
||||
type is picked as <constant>regular-file</constant>. Expects an octal integer, in typical UNIX
|
||||
fashion. If not set, but the source match pattern includes a wildcard for this field
|
||||
(i.e. <literal>@t</literal>), the value from the pattern is used.</para>
|
||||
|
||||
<para>Note that this setting is not used for matching, it only has an effect on newly written
|
||||
files when a transfer takes place.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>TriesDone=</varname></term>
|
||||
<term><varname>TriesLeft=</varname></term>
|
||||
|
||||
<listitem><para>These options take positive, decimal integers, and control the number of attempts
|
||||
done and left for this file. These settings are useful for managing kernel images, following the
|
||||
scheme defined in <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot
|
||||
Assessment</ulink>, and only have an effect if the target pattern includes the <literal>@d</literal>
|
||||
or <literal>@l</literal> wildcards.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>InstancesMax=</varname></term>
|
||||
|
||||
<listitem><para>Takes a decimal integer equal to or greater than 2. This configures how many concurrent
|
||||
versions of the resource to keep. Whenever a new update is initiated it is made sure that no more
|
||||
than the number of versions specified here minus one exist in the target. Any excess versions are
|
||||
deleted (in case the target <varname>Type=</varname> of <constant>regular-file</constant>,
|
||||
<constant>directory</constant>, <constant>subvolume</constant> is used) or emptied (in case the
|
||||
target <varname>Type=</varname> of <constant>partition</constant> is used; emptying in this case
|
||||
simply means to set the partition label to the special string <literal>_empty</literal>; note that no
|
||||
partitions are actually removed). After an update is completed the number of concurrent versions of
|
||||
the target resources is equal to or below the number specified here.</para>
|
||||
|
||||
<para>Note that this setting may be set differently for each transfer. However, it generally is
|
||||
advisable to keep this setting the same for all transfers, since otherwise incomplete combinations of
|
||||
files or partitions will be left installed.</para>
|
||||
|
||||
<para>If the target <varname>Type=</varname> is selected as <constant>partition</constant>, the number
|
||||
of concurrent versions to keep is additionally restricted by the number of partition slots of the
|
||||
right type in the partition table. i.e. if there are only 2 partition slots for the selected
|
||||
partition type, setting this value larger than 2 is without effect, since no more than 2 concurrent
|
||||
versions could be stored in the image anyway.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>RemoveTemporary=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If this option is enabled (which is the default) before
|
||||
initiating an update, all left-over, incomplete updates from a previous attempt are removed from the
|
||||
target directory. This only has an effect if the target resource <varname>Type=</varname> is selected
|
||||
as <constant>regular-file</constant>, <constant>directory</constant> or
|
||||
<constant>subvolume</constant>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>CurrentSymlink=</varname></term>
|
||||
|
||||
<listitem><para>Takes a symlink name as argument. If this option is used, as the last step of the
|
||||
update a symlink under the specified name is created/updated pointing to the completed update. This
|
||||
is useful in to provide a stable name always pointing to the newest version of the resource. This is
|
||||
only supported if the target resource <varname>Type=</varname> is selected as
|
||||
<constant>regular-file</constant>, <constant>directory</constant> or
|
||||
<constant>subvolume</constant>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Specifiers</title>
|
||||
|
||||
<para>Specifiers may be used in the <varname>MinVersion=</varname>, <varname>ProtectVersion=</varname>,
|
||||
<varname>Path=</varname>, <varname>MatchPattern=</varname> and <varname>CurrentSymlink=</varname>
|
||||
settings. The following expansions are understood:</para>
|
||||
<table class='specifiers'>
|
||||
<title>Specifiers available</title>
|
||||
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
|
||||
<colspec colname="spec" />
|
||||
<colspec colname="mean" />
|
||||
<colspec colname="detail" />
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Specifier</entry>
|
||||
<entry>Meaning</entry>
|
||||
<entry>Details</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="a"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="A"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="b"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="B"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="H"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="l"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="m"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="M"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="o"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="v"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="w"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="W"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="T"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="V"/>
|
||||
<xi:include href="standard-specifiers.xml" xpointer="percent"/>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>Do not confuse the <literal>%</literal> specifier expansion prefix with the <literal>@</literal>
|
||||
pattern matching wildcard prefix. The former are simple shortcuts that are expanded while the drop-in
|
||||
files are parsed, the latter encapsulate a variable part of a match pattern string. For details about
|
||||
pattern matching wildcards, see above.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>Updates for a Verity Enabled Secure OS</title>
|
||||
|
||||
<para>With the following three files we define a root file system partition, a matching Verity
|
||||
partition, and a unified kernel image to update as one. This example is an extension of the example
|
||||
discussed earlier in this man page.</para>
|
||||
|
||||
<para><programlisting># /usr/lib/sysupdate.d/50-verity.conf
|
||||
[Transfer]
|
||||
ProtectVersion=%A
|
||||
|
||||
[Source]
|
||||
Type=url-file
|
||||
Path=https://download.example.com/
|
||||
MatchPattern=foobarOS_@v_@u.verity.xz
|
||||
|
||||
[Target]
|
||||
Type=partition
|
||||
Path=auto
|
||||
MatchPattern=foobarOS_@v_verity
|
||||
MatchPartitionType=root-verity
|
||||
PartitionFlags=0
|
||||
PartitionReadOnly=1</programlisting></para>
|
||||
|
||||
<para>The above defines the update mechanism for the Verity partition of the root file system. Verity
|
||||
partition images are downloaded from
|
||||
<literal>https://download.example.com/foobarOS_@v_@u.verity.xz</literal> and written to a suitable
|
||||
local partition, which is marked read-only. Under the assumption this update is run from the image
|
||||
itself the current image version (i.e. the <literal>%A</literal> specifier) is marked as protected, to
|
||||
ensure it is not corrupted while booted. Note that the partition UUID for the target partition is
|
||||
encoded in the source file name. Fixating the partition UUID can be useful to ensure that
|
||||
<literal>roothash=</literal> on the kernel command line is sufficient to pinpoint both the Verity and
|
||||
root file system partition, and also encode the Verity root level hash (under the assumption the UUID
|
||||
in the file names match their top-level hash, the way
|
||||
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
suggests).</para>
|
||||
|
||||
<para><programlisting># /usr/lib/sysupdate.d/60-root.conf
|
||||
[Transfer]
|
||||
ProtectVersion=%A
|
||||
|
||||
[Source]
|
||||
Type=url-file
|
||||
Path=https://download.example.com/
|
||||
MatchPattern=foobarOS_@v_@u.root.xz
|
||||
|
||||
[Target]
|
||||
Type=partition
|
||||
Path=auto
|
||||
MatchPattern=foobarOS_@v
|
||||
MatchPartitionType=root
|
||||
PartitionFlags=0
|
||||
PartitionReadOnly=1</programlisting></para>
|
||||
|
||||
<para>The above defines a matching transfer definition for the root file system.</para>
|
||||
|
||||
<para><programlisting># /usr/lib/sysupdate.d/70-kernel.conf
|
||||
[Transfer]
|
||||
ProtectVersion=%A
|
||||
|
||||
[Source]
|
||||
Type=url-file
|
||||
Path=https://download.example.com/
|
||||
MatchPattern=foobarOS_@v.efi.xz
|
||||
|
||||
[Target]
|
||||
Type=file
|
||||
Path=/efi/EFI/Linux
|
||||
MatchPattern=foobarOS_@v+@l-@d.efi \
|
||||
foobarOS_@v+@l.efi \
|
||||
foobarOS_@v.efi
|
||||
Mode=0444
|
||||
TriesLeft=3
|
||||
TriesDone=0
|
||||
InstancesMax=2</programlisting></para>
|
||||
|
||||
<para>The above installs a unified kernel image into the ESP (which is mounted to
|
||||
<filename>/efi/</filename>), as per <ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot
|
||||
Loader Specification</ulink> Type #2. This defines three possible patterns for the names of the
|
||||
kernel images images, as per <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot
|
||||
Assessment</ulink>, and ensures when installing new kernels, they are set up with 3 tries left. No
|
||||
more than two parallel kernels are kept.</para>
|
||||
|
||||
<para>With this setup the web server would serve the following files, for a hypothetical version 7 of
|
||||
the OS:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para><filename>SHA256SUMS</filename> – The manifest file, containing available files and their SHA256 hashes</para></listitem>
|
||||
<listitem><para><filename>SHA256SUMS.gpg</filename> – The detached cryptographic signature for the manifest file</para></listitem>
|
||||
<listitem><para><filename>foobarOS_7_8b8186b1-2b4e-4eb6-ad39-8d4d18d2a8fb.verity.xz</filename> – The Verity image for version 7</para></listitem>
|
||||
<listitem><para><filename>foobarOS_7_f4d1234f-3ebf-47c4-b31d-4052982f9a2f.root.xz</filename> – The root file system image for version 7</para></listitem>
|
||||
<listitem><para><filename>foobarOS_7_efi.xz</filename> – The unified kernel image for version 7</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>For each new OS release a new set of the latter three files would be added, each time with an
|
||||
updated version. The <filename>SHA256SUMS</filename> manifest should then be updated accordingly,
|
||||
listing all files for all versions that shall be offered for download.</para>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Updates for Plain Directory Container Image</title>
|
||||
|
||||
<para><programlisting>
|
||||
[Source]
|
||||
Type=url-tar
|
||||
Path=https://download.example.com/
|
||||
MatchPattern=myContainer_@v.tar.gz
|
||||
|
||||
[Target]
|
||||
Type=subvolume
|
||||
Path=/var/lib/machines
|
||||
MatchPattern=myContainer_@v
|
||||
CurrentSymlink=myContainer</programlisting></para>
|
||||
|
||||
<para>On updates this downloads <literal>https://download.example.com/myContainer_@v.tar.gz</literal>
|
||||
and decompresses/unpacks it to <filename>/var/lib/machines/myContainer_@v</filename>. After each update
|
||||
a symlink <filename>/var/lib/machines/myContainer</filename> is created/updated always pointing to the
|
||||
most recent update.</para>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
30
meson.build
30
meson.build
@ -1644,6 +1644,18 @@ conf.set('DEFAULT_DNSSEC_MODE',
|
||||
'DNSSEC_' + default_dnssec.underscorify().to_upper())
|
||||
conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec)
|
||||
|
||||
want_sysupdate = get_option('sysupdate')
|
||||
if want_sysupdate != 'false'
|
||||
have = (conf.get('HAVE_OPENSSL') == 1 and
|
||||
conf.get('HAVE_LIBFDISK') == 1)
|
||||
if want_sysupdate == 'true' and not have
|
||||
error('sysupdate support was requested, but dependencies are not available')
|
||||
endif
|
||||
else
|
||||
have = false
|
||||
endif
|
||||
conf.set10('ENABLE_SYSUPDATE', have)
|
||||
|
||||
want_importd = get_option('importd')
|
||||
if want_importd != 'false'
|
||||
have = (conf.get('HAVE_LIBCURL') == 1 and
|
||||
@ -2006,6 +2018,7 @@ subdir('src/rpm')
|
||||
subdir('src/shutdown')
|
||||
subdir('src/sysext')
|
||||
subdir('src/systemctl')
|
||||
subdir('src/sysupdate')
|
||||
subdir('src/timedate')
|
||||
subdir('src/timesync')
|
||||
subdir('src/tmpfiles')
|
||||
@ -3074,6 +3087,22 @@ if conf.get('ENABLE_REPART') == 1
|
||||
endif
|
||||
endif
|
||||
|
||||
if conf.get('ENABLE_SYSUPDATE') == 1
|
||||
exe = executable(
|
||||
'systemd-sysupdate',
|
||||
systemd_sysupdate_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads,
|
||||
libblkid,
|
||||
libfdisk,
|
||||
libopenssl],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
public_programs += exe
|
||||
endif
|
||||
|
||||
if conf.get('ENABLE_VCONSOLE') == 1
|
||||
executable(
|
||||
'systemd-vconsole-setup',
|
||||
@ -4117,6 +4146,7 @@ foreach tuple : [
|
||||
['rfkill'],
|
||||
['sysext'],
|
||||
['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1],
|
||||
['sysupdate'],
|
||||
['sysusers'],
|
||||
['timedated'],
|
||||
['timesyncd'],
|
||||
|
@ -100,6 +100,8 @@ option('binfmt', type : 'boolean',
|
||||
description : 'support for custom binary formats')
|
||||
option('repart', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'install the systemd-repart tool')
|
||||
option('sysupdate', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'install the systemd-sysupdate tool')
|
||||
option('coredump', type : 'boolean',
|
||||
description : 'install the coredump handler')
|
||||
option('pstore', type : 'boolean',
|
||||
|
22
src/sysupdate/meson.build
Normal file
22
src/sysupdate/meson.build
Normal file
@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
systemd_sysupdate_sources = files('''
|
||||
sysupdate-instance.c
|
||||
sysupdate-instance.h
|
||||
sysupdate-partition.c
|
||||
sysupdate-partition.h
|
||||
sysupdate-pattern.c
|
||||
sysupdate-pattern.h
|
||||
sysupdate-resource.c
|
||||
sysupdate-resource.h
|
||||
sysupdate-transfer.c
|
||||
sysupdate-transfer.h
|
||||
sysupdate-update-set.c
|
||||
sysupdate-update-set.h
|
||||
sysupdate-util.c
|
||||
sysupdate-util.h
|
||||
sysupdate-cache.c
|
||||
sysupdate-cache.h
|
||||
sysupdate.c
|
||||
sysupdate.h
|
||||
'''.split())
|
88
src/sysupdate/sysupdate-cache.c
Normal file
88
src/sysupdate/sysupdate-cache.c
Normal file
@ -0,0 +1,88 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "memory-util.h"
|
||||
#include "sysupdate-cache.h"
|
||||
|
||||
#define WEB_CACHE_ENTRIES_MAX 64U
|
||||
#define WEB_CACHE_ITEM_SIZE_MAX (64U*1024U*1024U)
|
||||
|
||||
static WebCacheItem* web_cache_item_free(WebCacheItem *i) {
|
||||
if (!i)
|
||||
return NULL;
|
||||
|
||||
free(i->url);
|
||||
return mfree(i);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(WebCacheItem*, web_cache_item_free);
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(web_cache_hash_ops, char, string_hash_func, string_compare_func, WebCacheItem, web_cache_item_free);
|
||||
|
||||
int web_cache_add_item(
|
||||
Hashmap **web_cache,
|
||||
const char *url,
|
||||
bool verified,
|
||||
const void *data,
|
||||
size_t size) {
|
||||
|
||||
_cleanup_(web_cache_item_freep) WebCacheItem *item = NULL;
|
||||
_cleanup_free_ char *u = NULL;
|
||||
int r;
|
||||
|
||||
assert(web_cache);
|
||||
assert(url);
|
||||
assert(data || size == 0);
|
||||
|
||||
if (size > WEB_CACHE_ITEM_SIZE_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
item = web_cache_get_item(*web_cache, url, verified);
|
||||
if (item && memcmp_nn(item->data, item->size, data, size) == 0)
|
||||
return 0;
|
||||
|
||||
if (hashmap_size(*web_cache) >= (size_t) (WEB_CACHE_ENTRIES_MAX + !!hashmap_get(*web_cache, url)))
|
||||
return -ENOSPC;
|
||||
|
||||
r = hashmap_ensure_allocated(web_cache, &web_cache_hash_ops);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
u = strdup(url);
|
||||
if (!u)
|
||||
return -ENOMEM;
|
||||
|
||||
item = malloc(offsetof(WebCacheItem, data) + size + 1);
|
||||
if (!item)
|
||||
return -ENOMEM;
|
||||
|
||||
*item = (WebCacheItem) {
|
||||
.url = TAKE_PTR(u),
|
||||
.size = size,
|
||||
.verified = verified,
|
||||
};
|
||||
|
||||
/* Just to be extra paranoid, let's NUL terminate the downloaded buffer */
|
||||
*(uint8_t*) mempcpy(item->data, data, size) = 0;
|
||||
|
||||
web_cache_item_free(hashmap_remove(*web_cache, url));
|
||||
|
||||
r = hashmap_put(*web_cache, item->url, item);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TAKE_PTR(item);
|
||||
return 1;
|
||||
}
|
||||
|
||||
WebCacheItem* web_cache_get_item(Hashmap *web_cache, const char *url, bool verified) {
|
||||
WebCacheItem *i;
|
||||
|
||||
i = hashmap_get(web_cache, url);
|
||||
if (!i)
|
||||
return NULL;
|
||||
|
||||
if (i->verified != verified)
|
||||
return NULL;
|
||||
|
||||
return i;
|
||||
}
|
18
src/sysupdate/sysupdate-cache.h
Normal file
18
src/sysupdate/sysupdate-cache.h
Normal file
@ -0,0 +1,18 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "hashmap.h"
|
||||
|
||||
typedef struct WebCacheItem {
|
||||
char *url;
|
||||
bool verified;
|
||||
size_t size;
|
||||
uint8_t data[];
|
||||
} WebCacheItem;
|
||||
|
||||
/* A simple in-memory cache for downloaded manifests. Very likely multiple transfers will use the same
|
||||
* manifest URLs, hence let's make sure we only download them once within each sysupdate invocation. */
|
||||
|
||||
int web_cache_add_item(Hashmap **cache, const char *url, bool verified, const void *data, size_t size);
|
||||
|
||||
WebCacheItem* web_cache_get_item(Hashmap *cache, const char *url, bool verified);
|
63
src/sysupdate/sysupdate-instance.c
Normal file
63
src/sysupdate/sysupdate-instance.c
Normal file
@ -0,0 +1,63 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "sysupdate-instance.h"
|
||||
|
||||
void instance_metadata_destroy(InstanceMetadata *m) {
|
||||
assert(m);
|
||||
free(m->version);
|
||||
}
|
||||
|
||||
int instance_new(
|
||||
Resource *rr,
|
||||
const char *path,
|
||||
const InstanceMetadata *f,
|
||||
Instance **ret) {
|
||||
|
||||
_cleanup_(instance_freep) Instance *i = NULL;
|
||||
_cleanup_free_ char *p = NULL, *v = NULL;
|
||||
|
||||
assert(rr);
|
||||
assert(path);
|
||||
assert(f);
|
||||
assert(f->version);
|
||||
assert(ret);
|
||||
|
||||
p = strdup(path);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
v = strdup(f->version);
|
||||
if (!v)
|
||||
return log_oom();
|
||||
|
||||
i = new(Instance, 1);
|
||||
if (!i)
|
||||
return log_oom();
|
||||
|
||||
*i = (Instance) {
|
||||
.resource = rr,
|
||||
.metadata = *f,
|
||||
.path = TAKE_PTR(p),
|
||||
.partition_info = PARTITION_INFO_NULL,
|
||||
};
|
||||
|
||||
i->metadata.version = TAKE_PTR(v);
|
||||
|
||||
*ret = TAKE_PTR(i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Instance *instance_free(Instance *i) {
|
||||
if (!i)
|
||||
return NULL;
|
||||
|
||||
instance_metadata_destroy(&i->metadata);
|
||||
|
||||
free(i->path);
|
||||
partition_info_destroy(&i->partition_info);
|
||||
|
||||
return mfree(i);
|
||||
}
|
67
src/sysupdate/sysupdate-instance.h
Normal file
67
src/sysupdate/sysupdate-instance.h
Normal file
@ -0,0 +1,67 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "fs-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef struct InstanceMetadata InstanceMetadata;
|
||||
typedef struct Instance Instance;
|
||||
|
||||
#include "sysupdate-resource.h"
|
||||
#include "sysupdate-partition.h"
|
||||
|
||||
struct InstanceMetadata {
|
||||
/* Various bits of metadata for each instance, that is either derived from the filename/GPT label or
|
||||
* from metadata of the file/partition itself */
|
||||
char *version;
|
||||
sd_id128_t partition_uuid;
|
||||
bool partition_uuid_set;
|
||||
uint64_t partition_flags; /* GPT partition flags */
|
||||
bool partition_flags_set;
|
||||
usec_t mtime;
|
||||
mode_t mode;
|
||||
uint64_t size; /* uncompressed size of the file */
|
||||
uint64_t tries_done, tries_left; /* for boot assessment counters */
|
||||
int no_auto;
|
||||
int read_only;
|
||||
int growfs;
|
||||
uint8_t sha256sum[32]; /* SHA256 sum of the download (i.e. compressed) file */
|
||||
bool sha256sum_set;
|
||||
};
|
||||
|
||||
#define INSTANCE_METADATA_NULL \
|
||||
{ \
|
||||
.mtime = USEC_INFINITY, \
|
||||
.mode = MODE_INVALID, \
|
||||
.size = UINT64_MAX, \
|
||||
.tries_done = UINT64_MAX, \
|
||||
.tries_left = UINT64_MAX, \
|
||||
.no_auto = -1, \
|
||||
.read_only = -1, \
|
||||
.growfs = -1, \
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
/* A pointer back to the resource this belongs to */
|
||||
Resource *resource;
|
||||
|
||||
/* Metadata of this version */
|
||||
InstanceMetadata metadata;
|
||||
|
||||
/* Where we found the instance */
|
||||
char *path;
|
||||
PartitionInfo partition_info;
|
||||
};
|
||||
|
||||
void instance_metadata_destroy(InstanceMetadata *m);
|
||||
|
||||
int instance_new(Resource *rr, const char *path, const InstanceMetadata *f, Instance **ret);
|
||||
Instance *instance_free(Instance *i);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Instance*, instance_free);
|
379
src/sysupdate/sysupdate-partition.c
Normal file
379
src/sysupdate/sysupdate-partition.c
Normal file
@ -0,0 +1,379 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/file.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "extract-word.h"
|
||||
#include "gpt.h"
|
||||
#include "id128-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "sysupdate-partition.h"
|
||||
#include "util.h"
|
||||
|
||||
void partition_info_destroy(PartitionInfo *p) {
|
||||
assert(p);
|
||||
|
||||
p->label = mfree(p->label);
|
||||
p->device = mfree(p->device);
|
||||
}
|
||||
|
||||
static int fdisk_partition_get_attrs_as_uint64(
|
||||
struct fdisk_partition *pa,
|
||||
uint64_t *ret) {
|
||||
|
||||
uint64_t flags = 0;
|
||||
const char *a;
|
||||
int r;
|
||||
|
||||
assert(pa);
|
||||
assert(ret);
|
||||
|
||||
/* Retrieve current flags as uint64_t mask */
|
||||
|
||||
a = fdisk_partition_get_attrs(pa);
|
||||
if (!a) {
|
||||
*ret = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
|
||||
r = extract_first_word(&a, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
if (streq(word, "RequiredPartition"))
|
||||
flags |= GPT_FLAG_REQUIRED_PARTITION;
|
||||
else if (streq(word, "NoBlockIOProtocol"))
|
||||
flags |= GPT_FLAG_NO_BLOCK_IO_PROTOCOL;
|
||||
else if (streq(word, "LegacyBIOSBootable"))
|
||||
flags |= GPT_FLAG_LEGACY_BIOS_BOOTABLE;
|
||||
else {
|
||||
const char *e;
|
||||
unsigned u;
|
||||
|
||||
/* Drop "GUID" prefix if specified */
|
||||
e = startswith(word, "GUID:") ?: word;
|
||||
|
||||
if (safe_atou(e, &u) < 0) {
|
||||
log_debug("Unknown partition flag '%s', ignoring.", word);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (u >= sizeof(flags)*8) { /* partition flags on GPT are 64bit. Let's ignore any further
|
||||
bits should libfdisk report them */
|
||||
log_debug("Partition flag above bit 63 (%s), ignoring.", word);
|
||||
continue;
|
||||
}
|
||||
|
||||
flags |= UINT64_C(1) << u;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = flags;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fdisk_partition_set_attrs_as_uint64(
|
||||
struct fdisk_partition *pa,
|
||||
uint64_t flags) {
|
||||
|
||||
_cleanup_free_ char *attrs = NULL;
|
||||
int r;
|
||||
|
||||
assert(pa);
|
||||
|
||||
for (unsigned i = 0; i < sizeof(flags) * 8; i++) {
|
||||
if (!FLAGS_SET(flags, UINT64_C(1) << i))
|
||||
continue;
|
||||
|
||||
r = strextendf_with_separator(&attrs, ",", "%u", i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return fdisk_partition_set_attrs(pa, strempty(attrs));
|
||||
}
|
||||
|
||||
int read_partition_info(
|
||||
struct fdisk_context *c,
|
||||
struct fdisk_table *t,
|
||||
size_t i,
|
||||
PartitionInfo *ret) {
|
||||
|
||||
_cleanup_free_ char *label_copy = NULL, *device = NULL;
|
||||
const char *pts, *ids, *label;
|
||||
struct fdisk_partition *p;
|
||||
struct fdisk_parttype *pt;
|
||||
uint64_t start, size, flags;
|
||||
sd_id128_t ptid, id;
|
||||
size_t partno;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(t);
|
||||
assert(ret);
|
||||
|
||||
p = fdisk_table_get_partition(t, i);
|
||||
if (!p)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
|
||||
|
||||
if (fdisk_partition_is_used(p) <= 0) {
|
||||
*ret = (PartitionInfo) PARTITION_INFO_NULL;
|
||||
return 0; /* not found! */
|
||||
}
|
||||
|
||||
if (fdisk_partition_has_partno(p) <= 0 ||
|
||||
fdisk_partition_has_start(p) <= 0 ||
|
||||
fdisk_partition_has_size(p) <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a number, position or size.");
|
||||
|
||||
partno = fdisk_partition_get_partno(p);
|
||||
|
||||
start = fdisk_partition_get_start(p);
|
||||
assert(start <= UINT64_MAX / 512U);
|
||||
start *= 512U;
|
||||
|
||||
size = fdisk_partition_get_size(p);
|
||||
assert(size <= UINT64_MAX / 512U);
|
||||
size *= 512U;
|
||||
|
||||
label = fdisk_partition_get_name(p);
|
||||
if (!label)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a label.");
|
||||
|
||||
pt = fdisk_partition_get_type(p);
|
||||
if (!pt)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire type of partition: %m");
|
||||
|
||||
pts = fdisk_parttype_get_string(pt);
|
||||
if (!pts)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire type of partition as string: %m");
|
||||
|
||||
r = sd_id128_from_string(pts, &ptid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse partition type UUID %s: %m", pts);
|
||||
|
||||
ids = fdisk_partition_get_uuid(p);
|
||||
if (!ids)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a UUID.");
|
||||
|
||||
r = sd_id128_from_string(ids, &id);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse partition UUID %s: %m", ids);
|
||||
|
||||
r = fdisk_partition_get_attrs_as_uint64(p, &flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get partition flags: %m");
|
||||
|
||||
r = fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device);
|
||||
if (r != 0)
|
||||
return log_error_errno(r, "Failed to get partition device name: %m");
|
||||
|
||||
label_copy = strdup(label);
|
||||
if (!label_copy)
|
||||
return log_oom();
|
||||
|
||||
*ret = (PartitionInfo) {
|
||||
.partno = partno,
|
||||
.start = start,
|
||||
.size = size,
|
||||
.flags = flags,
|
||||
.type = ptid,
|
||||
.uuid = id,
|
||||
.label = TAKE_PTR(label_copy),
|
||||
.device = TAKE_PTR(device),
|
||||
.no_auto = FLAGS_SET(flags, GPT_FLAG_NO_AUTO) && gpt_partition_type_knows_no_auto(ptid),
|
||||
.read_only = FLAGS_SET(flags, GPT_FLAG_READ_ONLY) && gpt_partition_type_knows_read_only(ptid),
|
||||
.growfs = FLAGS_SET(flags, GPT_FLAG_GROWFS) && gpt_partition_type_knows_growfs(ptid),
|
||||
};
|
||||
|
||||
return 1; /* found! */
|
||||
}
|
||||
|
||||
int find_suitable_partition(
|
||||
const char *device,
|
||||
uint64_t space,
|
||||
sd_id128_t *partition_type,
|
||||
PartitionInfo *ret) {
|
||||
|
||||
_cleanup_(partition_info_destroy) PartitionInfo smallest = PARTITION_INFO_NULL;
|
||||
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
|
||||
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
|
||||
size_t n_partitions;
|
||||
int r;
|
||||
|
||||
assert(device);
|
||||
assert(ret);
|
||||
|
||||
c = fdisk_new_context();
|
||||
if (!c)
|
||||
return log_oom();
|
||||
|
||||
r = fdisk_assign_device(c, device, /* readonly= */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open device '%s': %m", device);
|
||||
|
||||
if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device);
|
||||
|
||||
r = fdisk_get_partitions(c, &t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire partition table: %m");
|
||||
|
||||
n_partitions = fdisk_table_get_nents(t);
|
||||
for (size_t i = 0; i < n_partitions; i++) {
|
||||
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
|
||||
|
||||
r = read_partition_info(c, t, i, &pinfo);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) /* not assigned */
|
||||
continue;
|
||||
|
||||
/* Filter out non-matching partition types */
|
||||
if (partition_type && !sd_id128_equal(pinfo.type, *partition_type))
|
||||
continue;
|
||||
|
||||
if (!streq_ptr(pinfo.label, "_empty")) /* used */
|
||||
continue;
|
||||
|
||||
if (space != UINT64_MAX && pinfo.size < space) /* too small */
|
||||
continue;
|
||||
|
||||
if (smallest.partno != SIZE_MAX && smallest.size <= pinfo.size) /* already found smaller */
|
||||
continue;
|
||||
|
||||
smallest = pinfo;
|
||||
pinfo = (PartitionInfo) PARTITION_INFO_NULL;
|
||||
}
|
||||
|
||||
if (smallest.partno == SIZE_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "No available partition of a suitable size found.");
|
||||
|
||||
*ret = smallest;
|
||||
smallest = (PartitionInfo) PARTITION_INFO_NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int patch_partition(
|
||||
const char *device,
|
||||
const PartitionInfo *info,
|
||||
PartitionChange change) {
|
||||
|
||||
_cleanup_(fdisk_unref_partitionp) struct fdisk_partition *pa = NULL;
|
||||
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
|
||||
bool tweak_no_auto, tweak_read_only, tweak_growfs;
|
||||
int r, fd;
|
||||
|
||||
assert(device);
|
||||
assert(info);
|
||||
assert(change <= _PARTITION_CHANGE_MAX);
|
||||
|
||||
if (change == 0) /* Nothing to do */
|
||||
return 0;
|
||||
|
||||
c = fdisk_new_context();
|
||||
if (!c)
|
||||
return log_oom();
|
||||
|
||||
r = fdisk_assign_device(c, device, /* readonly= */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open device '%s': %m", device);
|
||||
|
||||
assert_se((fd = fdisk_get_devfd(c)) >= 0);
|
||||
|
||||
/* Make sure udev doesn't read the device while we make changes (this lock is released automatically
|
||||
* by the kernel when the fd is closed, i.e. when the fdisk context is freed, hence no explicit
|
||||
* unlock by us here anywhere.) */
|
||||
if (flock(fd, LOCK_EX) < 0)
|
||||
return log_error_errno(errno, "Failed to lock block device '%s': %m", device);
|
||||
|
||||
if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device);
|
||||
|
||||
r = fdisk_get_partition(c, info->partno, &pa);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read partition %zu of GPT label of '%s': %m", info->partno, device);
|
||||
|
||||
if (change & PARTITION_LABEL) {
|
||||
r = fdisk_partition_set_name(pa, info->label);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update partition label: %m");
|
||||
}
|
||||
|
||||
if (change & PARTITION_UUID) {
|
||||
r = fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update partition UUID: %m");
|
||||
}
|
||||
|
||||
/* Tweak the read-only flag, but only if supported by the partition type */
|
||||
tweak_no_auto =
|
||||
FLAGS_SET(change, PARTITION_NO_AUTO) &&
|
||||
gpt_partition_type_knows_no_auto(info->type);
|
||||
tweak_read_only =
|
||||
FLAGS_SET(change, PARTITION_READ_ONLY) &&
|
||||
gpt_partition_type_knows_read_only(info->type);
|
||||
tweak_growfs =
|
||||
FLAGS_SET(change, PARTITION_GROWFS) &&
|
||||
gpt_partition_type_knows_growfs(info->type);
|
||||
|
||||
if (change & PARTITION_FLAGS) {
|
||||
uint64_t flags;
|
||||
|
||||
/* Update the full flags parameter, and import the read-only flag into it */
|
||||
|
||||
flags = info->flags;
|
||||
if (tweak_no_auto)
|
||||
SET_FLAG(flags, GPT_FLAG_NO_AUTO, info->no_auto);
|
||||
if (tweak_read_only)
|
||||
SET_FLAG(flags, GPT_FLAG_READ_ONLY, info->read_only);
|
||||
if (tweak_growfs)
|
||||
SET_FLAG(flags, GPT_FLAG_GROWFS, info->growfs);
|
||||
|
||||
r = fdisk_partition_set_attrs_as_uint64(pa, flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update partition flags: %m");
|
||||
|
||||
} else if (tweak_no_auto || tweak_read_only || tweak_growfs) {
|
||||
uint64_t old_flags, new_flags;
|
||||
|
||||
/* So we aren't supposed to update the full flags parameter, but we are supposed to update
|
||||
* the RO flag of it. */
|
||||
|
||||
r = fdisk_partition_get_attrs_as_uint64(pa, &old_flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get old partition flags: %m");
|
||||
|
||||
new_flags = old_flags;
|
||||
if (tweak_no_auto)
|
||||
SET_FLAG(new_flags, GPT_FLAG_NO_AUTO, info->no_auto);
|
||||
if (tweak_read_only)
|
||||
SET_FLAG(new_flags, GPT_FLAG_READ_ONLY, info->read_only);
|
||||
if (tweak_growfs)
|
||||
SET_FLAG(new_flags, GPT_FLAG_GROWFS, info->growfs);
|
||||
|
||||
if (new_flags != old_flags) {
|
||||
r = fdisk_partition_set_attrs_as_uint64(pa, new_flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update partition flags: %m");
|
||||
}
|
||||
}
|
||||
|
||||
r = fdisk_set_partition(c, info->partno, pa);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update partition: %m");
|
||||
|
||||
r = fdisk_write_disklabel(c);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write updated partition table: %m");
|
||||
|
||||
return 0;
|
||||
}
|
49
src/sysupdate/sysupdate-partition.h
Normal file
49
src/sysupdate/sysupdate-partition.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "fdisk-util.h"
|
||||
#include "macro.h"
|
||||
|
||||
typedef struct PartitionInfo PartitionInfo;
|
||||
|
||||
typedef enum PartitionChange {
|
||||
PARTITION_FLAGS = 1 << 0,
|
||||
PARTITION_NO_AUTO = 1 << 1,
|
||||
PARTITION_READ_ONLY = 1 << 2,
|
||||
PARTITION_GROWFS = 1 << 3,
|
||||
PARTITION_UUID = 1 << 4,
|
||||
PARTITION_LABEL = 1 << 5,
|
||||
_PARTITION_CHANGE_MAX = (1 << 6) - 1, /* all of the above */
|
||||
_PARTITION_CHANGE_INVALID = -EINVAL,
|
||||
} PartitionChange;
|
||||
|
||||
struct PartitionInfo {
|
||||
size_t partno;
|
||||
uint64_t start, size;
|
||||
uint64_t flags;
|
||||
sd_id128_t type, uuid;
|
||||
char *label;
|
||||
char *device; /* Note that this might point to some non-existing path in case we operate on a loopback file */
|
||||
bool no_auto:1;
|
||||
bool read_only:1;
|
||||
bool growfs:1;
|
||||
};
|
||||
|
||||
#define PARTITION_INFO_NULL \
|
||||
{ \
|
||||
.partno = SIZE_MAX, \
|
||||
.start = UINT64_MAX, \
|
||||
.size = UINT64_MAX, \
|
||||
}
|
||||
|
||||
void partition_info_destroy(PartitionInfo *p);
|
||||
|
||||
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
|
||||
|
||||
int find_suitable_partition(const char *device, uint64_t space, sd_id128_t *partition_type, PartitionInfo *ret);
|
||||
int patch_partition(const char *device, const PartitionInfo *info, PartitionChange change);
|
605
src/sysupdate/sysupdate-pattern.c
Normal file
605
src/sysupdate/sysupdate-pattern.c
Normal file
@ -0,0 +1,605 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "list.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "sysupdate-pattern.h"
|
||||
#include "sysupdate-util.h"
|
||||
|
||||
typedef enum PatternElementType {
|
||||
PATTERN_LITERAL,
|
||||
PATTERN_VERSION,
|
||||
PATTERN_PARTITION_UUID,
|
||||
PATTERN_PARTITION_FLAGS,
|
||||
PATTERN_MTIME,
|
||||
PATTERN_MODE,
|
||||
PATTERN_SIZE,
|
||||
PATTERN_TRIES_DONE,
|
||||
PATTERN_TRIES_LEFT,
|
||||
PATTERN_NO_AUTO,
|
||||
PATTERN_READ_ONLY,
|
||||
PATTERN_GROWFS,
|
||||
PATTERN_SHA256SUM,
|
||||
_PATTERN_ELEMENT_TYPE_MAX,
|
||||
_PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
|
||||
} PatternElementType;
|
||||
|
||||
typedef struct PatternElement PatternElement;
|
||||
|
||||
struct PatternElement {
|
||||
PatternElementType type;
|
||||
LIST_FIELDS(PatternElement, elements);
|
||||
char literal[];
|
||||
};
|
||||
|
||||
static PatternElement *pattern_element_free_all(PatternElement *e) {
|
||||
PatternElement *p;
|
||||
|
||||
while ((p = LIST_POP(elements, e)))
|
||||
free(p);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
|
||||
|
||||
static PatternElementType pattern_element_type_from_char(char c) {
|
||||
switch (c) {
|
||||
case 'v':
|
||||
return PATTERN_VERSION;
|
||||
case 'u':
|
||||
return PATTERN_PARTITION_UUID;
|
||||
case 'f':
|
||||
return PATTERN_PARTITION_FLAGS;
|
||||
case 't':
|
||||
return PATTERN_MTIME;
|
||||
case 'm':
|
||||
return PATTERN_MODE;
|
||||
case 's':
|
||||
return PATTERN_SIZE;
|
||||
case 'd':
|
||||
return PATTERN_TRIES_DONE;
|
||||
case 'l':
|
||||
return PATTERN_TRIES_LEFT;
|
||||
case 'a':
|
||||
return PATTERN_NO_AUTO;
|
||||
case 'r':
|
||||
return PATTERN_READ_ONLY;
|
||||
case 'g':
|
||||
return PATTERN_GROWFS;
|
||||
case 'h':
|
||||
return PATTERN_SHA256SUM;
|
||||
default:
|
||||
return _PATTERN_ELEMENT_TYPE_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static bool valid_char(char x) {
|
||||
|
||||
/* Let's refuse control characters here, and let's reserve some characters typically used in pattern
|
||||
* languages so that we can use them later, possibly. */
|
||||
|
||||
if ((unsigned) x < ' ' || x >= 127)
|
||||
return false;
|
||||
|
||||
return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '/', '|');
|
||||
}
|
||||
|
||||
static int pattern_split(
|
||||
const char *pattern,
|
||||
PatternElement **ret) {
|
||||
|
||||
_cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
|
||||
bool at = false, last_literal = true;
|
||||
PatternElement *last = NULL;
|
||||
uint64_t mask_found = 0;
|
||||
size_t l, k = 0;
|
||||
|
||||
assert(pattern);
|
||||
|
||||
l = strlen(pattern);
|
||||
|
||||
for (const char *e = pattern; *e != 0; e++) {
|
||||
if (*e == '@') {
|
||||
if (!at) {
|
||||
at = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Two at signs in a sequence, write out one */
|
||||
at = false;
|
||||
|
||||
} else if (at) {
|
||||
PatternElementType t;
|
||||
uint64_t bit;
|
||||
|
||||
t = pattern_element_type_from_char(*e);
|
||||
if (t < 0)
|
||||
return log_debug_errno(t, "Unknown pattern field marker '@%c'.", *e);
|
||||
|
||||
bit = UINT64_C(1) << t;
|
||||
if (mask_found & bit)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern field marker '@%c' appears twice in pattern.", *e);
|
||||
|
||||
/* We insist that two pattern field markers are separated by some literal string that
|
||||
* we can use to separate the fields when parsing. */
|
||||
if (!last_literal)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
|
||||
|
||||
if (ret) {
|
||||
PatternElement *z;
|
||||
|
||||
z = malloc(offsetof(PatternElement, literal));
|
||||
if (!z)
|
||||
return -ENOMEM;
|
||||
|
||||
z->type = t;
|
||||
LIST_INSERT_AFTER(elements, first, last, z);
|
||||
last = z;
|
||||
}
|
||||
|
||||
mask_found |= bit;
|
||||
last_literal = at = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!valid_char(*e))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADRQC), "Invalid character 0x%0x in pattern, refusing.", *e);
|
||||
|
||||
last_literal = true;
|
||||
|
||||
if (!ret)
|
||||
continue;
|
||||
|
||||
if (!last || last->type != PATTERN_LITERAL) {
|
||||
PatternElement *z;
|
||||
|
||||
z = malloc0(offsetof(PatternElement, literal) + l + 1); /* l is an upper bound to all literal elements */
|
||||
if (!z)
|
||||
return -ENOMEM;
|
||||
|
||||
z->type = PATTERN_LITERAL;
|
||||
k = 0;
|
||||
|
||||
LIST_INSERT_AFTER(elements, first, last, z);
|
||||
last = z;
|
||||
}
|
||||
|
||||
assert(last);
|
||||
assert(last->type == PATTERN_LITERAL);
|
||||
|
||||
last->literal[k++] = *e;
|
||||
}
|
||||
|
||||
if (at)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Trailing @ character found, refusing.");
|
||||
if (!(mask_found & (UINT64_C(1) << PATTERN_VERSION)))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Version field marker '@v' not specified in pattern, refusing.");
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(first);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
|
||||
_cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
|
||||
_cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
|
||||
PatternElement *e;
|
||||
const char *p;
|
||||
int r;
|
||||
|
||||
assert(pattern);
|
||||
assert(s);
|
||||
|
||||
r = pattern_split(pattern, &elements);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p = s;
|
||||
LIST_FOREACH(elements, e, elements) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
const char *n;
|
||||
|
||||
if (e->type == PATTERN_LITERAL) {
|
||||
const char *k;
|
||||
|
||||
/* Skip literal fields */
|
||||
k = startswith(p, e->literal);
|
||||
if (!k)
|
||||
goto nope;
|
||||
|
||||
p = k;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e->elements_next) {
|
||||
/* The next element must be literal, as we use it to determine where to split */
|
||||
assert(e->elements_next->type == PATTERN_LITERAL);
|
||||
|
||||
n = strstr(p, e->elements_next->literal);
|
||||
if (!n)
|
||||
goto nope;
|
||||
|
||||
} else
|
||||
/* End of the string */
|
||||
assert_se(n = strchr(p, 0));
|
||||
t = strndup(p, n - p);
|
||||
if (!t)
|
||||
return -ENOMEM;
|
||||
|
||||
switch (e->type) {
|
||||
|
||||
case PATTERN_VERSION:
|
||||
if (!version_is_valid(t)) {
|
||||
log_debug("Version string is not valid, refusing: %s", t);
|
||||
goto nope;
|
||||
}
|
||||
|
||||
assert(!found.version);
|
||||
found.version = TAKE_PTR(t);
|
||||
break;
|
||||
|
||||
case PATTERN_PARTITION_UUID: {
|
||||
sd_id128_t id;
|
||||
|
||||
if (sd_id128_from_string(t, &id) < 0)
|
||||
goto nope;
|
||||
|
||||
assert(!found.partition_uuid_set);
|
||||
found.partition_uuid = id;
|
||||
found.partition_uuid_set = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_PARTITION_FLAGS: {
|
||||
uint64_t f;
|
||||
|
||||
if (safe_atoux64(t, &f) < 0)
|
||||
goto nope;
|
||||
|
||||
if (found.partition_flags_set && found.partition_flags != f)
|
||||
goto nope;
|
||||
|
||||
assert(!found.partition_flags_set);
|
||||
found.partition_flags = f;
|
||||
found.partition_flags_set = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_MTIME: {
|
||||
uint64_t v;
|
||||
|
||||
if (safe_atou64(t, &v) < 0)
|
||||
goto nope;
|
||||
if (v == USEC_INFINITY) /* Don't permit our internal special infinity value */
|
||||
goto nope;
|
||||
if (v / 1000000U > TIME_T_MAX) /* Make sure this fits in a timespec structure */
|
||||
goto nope;
|
||||
|
||||
assert(found.mtime == USEC_INFINITY);
|
||||
found.mtime = v;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_MODE: {
|
||||
mode_t m;
|
||||
|
||||
r = parse_mode(t, &m);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
if (m & ~0775) /* Don't allow world-writable files or suid files to be generated this way */
|
||||
goto nope;
|
||||
|
||||
assert(found.mode == MODE_INVALID);
|
||||
found.mode = m;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_SIZE: {
|
||||
uint64_t u;
|
||||
|
||||
r = safe_atou64(t, &u);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
if (u == UINT64_MAX)
|
||||
goto nope;
|
||||
|
||||
assert(found.size == UINT64_MAX);
|
||||
found.size = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_TRIES_DONE: {
|
||||
uint64_t u;
|
||||
|
||||
r = safe_atou64(t, &u);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
if (u == UINT64_MAX)
|
||||
goto nope;
|
||||
|
||||
assert(found.tries_done == UINT64_MAX);
|
||||
found.tries_done = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_TRIES_LEFT: {
|
||||
uint64_t u;
|
||||
|
||||
r = safe_atou64(t, &u);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
if (u == UINT64_MAX)
|
||||
goto nope;
|
||||
|
||||
assert(found.tries_left == UINT64_MAX);
|
||||
found.tries_left = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_NO_AUTO:
|
||||
r = parse_boolean(t);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
|
||||
assert(found.no_auto < 0);
|
||||
found.no_auto = r;
|
||||
break;
|
||||
|
||||
case PATTERN_READ_ONLY:
|
||||
r = parse_boolean(t);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
|
||||
assert(found.read_only < 0);
|
||||
found.read_only = r;
|
||||
break;
|
||||
|
||||
case PATTERN_GROWFS:
|
||||
r = parse_boolean(t);
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
|
||||
assert(found.growfs < 0);
|
||||
found.growfs = r;
|
||||
break;
|
||||
|
||||
case PATTERN_SHA256SUM: {
|
||||
_cleanup_free_ void *d = NULL;
|
||||
size_t l;
|
||||
|
||||
if (strlen(t) != sizeof(found.sha256sum) * 2)
|
||||
goto nope;
|
||||
|
||||
r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l);
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
if (r < 0)
|
||||
goto nope;
|
||||
|
||||
assert(!found.sha256sum_set);
|
||||
assert(l == sizeof(found.sha256sum));
|
||||
memcpy(found.sha256sum, d, l);
|
||||
found.sha256sum_set = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert_se("unexpected pattern element");
|
||||
}
|
||||
|
||||
p = n;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
*ret = found;
|
||||
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
nope:
|
||||
if (ret)
|
||||
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
|
||||
_cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
|
||||
char **p;
|
||||
int r;
|
||||
|
||||
STRV_FOREACH(p, patterns) {
|
||||
r = pattern_match(*p, s, &found);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
if (ret) {
|
||||
*ret = found;
|
||||
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int pattern_valid(const char *pattern) {
|
||||
int r;
|
||||
|
||||
r = pattern_split(pattern, NULL);
|
||||
if (r == -EINVAL)
|
||||
return false;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int pattern_format(
|
||||
const char *pattern,
|
||||
const InstanceMetadata *fields,
|
||||
char **ret) {
|
||||
|
||||
_cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
|
||||
_cleanup_free_ char *j = NULL;
|
||||
PatternElement *e;
|
||||
int r;
|
||||
|
||||
assert(pattern);
|
||||
assert(fields);
|
||||
assert(ret);
|
||||
|
||||
r = pattern_split(pattern, &elements);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
LIST_FOREACH(elements, e, elements) {
|
||||
|
||||
switch (e->type) {
|
||||
|
||||
case PATTERN_LITERAL:
|
||||
if (!strextend(&j, e->literal))
|
||||
return -ENOMEM;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_VERSION:
|
||||
if (!fields->version)
|
||||
return -ENXIO;
|
||||
|
||||
if (!strextend(&j, fields->version))
|
||||
return -ENOMEM;
|
||||
break;
|
||||
|
||||
case PATTERN_PARTITION_UUID: {
|
||||
char formatted[SD_ID128_STRING_MAX];
|
||||
|
||||
if (!fields->partition_uuid_set)
|
||||
return -ENXIO;
|
||||
|
||||
if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
|
||||
return -ENOMEM;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PATTERN_PARTITION_FLAGS:
|
||||
if (!fields->partition_flags_set)
|
||||
return -ENXIO;
|
||||
|
||||
r = strextendf(&j, "%" PRIx64, fields->partition_flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_MTIME:
|
||||
if (fields->mtime == USEC_INFINITY)
|
||||
return -ENXIO;
|
||||
|
||||
r = strextendf(&j, "%" PRIu64, fields->mtime);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_MODE:
|
||||
if (fields->mode == MODE_INVALID)
|
||||
return -ENXIO;
|
||||
|
||||
r = strextendf(&j, "%03o", fields->mode);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_SIZE:
|
||||
if (fields->size == UINT64_MAX)
|
||||
return -ENXIO;
|
||||
|
||||
r = strextendf(&j, "%" PRIu64, fields->size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case PATTERN_TRIES_DONE:
|
||||
if (fields->tries_done == UINT64_MAX)
|
||||
return -ENXIO;
|
||||
|
||||
r = strextendf(&j, "%" PRIu64, fields->tries_done);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case PATTERN_TRIES_LEFT:
|
||||
if (fields->tries_left == UINT64_MAX)
|
||||
return -ENXIO;
|
||||
|
||||
r = strextendf(&j, "%" PRIu64, fields->tries_left);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case PATTERN_NO_AUTO:
|
||||
if (fields->no_auto < 0)
|
||||
return -ENXIO;
|
||||
|
||||
if (!strextend(&j, one_zero(fields->no_auto)))
|
||||
return -ENOMEM;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_READ_ONLY:
|
||||
if (fields->read_only < 0)
|
||||
return -ENXIO;
|
||||
|
||||
if (!strextend(&j, one_zero(fields->read_only)))
|
||||
return -ENOMEM;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_GROWFS:
|
||||
if (fields->growfs < 0)
|
||||
return -ENXIO;
|
||||
|
||||
if (!strextend(&j, one_zero(fields->growfs)))
|
||||
return -ENOMEM;
|
||||
|
||||
break;
|
||||
|
||||
case PATTERN_SHA256SUM: {
|
||||
_cleanup_free_ char *h = NULL;
|
||||
|
||||
if (!fields->sha256sum_set)
|
||||
return -ENXIO;
|
||||
|
||||
h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
|
||||
if (!h)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!strextend(&j, h))
|
||||
return -ENOMEM;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(j);
|
||||
return 0;
|
||||
}
|
12
src/sysupdate/sysupdate-pattern.h
Normal file
12
src/sysupdate/sysupdate-pattern.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sysupdate-instance.h"
|
||||
#include "time-util.h"
|
||||
|
||||
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret);
|
||||
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret);
|
||||
int pattern_valid(const char *pattern);
|
||||
int pattern_format(const char *pattern, const InstanceMetadata *fields, char **ret);
|
633
src/sysupdate/sysupdate-resource.c
Normal file
633
src/sysupdate/sysupdate-resource.c
Normal file
@ -0,0 +1,633 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "blockdev-util.h"
|
||||
#include "chase-symlinks.h"
|
||||
#include "dirent-util.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "glyph-util.h"
|
||||
#include "gpt.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "import-util.h"
|
||||
#include "macro.h"
|
||||
#include "process-util.h"
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-table.h"
|
||||
#include "sysupdate-cache.h"
|
||||
#include "sysupdate-instance.h"
|
||||
#include "sysupdate-pattern.h"
|
||||
#include "sysupdate-resource.h"
|
||||
#include "sysupdate.h"
|
||||
#include "utf8.h"
|
||||
|
||||
void resource_destroy(Resource *rr) {
|
||||
assert(rr);
|
||||
|
||||
free(rr->path);
|
||||
strv_free(rr->patterns);
|
||||
|
||||
for (size_t i = 0; i < rr->n_instances; i++)
|
||||
instance_free(rr->instances[i]);
|
||||
free(rr->instances);
|
||||
}
|
||||
|
||||
static int resource_add_instance(
|
||||
Resource *rr,
|
||||
const char *path,
|
||||
const InstanceMetadata *f,
|
||||
Instance **ret) {
|
||||
|
||||
Instance *i;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
assert(path);
|
||||
assert(f);
|
||||
assert(f->version);
|
||||
|
||||
if (!GREEDY_REALLOC(rr->instances, rr->n_instances + 1))
|
||||
return log_oom();
|
||||
|
||||
r = instance_new(rr, path, f, &i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
rr->instances[rr->n_instances++] = i;
|
||||
|
||||
if (ret)
|
||||
*ret = i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resource_load_from_directory(
|
||||
Resource *rr,
|
||||
mode_t m) {
|
||||
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
|
||||
assert(IN_SET(m, S_IFREG, S_IFDIR));
|
||||
|
||||
d = opendir(rr->path);
|
||||
if (!d) {
|
||||
if (errno == ENOENT) {
|
||||
log_debug("Directory %s does not exist, not loading any resources.", rr->path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
Instance *instance;
|
||||
struct dirent *de;
|
||||
struct stat st;
|
||||
|
||||
errno = 0;
|
||||
de = readdir_no_dot(d);
|
||||
if (!de) {
|
||||
if (errno != 0)
|
||||
return log_error_errno(errno, "Failed to read directory '%s': %m", rr->path);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (de->d_type) {
|
||||
|
||||
case DT_UNKNOWN:
|
||||
break;
|
||||
|
||||
case DT_DIR:
|
||||
if (m != S_IFDIR)
|
||||
continue;
|
||||
|
||||
break;
|
||||
|
||||
case DT_REG:
|
||||
if (m != S_IFREG)
|
||||
continue;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT) < 0) {
|
||||
if (errno == ENOENT) /* Gone by now? */
|
||||
continue;
|
||||
|
||||
return log_error_errno(errno, "Failed to stat %s/%s: %m", rr->path, de->d_name);
|
||||
}
|
||||
|
||||
if ((st.st_mode & S_IFMT) != m)
|
||||
continue;
|
||||
|
||||
r = pattern_match_many(rr->patterns, de->d_name, &extracted_fields);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to match pattern: %m");
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
joined = path_join(rr->path, de->d_name);
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
r = resource_add_instance(rr, joined, &extracted_fields, &instance);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Inherit these from the source, if not explicitly overwritten */
|
||||
if (instance->metadata.mtime == USEC_INFINITY)
|
||||
instance->metadata.mtime = timespec_load(&st.st_mtim) ?: USEC_INFINITY;
|
||||
|
||||
if (instance->metadata.mode == MODE_INVALID)
|
||||
instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resource_load_from_blockdev(Resource *rr) {
|
||||
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
|
||||
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
|
||||
size_t n_partitions;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
|
||||
c = fdisk_new_context();
|
||||
if (!c)
|
||||
return log_oom();
|
||||
|
||||
r = fdisk_assign_device(c, rr->path, /* readonly= */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open device '%s': %m", rr->path);
|
||||
|
||||
if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path);
|
||||
|
||||
r = fdisk_get_partitions(c, &t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire partition table: %m");
|
||||
|
||||
n_partitions = fdisk_table_get_nents(t);
|
||||
for (size_t i = 0; i < n_partitions; i++) {
|
||||
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
||||
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
|
||||
Instance *instance;
|
||||
|
||||
r = read_partition_info(c, t, i, &pinfo);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) /* not assigned */
|
||||
continue;
|
||||
|
||||
/* Check if partition type matches */
|
||||
if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type))
|
||||
continue;
|
||||
|
||||
/* A label of "_empty" means "not used so far" for us */
|
||||
if (streq_ptr(pinfo.label, "_empty")) {
|
||||
rr->n_empty++;
|
||||
continue;
|
||||
}
|
||||
|
||||
r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to match pattern: %m");
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
instance->partition_info = pinfo;
|
||||
pinfo = (PartitionInfo) PARTITION_INFO_NULL;
|
||||
|
||||
/* Inherit data from source if not configured explicitly */
|
||||
if (!instance->metadata.partition_uuid_set) {
|
||||
instance->metadata.partition_uuid = instance->partition_info.uuid;
|
||||
instance->metadata.partition_uuid_set = true;
|
||||
}
|
||||
|
||||
if (!instance->metadata.partition_flags_set) {
|
||||
instance->metadata.partition_flags = instance->partition_info.flags;
|
||||
instance->metadata.partition_flags_set = true;
|
||||
}
|
||||
|
||||
if (instance->metadata.read_only < 0)
|
||||
instance->metadata.read_only = instance->partition_info.read_only;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int download_manifest(
|
||||
const char *url,
|
||||
bool verify_signature,
|
||||
char **ret_buffer,
|
||||
size_t *ret_size) {
|
||||
|
||||
_cleanup_free_ char *buffer = NULL, *suffixed_url = NULL;
|
||||
_cleanup_(close_pairp) int pfd[2] = { -1, -1 };
|
||||
_cleanup_fclose_ FILE *manifest = NULL;
|
||||
size_t size = 0;
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
assert(url);
|
||||
assert(ret_buffer);
|
||||
assert(ret_size);
|
||||
|
||||
/* Download a SHA256SUMS file as manifest */
|
||||
|
||||
r = import_url_append_component(url, "SHA256SUMS", &suffixed_url);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to append SHA256SUMS to URL: %m");
|
||||
|
||||
if (pipe2(pfd, O_CLOEXEC) < 0)
|
||||
return log_error_errno(errno, "Failed to allocate pipe: %m");
|
||||
|
||||
log_info("%s Acquiring manifest file %s…", special_glyph(SPECIAL_GLYPH_DOWNLOAD), suffixed_url);
|
||||
|
||||
r = safe_fork("(sd-pull)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* Child */
|
||||
|
||||
const char *cmdline[] = {
|
||||
"systemd-pull",
|
||||
"raw",
|
||||
"--direct", /* just download the specified URL, don't download anything else */
|
||||
"--verify", verify_signature ? "signature" : "no", /* verify the manifest file */
|
||||
suffixed_url,
|
||||
"-", /* write to stdout */
|
||||
NULL
|
||||
};
|
||||
|
||||
pfd[0] = safe_close(pfd[0]);
|
||||
|
||||
r = rearrange_stdio(-1, pfd[1], STDERR_FILENO);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to rearrange stdin/stdout: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
(void) unsetenv("NOTIFY_SOCKET");
|
||||
execv(pull_binary_path(), (char *const*) cmdline);
|
||||
log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path());
|
||||
_exit(EXIT_FAILURE);
|
||||
};
|
||||
|
||||
pfd[1] = safe_close(pfd[1]);
|
||||
|
||||
/* We'll first load the entire manifest into memory before parsing it. That's because the
|
||||
* systemd-pull tool can validate the download only after its completion, but still pass the data to
|
||||
* us as it runs. We thus need to check the return value of the process *before* parsing, to be
|
||||
* reasonably safe. */
|
||||
|
||||
manifest = fdopen(pfd[0], "r");
|
||||
if (!manifest)
|
||||
return log_error_errno(errno, "Failed allocate FILE object for manifest file: %m");
|
||||
|
||||
TAKE_FD(pfd[0]);
|
||||
|
||||
r = read_full_stream(manifest, &buffer, &size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read manifest file from child: %m");
|
||||
|
||||
manifest = safe_fclose(manifest);
|
||||
|
||||
r = wait_for_terminate_and_check("(sd-pull)", pid, WAIT_LOG);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r != 0)
|
||||
return -EPROTO;
|
||||
|
||||
*ret_buffer = TAKE_PTR(buffer);
|
||||
*ret_size = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resource_load_from_web(
|
||||
Resource *rr,
|
||||
bool verify,
|
||||
Hashmap **web_cache) {
|
||||
|
||||
size_t manifest_size = 0, left = 0;
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
const char *manifest, *p;
|
||||
size_t line_nr = 1;
|
||||
WebCacheItem *ci;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
|
||||
ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL;
|
||||
if (ci) {
|
||||
log_debug("Manifest web cache hit for %s.", rr->path);
|
||||
|
||||
manifest = (char*) ci->data;
|
||||
manifest_size = ci->size;
|
||||
} else {
|
||||
log_debug("Manifest web cache miss for %s.", rr->path);
|
||||
|
||||
r = download_manifest(rr->path, verify, &buf, &manifest_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
manifest = buf;
|
||||
}
|
||||
|
||||
if (memchr(manifest, 0, manifest_size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file has embedded NUL byte, refusing.");
|
||||
if (!utf8_is_valid_n(manifest, manifest_size))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file is not valid UTF-8, refusing.");
|
||||
|
||||
p = manifest;
|
||||
left = manifest_size;
|
||||
|
||||
while (left > 0) {
|
||||
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
_cleanup_free_ void *h = NULL;
|
||||
Instance *instance;
|
||||
const char *e;
|
||||
size_t hlen;
|
||||
|
||||
/* 64 character hash + separator + filename + newline */
|
||||
if (left < 67)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Corrupt manifest at line %zu, refusing.", line_nr);
|
||||
|
||||
if (p[0] == '\\')
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr);
|
||||
|
||||
r = unhexmem(p, 64, &h, &hlen);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
|
||||
|
||||
p += 64, left -= 64;
|
||||
|
||||
if (*p != ' ')
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing space separator at manifest line %zu, refusing.", line_nr);
|
||||
p++, left--;
|
||||
|
||||
if (!IN_SET(*p, '*', ' '))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr);
|
||||
p++, left--;
|
||||
|
||||
e = memchr(p, '\n', left);
|
||||
if (!e)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Truncated manifest file at line %zu, refusing.", line_nr);
|
||||
if (e == p)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty filename specified at manifest line %zu, refusing.", line_nr);
|
||||
|
||||
fn = strndup(p, e - p);
|
||||
if (!fn)
|
||||
return log_oom();
|
||||
|
||||
if (!filename_is_valid(fn))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid filename specified at manifest line %zu, refusing.", line_nr);
|
||||
if (string_has_cc(fn, NULL))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Filename contains control characters at manifest line %zu, refusing.", line_nr);
|
||||
|
||||
r = pattern_match_many(rr->patterns, fn, &extracted_fields);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to match pattern: %m");
|
||||
if (r > 0) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
r = import_url_append_component(rr->path, fn, &path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build instance URL: %m");
|
||||
|
||||
r = resource_add_instance(rr, path, &extracted_fields, &instance);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(hlen == sizeof(instance->metadata.sha256sum));
|
||||
|
||||
if (instance->metadata.sha256sum_set) {
|
||||
if (memcmp(instance->metadata.sha256sum, h, hlen) != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr);
|
||||
} else {
|
||||
memcpy(instance->metadata.sha256sum, h, hlen);
|
||||
instance->metadata.sha256sum_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
left -= (e - p) + 1;
|
||||
p = e + 1;
|
||||
|
||||
line_nr++;
|
||||
}
|
||||
|
||||
if (!ci && web_cache) {
|
||||
r = web_cache_add_item(web_cache, rr->path, verify, manifest, manifest_size);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to add manifest '%s' to cache, ignoring: %m", rr->path);
|
||||
else
|
||||
log_debug("Added manifest '%s' to cache.", rr->path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int instance_cmp(Instance *const*a, Instance *const*b) {
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
assert(b);
|
||||
assert(*a);
|
||||
assert(*b);
|
||||
assert((*a)->metadata.version);
|
||||
assert((*b)->metadata.version);
|
||||
|
||||
/* Newest version at the beginning */
|
||||
r = strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
|
||||
if (r != 0)
|
||||
return -r;
|
||||
|
||||
/* Instances don't have to be uniquely named (uniqueness on partition tables is not enforced at all,
|
||||
* and since we allow multiple matching patterns not even in directories they are unique). Hence
|
||||
* let's order by path as secondary ordering key. */
|
||||
return path_compare((*a)->path, (*b)->path);
|
||||
}
|
||||
|
||||
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) {
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
|
||||
switch (rr->type) {
|
||||
|
||||
case RESOURCE_TAR:
|
||||
case RESOURCE_REGULAR_FILE:
|
||||
r = resource_load_from_directory(rr, S_IFREG);
|
||||
break;
|
||||
|
||||
case RESOURCE_DIRECTORY:
|
||||
case RESOURCE_SUBVOLUME:
|
||||
r = resource_load_from_directory(rr, S_IFDIR);
|
||||
break;
|
||||
|
||||
case RESOURCE_PARTITION:
|
||||
r = resource_load_from_blockdev(rr);
|
||||
break;
|
||||
|
||||
case RESOURCE_URL_FILE:
|
||||
case RESOURCE_URL_TAR:
|
||||
r = resource_load_from_web(rr, verify, web_cache);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
typesafe_qsort(rr->instances, rr->n_instances, instance_cmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Instance* resource_find_instance(Resource *rr, const char *version) {
|
||||
Instance key = {
|
||||
.metadata.version = (char*) version,
|
||||
}, *k = &key;
|
||||
|
||||
return typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_cmp);
|
||||
}
|
||||
|
||||
int resource_resolve_path(
|
||||
Resource *rr,
|
||||
const char *root,
|
||||
const char *node) {
|
||||
|
||||
_cleanup_free_ char *p = NULL;
|
||||
dev_t d;
|
||||
int r;
|
||||
|
||||
assert(rr);
|
||||
|
||||
if (rr->path_auto) {
|
||||
|
||||
/* NB: we don't actually check the backing device of the root fs "/", but of "/usr", in order
|
||||
* to support environments where the root fs is a tmpfs, and the OS itself placed exclusively
|
||||
* in /usr/. */
|
||||
|
||||
if (rr->type != RESOURCE_PARTITION)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Automatic root path discovery only supported for partition resources.");
|
||||
|
||||
if (node) { /* If --image= is specified, directly use the loopback device */
|
||||
r = free_and_strdup_warn(&rr->path, node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (root)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
|
||||
"Block device is not allowed when using --root= mode.");
|
||||
|
||||
r = get_block_device_harder("/usr/", &d);
|
||||
|
||||
} else if (rr->type == RESOURCE_PARTITION) {
|
||||
_cleanup_close_ int fd = -1, real_fd = -1;
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
struct stat st;
|
||||
|
||||
r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(r, "Failed to stat '%s': %m", resolved);
|
||||
|
||||
if (S_ISBLK(st.st_mode) && root)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "When using --root= or --image= access to device nodes is prohibited.");
|
||||
|
||||
if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) {
|
||||
/* Not a directory, hence no need to find backing block device for the path */
|
||||
free_and_replace(rr->path, resolved);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!S_ISDIR(st.st_mode))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Target path '%s' does not refer to regular file, directory or block device, refusing.", rr->path);
|
||||
|
||||
if (node) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */
|
||||
r = free_and_strdup_warn(&rr->path, node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (real_fd < 0)
|
||||
return log_error_errno(real_fd, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr->path);
|
||||
|
||||
r = get_block_device_harder_fd(fd, &d);
|
||||
|
||||
} else if (RESOURCE_IS_FILESYSTEM(rr->type) && root) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
|
||||
r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
|
||||
|
||||
free_and_replace(rr->path, resolved);
|
||||
return 0;
|
||||
} else
|
||||
return 0; /* Otherwise assume there's nothing to resolve */
|
||||
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine block device of file system: %m");
|
||||
|
||||
r = block_get_whole_disk(d, &d);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m");
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
|
||||
|
||||
r = device_path_make_canonical(S_IFBLK, d, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (rr->path)
|
||||
log_info("Automatically discovered block device '%s' from '%s'.", p, rr->path);
|
||||
else
|
||||
log_info("Automatically discovered root block device '%s'.", p);
|
||||
|
||||
free_and_replace(rr->path, p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *resource_type_table[_RESOURCE_TYPE_MAX] = {
|
||||
[RESOURCE_URL_FILE] = "url-file",
|
||||
[RESOURCE_URL_TAR] = "url-tar",
|
||||
[RESOURCE_TAR] = "tar",
|
||||
[RESOURCE_PARTITION] = "partition",
|
||||
[RESOURCE_REGULAR_FILE] = "regular-file",
|
||||
[RESOURCE_DIRECTORY] = "directory",
|
||||
[RESOURCE_SUBVOLUME] = "subvolume",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType);
|
97
src/sysupdate/sysupdate-resource.h
Normal file
97
src/sysupdate/sysupdate-resource.h
Normal file
@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "macro.h"
|
||||
|
||||
/* Forward declare this type so that the headers below can use it */
|
||||
typedef struct Resource Resource;
|
||||
|
||||
#include "sysupdate-instance.h"
|
||||
|
||||
typedef enum ResourceType {
|
||||
RESOURCE_URL_FILE,
|
||||
RESOURCE_URL_TAR,
|
||||
RESOURCE_TAR,
|
||||
RESOURCE_PARTITION,
|
||||
RESOURCE_REGULAR_FILE,
|
||||
RESOURCE_DIRECTORY,
|
||||
RESOURCE_SUBVOLUME,
|
||||
_RESOURCE_TYPE_MAX,
|
||||
_RESOURCE_TYPE_INVALID = -EINVAL,
|
||||
} ResourceType;
|
||||
|
||||
static inline bool RESOURCE_IS_SOURCE(ResourceType t) {
|
||||
return IN_SET(t,
|
||||
RESOURCE_URL_FILE,
|
||||
RESOURCE_URL_TAR,
|
||||
RESOURCE_TAR,
|
||||
RESOURCE_REGULAR_FILE,
|
||||
RESOURCE_DIRECTORY,
|
||||
RESOURCE_SUBVOLUME);
|
||||
}
|
||||
|
||||
static inline bool RESOURCE_IS_TARGET(ResourceType t) {
|
||||
return IN_SET(t,
|
||||
RESOURCE_PARTITION,
|
||||
RESOURCE_REGULAR_FILE,
|
||||
RESOURCE_DIRECTORY,
|
||||
RESOURCE_SUBVOLUME);
|
||||
}
|
||||
|
||||
/* Returns true for all resources that deal with file system objects, i.e. where we operate on top of the
|
||||
* file system layer, instead of below. */
|
||||
static inline bool RESOURCE_IS_FILESYSTEM(ResourceType t) {
|
||||
return IN_SET(t,
|
||||
RESOURCE_TAR,
|
||||
RESOURCE_REGULAR_FILE,
|
||||
RESOURCE_DIRECTORY,
|
||||
RESOURCE_SUBVOLUME);
|
||||
}
|
||||
|
||||
static inline bool RESOURCE_IS_TAR(ResourceType t) {
|
||||
return IN_SET(t,
|
||||
RESOURCE_TAR,
|
||||
RESOURCE_URL_TAR);
|
||||
}
|
||||
|
||||
static inline bool RESOURCE_IS_URL(ResourceType t) {
|
||||
return IN_SET(t,
|
||||
RESOURCE_URL_TAR,
|
||||
RESOURCE_URL_FILE);
|
||||
}
|
||||
|
||||
struct Resource {
|
||||
ResourceType type;
|
||||
|
||||
/* Where to look for instances, and what to match precisely */
|
||||
char *path;
|
||||
bool path_auto; /* automatically find root path (only available if target resource, not source resource) */
|
||||
char **patterns;
|
||||
sd_id128_t partition_type;
|
||||
bool partition_type_set;
|
||||
|
||||
/* All instances of this resource we found */
|
||||
Instance **instances;
|
||||
size_t n_instances;
|
||||
|
||||
/* If this is a partition resource (RESOURCE_PARTITION), then how many partition slots are currently unassigned, that we can use */
|
||||
size_t n_empty;
|
||||
};
|
||||
|
||||
void resource_destroy(Resource *rr);
|
||||
|
||||
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache);
|
||||
|
||||
Instance* resource_find_instance(Resource *rr, const char *version);
|
||||
|
||||
int resource_resolve_path(Resource *rr, const char *root, const char *node);
|
||||
|
||||
ResourceType resource_type_from_string(const char *s) _pure_;
|
||||
const char *resource_type_to_string(ResourceType t) _const_;
|
1247
src/sysupdate/sysupdate-transfer.c
Normal file
1247
src/sysupdate/sysupdate-transfer.c
Normal file
File diff suppressed because it is too large
Load Diff
62
src/sysupdate/sysupdate-transfer.h
Normal file
62
src/sysupdate/sysupdate-transfer.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
/* Forward declare this type so that the headers below can use it */
|
||||
typedef struct Transfer Transfer;
|
||||
|
||||
#include "sysupdate-partition.h"
|
||||
#include "sysupdate-resource.h"
|
||||
|
||||
struct Transfer {
|
||||
char *definition_path;
|
||||
char *min_version;
|
||||
char **protected_versions;
|
||||
char *current_symlink;
|
||||
bool verify;
|
||||
|
||||
Resource source, target;
|
||||
|
||||
uint64_t instances_max;
|
||||
bool remove_temporary;
|
||||
|
||||
/* When creating a new partition/file, optionally override these attributes explicitly */
|
||||
sd_id128_t partition_uuid;
|
||||
bool partition_uuid_set;
|
||||
uint64_t partition_flags;
|
||||
bool partition_flags_set;
|
||||
mode_t mode;
|
||||
uint64_t tries_left, tries_done;
|
||||
int no_auto;
|
||||
int read_only;
|
||||
int growfs;
|
||||
|
||||
/* If we create a new file/dir/subvol in the fs, the temporary and final path we create it under, as well as the read-only flag for it */
|
||||
char *temporary_path;
|
||||
char *final_path;
|
||||
int install_read_only;
|
||||
|
||||
/* If we write to a partition in a partition table, the metrics of it */
|
||||
PartitionInfo partition_info;
|
||||
PartitionChange partition_change;
|
||||
};
|
||||
|
||||
Transfer *transfer_new(void);
|
||||
|
||||
Transfer *transfer_free(Transfer *t);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_free);
|
||||
|
||||
int transfer_read_definition(Transfer *t, const char *path);
|
||||
|
||||
int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
|
||||
|
||||
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
|
||||
|
||||
int transfer_acquire_instance(Transfer *t, Instance *i);
|
||||
|
||||
int transfer_install_instance(Transfer *t, Instance *i, const char *root);
|
63
src/sysupdate/sysupdate-update-set.c
Normal file
63
src/sysupdate/sysupdate-update-set.c
Normal file
@ -0,0 +1,63 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "string-util.h"
|
||||
#include "sysupdate-update-set.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
UpdateSet *update_set_free(UpdateSet *us) {
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
free(us->version);
|
||||
free(us->instances); /* The objects referenced by this array are freed via resource_free(), not us */
|
||||
|
||||
return mfree(us);
|
||||
}
|
||||
|
||||
int update_set_cmp(UpdateSet *const*a, UpdateSet *const*b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
assert(*a);
|
||||
assert(*b);
|
||||
assert((*a)->version);
|
||||
assert((*b)->version);
|
||||
|
||||
/* Newest version at the beginning */
|
||||
return -strverscmp_improved((*a)->version, (*b)->version);
|
||||
}
|
||||
|
||||
const char *update_set_flags_to_color(UpdateSetFlags flags) {
|
||||
|
||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
||||
return ansi_highlight();
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
|
||||
return ansi_highlight_magenta();
|
||||
|
||||
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
|
||||
return ansi_highlight_green();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *update_set_flags_to_glyph(UpdateSetFlags flags) {
|
||||
|
||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||
return special_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN);
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
||||
return special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE);
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
|
||||
return special_glyph(SPECIAL_GLYPH_WHITE_CIRCLE);
|
||||
|
||||
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
|
||||
return special_glyph(SPECIAL_GLYPH_CIRCLE_ARROW);
|
||||
|
||||
return " ";
|
||||
}
|
32
src/sysupdate/sysupdate-update-set.h
Normal file
32
src/sysupdate/sysupdate-update-set.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct UpdateSet UpdateSet;
|
||||
|
||||
#include "sysupdate-instance.h"
|
||||
|
||||
typedef enum UpdateSetFlags {
|
||||
UPDATE_NEWEST = 1 << 0,
|
||||
UPDATE_AVAILABLE = 1 << 1,
|
||||
UPDATE_INSTALLED = 1 << 2,
|
||||
UPDATE_OBSOLETE = 1 << 3,
|
||||
UPDATE_PROTECTED = 1 << 4,
|
||||
} UpdateSetFlags;
|
||||
|
||||
struct UpdateSet {
|
||||
UpdateSetFlags flags;
|
||||
char *version;
|
||||
Instance **instances;
|
||||
size_t n_instances;
|
||||
};
|
||||
|
||||
UpdateSet *update_set_free(UpdateSet *us);
|
||||
|
||||
int update_set_cmp(UpdateSet *const*a, UpdateSet *const*b);
|
||||
|
||||
const char *update_set_flags_to_color(UpdateSetFlags flags);
|
||||
const char *update_set_flags_to_glyph(UpdateSetFlags flags);
|
17
src/sysupdate/sysupdate-util.c
Normal file
17
src/sysupdate/sysupdate-util.c
Normal file
@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "path-util.h"
|
||||
#include "sysupdate-util.h"
|
||||
|
||||
bool version_is_valid(const char *s) {
|
||||
if (isempty(s))
|
||||
return false;
|
||||
|
||||
if (!filename_is_valid(s))
|
||||
return false;
|
||||
|
||||
if (!in_charset(s, ALPHANUMERICAL ".,_-+"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
6
src/sysupdate/sysupdate-util.h
Normal file
6
src/sysupdate/sysupdate-util.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool version_is_valid(const char *s);
|
1412
src/sysupdate/sysupdate.c
Normal file
1412
src/sysupdate/sysupdate.c
Normal file
File diff suppressed because it is too large
Load Diff
21
src/sysupdate/sysupdate.h
Normal file
21
src/sysupdate/sysupdate.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
extern bool arg_sync;
|
||||
extern uint64_t arg_instances_max;
|
||||
extern char *arg_root;
|
||||
|
||||
static inline const char* import_binary_path(void) {
|
||||
return secure_getenv("SYSTEMD_IMPORT_PATH") ?: SYSTEMD_IMPORT_PATH;
|
||||
}
|
||||
|
||||
static inline const char* import_fs_binary_path(void) {
|
||||
return secure_getenv("SYSTEMD_IMPORT_FS_PATH") ?: SYSTEMD_IMPORT_FS_PATH;
|
||||
}
|
||||
|
||||
static inline const char *pull_binary_path(void) {
|
||||
return secure_getenv("SYSTEMD_PULL_PATH") ?: SYSTEMD_PULL_PATH;
|
||||
}
|
1
test/TEST-72-SYSUPDATE/Makefile
Symbolic link
1
test/TEST-72-SYSUPDATE/Makefile
Symbolic link
@ -0,0 +1 @@
|
||||
../TEST-01-BASIC/Makefile
|
16
test/TEST-72-SYSUPDATE/test.sh
Executable file
16
test/TEST-72-SYSUPDATE/test.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
|
||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
|
||||
set -e
|
||||
|
||||
TEST_DESCRIPTION="test sysupdate"
|
||||
|
||||
# shellcheck source=test/test-functions
|
||||
. "${TEST_BASE_DIR:?}/test-functions"
|
||||
|
||||
test_append_files() {
|
||||
inst_binary sha256sum
|
||||
}
|
||||
|
||||
do_test "$@"
|
8
test/units/testsuite-72.service
Normal file
8
test/units/testsuite-72.service
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Unit]
|
||||
Description=TEST-72-SYSUPDATE
|
||||
|
||||
[Service]
|
||||
ExecStartPre=rm -f /failed /testok
|
||||
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
|
||||
Type=oneshot
|
170
test/units/testsuite-72.sh
Executable file
170
test/units/testsuite-72.sh
Executable file
@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
|
||||
# ex: ts=8 sw=4 sts=4 et filetype=sh
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
SYSUPDATE=/lib/systemd/systemd-sysupdate
|
||||
|
||||
if ! test -x "$SYSUPDATE"; then
|
||||
echo "no systemd-sysupdate" >/skipped
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export SYSTEMD_PAGER=cat
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
|
||||
rm -f /var/tmp/72-joined.raw
|
||||
truncate -s 10M /var/tmp/72-joined.raw
|
||||
|
||||
sfdisk /var/tmp/72-joined.raw <<EOF
|
||||
label: gpt
|
||||
unit: sectors
|
||||
sector-size: 512
|
||||
|
||||
size=2048, type=4f68bce3-e8cd-4db1-96e7-fbcaf984b709, name=_empty
|
||||
size=2048, type=4f68bce3-e8cd-4db1-96e7-fbcaf984b709, name=_empty
|
||||
size=2048, type=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5, name=_empty
|
||||
size=2048, type=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5, name=_empty
|
||||
EOF
|
||||
|
||||
rm -rf /var/tmp/72-dirs
|
||||
|
||||
rm -rf /var/tmp/72-defs
|
||||
mkdir -p /var/tmp/72-defs
|
||||
|
||||
cat >/var/tmp/72-defs/01-first.conf <<"EOF"
|
||||
[Source]
|
||||
Type=regular-file
|
||||
Path=/var/tmp/72-source
|
||||
MatchPattern=part1-@v.raw
|
||||
|
||||
[Target]
|
||||
Type=partition
|
||||
Path=/var/tmp/72-joined.raw
|
||||
MatchPattern=part1-@v
|
||||
MatchPartitionType=root-x86-64
|
||||
EOF
|
||||
|
||||
cat >/var/tmp/72-defs/02-second.conf <<"EOF"
|
||||
[Source]
|
||||
Type=regular-file
|
||||
Path=/var/tmp/72-source
|
||||
MatchPattern=part2-@v.raw.gz
|
||||
|
||||
[Target]
|
||||
Type=partition
|
||||
Path=/var/tmp/72-joined.raw
|
||||
MatchPattern=part2-@v
|
||||
MatchPartitionType=root-x86-64-verity
|
||||
EOF
|
||||
|
||||
cat >/var/tmp/72-defs/03-third.conf <<"EOF"
|
||||
[Source]
|
||||
Type=directory
|
||||
Path=/var/tmp/72-source
|
||||
MatchPattern=dir-@v
|
||||
|
||||
[Target]
|
||||
Type=directory
|
||||
Path=/var/tmp/72-dirs
|
||||
CurrentSymlink=/var/tmp/72-dirs/current
|
||||
MatchPattern=dir-@v
|
||||
InstancesMax=3
|
||||
EOF
|
||||
|
||||
rm -rf /var/tmp/72-source
|
||||
mkdir -p /var/tmp/72-source
|
||||
|
||||
new_version() {
|
||||
# Create a pair of random partition payloads, and compress one
|
||||
dd if=/dev/urandom of="/var/tmp/72-source/part1-$1.raw" bs=1024 count=1024
|
||||
dd if=/dev/urandom of="/var/tmp/72-source/part2-$1.raw" bs=1024 count=1024
|
||||
gzip -k -f "/var/tmp/72-source/part2-$1.raw"
|
||||
|
||||
mkdir -p "/var/tmp/72-source/dir-$1"
|
||||
echo $RANDOM >"/var/tmp/72-source/dir-$1/foo.txt"
|
||||
echo $RANDOM >"/var/tmp/72-source/dir-$1/bar.txt"
|
||||
|
||||
tar --numeric-owner -C "/var/tmp/72-source/dir-$1/" -czf "/var/tmp/72-source/dir-$1.tar.gz" .
|
||||
|
||||
( cd /var/tmp/72-source/ && sha256sum part* dir-*.tar.gz >SHA256SUMS )
|
||||
}
|
||||
|
||||
update_now() {
|
||||
# Update to newest version. First there should be an update ready, then we
|
||||
# do the update, and then there should not be any ready anymore
|
||||
|
||||
"$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no check-new
|
||||
"$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no update
|
||||
( ! "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no check-new )
|
||||
}
|
||||
|
||||
verify_version() {
|
||||
# Expects: version ID + sector offset of both partitions to compare
|
||||
dd if=/var/tmp/72-joined.raw bs=1024 skip="$2" count=1024 | cmp "/var/tmp/72-source/part1-$1.raw"
|
||||
dd if=/var/tmp/72-joined.raw bs=1024 skip="$3" count=1024 | cmp "/var/tmp/72-source/part2-$1.raw"
|
||||
cmp "/var/tmp/72-source/dir-$1/foo.txt" /var/tmp/72-dirs/current/foo.txt
|
||||
cmp "/var/tmp/72-source/dir-$1/bar.txt" /var/tmp/72-dirs/current/bar.txt
|
||||
}
|
||||
|
||||
# Install initial version and verify
|
||||
new_version v1
|
||||
update_now
|
||||
verify_version v1 1024 3072
|
||||
|
||||
# Create second version, update and verify that it is added
|
||||
new_version v2
|
||||
update_now
|
||||
verify_version v2 2048 4096
|
||||
|
||||
# Create third version, update and verify it replaced the first version
|
||||
new_version v3
|
||||
update_now
|
||||
verify_version v3 1024 3072
|
||||
|
||||
# Create fourth version, and update through a file:// URL. This should be
|
||||
# almost as good as testing HTTP, but is simpler for us to set up. file:// is
|
||||
# abstracted in curl for us, and since our main goal is to test our own code
|
||||
# (and not curl) this test should be quite good even if not comprehensive. This
|
||||
# will test the SHA256SUMS logic at least (we turn off GPG validation though,
|
||||
# see above)
|
||||
new_version v4
|
||||
|
||||
cat >/var/tmp/72-defs/02-second.conf <<"EOF"
|
||||
[Source]
|
||||
Type=url-file
|
||||
Path=file:///var/tmp/72-source
|
||||
MatchPattern=part2-@v.raw.gz
|
||||
|
||||
[Target]
|
||||
Type=partition
|
||||
Path=/var/tmp/72-joined.raw
|
||||
MatchPattern=part2-@v
|
||||
MatchPartitionType=root-x86-64-verity
|
||||
EOF
|
||||
|
||||
cat >/var/tmp/72-defs/03-third.conf <<"EOF"
|
||||
[Source]
|
||||
Type=url-tar
|
||||
Path=file:///var/tmp/72-source
|
||||
MatchPattern=dir-@v.tar.gz
|
||||
|
||||
[Target]
|
||||
Type=directory
|
||||
Path=/var/tmp/72-dirs
|
||||
CurrentSymlink=/var/tmp/72-dirs/current
|
||||
MatchPattern=dir-@v
|
||||
InstancesMax=3
|
||||
EOF
|
||||
|
||||
update_now
|
||||
verify_version v4 2048 4096
|
||||
|
||||
rm /var/tmp/72-joined.raw
|
||||
rm -r /var/tmp/72-dirs /var/tmp/72-defs /var/tmp/72-source
|
||||
|
||||
echo OK >/testok
|
||||
|
||||
exit 0
|
@ -140,6 +140,8 @@ units = [
|
||||
['systemd-reboot.service', ''],
|
||||
['systemd-rfkill.socket', 'ENABLE_RFKILL'],
|
||||
['systemd-sysext.service', 'ENABLE_SYSEXT'],
|
||||
['systemd-sysupdate.timer', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-sysupdate-reboot.timer', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-sysusers.service', 'ENABLE_SYSUSERS',
|
||||
'sysinit.target.wants/'],
|
||||
['systemd-tmpfiles-clean.service', 'ENABLE_TMPFILES'],
|
||||
@ -236,6 +238,8 @@ in_units = [
|
||||
['systemd-suspend.service', ''],
|
||||
['systemd-sysctl.service', '',
|
||||
'sysinit.target.wants/'],
|
||||
['systemd-sysupdate.service', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-sysupdate-reboot.service', 'ENABLE_SYSUPDATE'],
|
||||
['systemd-timedated.service', 'ENABLE_TIMEDATED',
|
||||
'dbus-org.freedesktop.timedate1.service'],
|
||||
['systemd-timesyncd.service', 'ENABLE_TIMESYNCD'],
|
||||
|
20
units/systemd-sysupdate-reboot.service.in
Normal file
20
units/systemd-sysupdate-reboot.service.in
Normal file
@ -0,0 +1,20 @@
|
||||
# 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=Reboot Automatically After System Update
|
||||
Documentation=man:systemd-sysupdate-reboot.service(8)
|
||||
ConditionVirtualization=!container
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart={{ROOTLIBEXECDIR}}/systemd-sysupdate reboot
|
||||
|
||||
[Install]
|
||||
Also=systemd-sysupdate-reboot.timer
|
20
units/systemd-sysupdate-reboot.timer
Normal file
20
units/systemd-sysupdate-reboot.timer
Normal file
@ -0,0 +1,20 @@
|
||||
# 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=Reboot Automatically After System Update
|
||||
Documentation=man:systemd-sysupdate-reboot.service(8)
|
||||
ConditionVirtualization=!container
|
||||
|
||||
[Timer]
|
||||
OnCalendar=4:10
|
||||
RandomizedDelaySec=30min
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
34
units/systemd-sysupdate.service.in
Normal file
34
units/systemd-sysupdate.service.in
Normal file
@ -0,0 +1,34 @@
|
||||
# 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=Automatic System Update
|
||||
Documentation=man:systemd-sysupdate.service(8)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
ConditionVirtualization=!container
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
NotifyAccess=main
|
||||
ExecStart={{ROOTLIBEXECDIR}}/systemd-sysupdate update
|
||||
CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD CAP_SETFCAP CAP_SYS_ADMIN CAP_SETPCAP CAP_DAC_OVERRIDE CAP_LINUX_IMMUTABLE
|
||||
NoNewPrivileges=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
ProtectHostname=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=net
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
SystemCallFilter=@system-service @mount
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallArchitectures=native
|
||||
LockPersonality=yes
|
||||
|
||||
[Install]
|
||||
Also=systemd-sysupdate.timer
|
30
units/systemd-sysupdate.timer
Normal file
30
units/systemd-sysupdate.timer
Normal file
@ -0,0 +1,30 @@
|
||||
# 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=Automatic System Update
|
||||
Documentation=man:systemd-sysupdate.service(8)
|
||||
|
||||
# For containers we assume that the manager will handle updates. And we likely
|
||||
# can't even access our backing block device anyway.
|
||||
ConditionVirtualization=!container
|
||||
|
||||
[Timer]
|
||||
# Trigger the update 15min after boot, and then – on average – every 6h, but
|
||||
# randomly distributed in a 2h…6h interval. In addition trigger things
|
||||
# persistently once on each saturday, to ensure that even on systems that are
|
||||
# never booted up for long we have a chance to to do the update.
|
||||
OnBootSec=15min
|
||||
OnUnitActiveSec=2h
|
||||
OnCalendar=Sat
|
||||
RandomizedDelaySec=4h
|
||||
Persistent=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
Loading…
x
Reference in New Issue
Block a user