Merge pull request #26663 from poettering/vpick

add new "vpick" concept for automatically picking newest resource from .v/ dir containing versioned files
This commit is contained in:
Lennart Poettering
2024-01-03 22:17:32 +01:00
committed by GitHub
30 changed files with 2401 additions and 128 deletions

42
TODO
View File

@@ -473,6 +473,17 @@ Features:
line, and then generate a mount unit for it using a udev generated symlink
based on lo_file_name.
* teach systemd-nspawn the boot assessment logic: hook up vpick's try counters
with success notifications from nspawn payloads. When this is enabled,
automatically support reverting back to older OS versin images if newer ones
fail to boot.
* implement new "systemd-fsrebind" tool that works like gpt-auto-generator but
looks at a root dir and then applies vpick on various dirs/images to pick a
root tree, a /usr/ tree, a /home/, a /srv/, a /var/ tree and so on. Dirs
could also be btrfs subvols (combine with btrfs auto-snapshort approach for
creating versions like these automatically).
* remove tomoyo support, it's obsolete and unmaintained apparently
* In .socket units, add ConnectStream=, ConnectDatagram=,
@@ -704,17 +715,6 @@ Features:
* automatic boot assessment: add one more default success check that just waits
for a bit after boot, and blesses the boot if the system stayed up that long.
* implement concept of "versioned" resources inside a dir, and write a spec for
it. Make all tools in systemd, in particular
RootImage=/RootDirectory=/--image=/--directory= implement this. Idea:
directories ending in ".v/" indicate a directory with versioned resources in
them. Versioned resources inside a .v dir are always named in the pattern
<prefix>_<version>[+<tries-left>[-<tries-done>]].<suffix>
* add support for using this .v/ logic on the root fs itself: in the initrd,
after mounting the rootfs, look for root-<arch>.v/ in the root fs, and then
apply the logic, moving the switch root logic there.
* systemd-repart: add support for generating ISO9660 images
* systemd-repart: in addition to the existing "factory reset" mode (which
@@ -1170,26 +1170,6 @@ Features:
passwords, not just the first. i.e. if there are multiple defined, prefer
unlocked over locked and prefer non-empty over empty.
* maybe add a tool inspired by the GPT auto discovery spec that runs in the
initrd and rearranges the rootfs hierarchy via bind mounts, if
enabled. Specifically in some top-level dir /@auto/ it will look for
dirs/symlinks/subvolumes that are named after their purpose, and optionally
encode a version as well as assessment counters, and then mount them into the
file system tree to boot into, similar to how we do that for the gpt auto
logic. Maybe then bind mount the original root into /.superior or something
like that (so that update tools can look there). Further discussion in this
thread:
https://lists.freedesktop.org/archives/systemd-devel/2021-November/047059.html
The GPT dissection logic should automatically enable this tool whenever we
detect a specially marked root fs (i.e introduce a new generic root gpt type
for this, that is arch independent). The also implement this in the image
dissection logic, so that nspawn/RootImage= and so on grok it. Maybe make
generic enough so that it can also work for ostrees arrangements.
* if a path ending in ".auto.d/" is set for RootDirectory=/RootImage= then do a
strverscmp() of everything inside that dir and use that. i.e. implement very
simple version control. Also use this in systemd-nspawn --image= and so on.
* homed: while a home dir is not activated generate slightly different NSS
records for it, that reports the home dir as "/" and the shell as some binary
provided by us. Then, when an SSH login happens and SSH permits it our binary

View File

@@ -1131,6 +1131,7 @@ manpages = [
'HAVE_LIBCRYPTSETUP'],
['systemd-vmspawn', '1', [], 'ENABLE_VMSPAWN'],
['systemd-volatile-root.service', '8', ['systemd-volatile-root'], ''],
['systemd-vpick', '1', [], ''],
['systemd-xdg-autostart-generator', '8', [], 'ENABLE_XDG_AUTOSTART'],
['systemd', '1', ['init'], ''],
['systemd.automount', '5', [], ''],
@@ -1165,6 +1166,7 @@ manpages = [
['systemd.time', '7', [], ''],
['systemd.timer', '5', [], ''],
['systemd.unit', '5', [], ''],
['systemd.v', '7', [], ''],
['sysupdate.d', '5', [], 'ENABLE_SYSUPDATE'],
['sysusers.d', '5', [], 'ENABLE_SYSUSERS'],
['telinit', '8', [], 'HAVE_SYSV_COMPAT'],

View File

@@ -120,6 +120,10 @@
mounted directly by <command>mount</command> and <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>. For
details see below.</para>
<para>In place of the image path a <literal>.v/</literal> versioned directory may be specified, see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
</refsect1>
<refsect1>
@@ -543,6 +547,7 @@
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions Specification</ulink></member>
<member><citerefentry project='man-pages'><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>umount</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>

View File

@@ -209,21 +209,21 @@
<term><option>-D</option></term>
<term><option>--directory=</option></term>
<listitem><para>Directory to use as file system root for the
container.</para>
<listitem><para>Directory to use as file system root for the container.</para>
<para>If neither <option>--directory=</option>, nor
<option>--image=</option> is specified the directory is
determined by searching for a directory named the same as the
machine name specified with <option>--machine=</option>. See
<para>If neither <option>--directory=</option>, nor <option>--image=</option> is specified the
directory is determined by searching for a directory named the same as the machine name specified
with <option>--machine=</option>. See
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
section "Files and Directories" for the precise search path.</para>
<para>If neither <option>--directory=</option>,
<option>--image=</option>, nor <option>--machine=</option>
are specified, the current directory will
be used. May not be specified together with
<option>--image=</option>.</para></listitem>
<para>In place of the directory path a <literal>.v/</literal> versioned directory may be specified, see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
<para>If neither <option>--directory=</option>, <option>--image=</option>, nor
<option>--machine=</option> are specified, the current directory will be used. May not be specified
together with <option>--image=</option>.</para></listitem>
</varlistentry>
<varlistentry>
@@ -317,6 +317,10 @@
<para>Any other partitions, such as foreign partitions or swap partitions are not mounted. May not be specified
together with <option>--directory=</option>, <option>--template=</option>.</para>
<para>In place of the image path a <literal>.v/</literal> versioned directory may be specified, see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
<xi:include href="version-info.xml" xpointer="v211"/></listitem>
</varlistentry>

198
man/systemd-vpick.xml Normal file
View File

@@ -0,0 +1,198 @@
<?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-vpick"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-vpick</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-vpick</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-vpick</refname>
<refpurpose>Resolve paths to <literal>.v/</literal> versioned directories</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-vpick <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">PATH</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-vpick</command> resolves a file system path referencing a <literal>.v/</literal>
versioned directory to a path to the newest (by version) file contained therein. This tool provides a
command line interface for the
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>
logic.</para>
<para>The tool expects a path to a <literal>.v/</literal> directory as argument (either directly, or with
a triple underscore pattern as final component). It then determines the newest file contained in that
directory, and writes its path to standard output.</para>
<para>Unless the triple underscore pattern is passed as last component of the path, it is typically
necessary to at least specify the <option>--suffix=</option> switch to configure the file suffix to look
for.</para>
<para>If the specified path does not reference a <literal>.v/</literal> path (i.e. neither the final
component ends in <literal>.v</literal>, nor the penultimate does or the final one does contain a triple
underscore) it specified path is written unmodified to standard output.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--basename=</option></term>
<term><option>-B</option></term>
<listitem><para>Overrides the "basename" of the files to look for, i.e. the part to the left of the
variable part of the filenames. Normally this is derived automatically from the filename of the
<literal>.v</literal> component of the specified path, or from the triple underscore pattern in the
last component of the specified path.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>-V</option></term>
<listitem><para>Explicitly configures the version to select. If specified, a filename with the
specified version string will be looked for, instead of the newest version
available.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>-A</option></term>
<listitem><para>Explicitly configures the architecture to select. If specified, a filename with the
specified architecture identifier will be looked for. If not specified only filenames with a locally
supported architecture are considered, or those without any architecture identifier.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--suffix=</option></term>
<term><option>-S</option></term>
<listitem><para>Configures the suffix of the filenames to consider. For the <literal>.v/</literal>
logic it is necessary to specify the suffix to look for, and the <literal>.v/</literal> component
must also carry the suffix immediately before <literal>.v</literal> in its name.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--type=</option></term>
<term><option>-t</option></term>
<listitem><para>Configures the inode type to look for in the <literal>.v/</literal> directory. Takes
one of <literal>reg</literal>, <literal>dir</literal>, <literal>sock</literal>,
<literal>fifo</literal>, <literal>blk</literal>, <literal>chr</literal>, <literal>lnk</literal> as
argument, each identifying an inode type. See <citerefentry
project='man-pages'><refentrytitle>inode</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details about inode types. If this option is used inodes not matching the specified type are filtered
and not taken into consideration.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--print=</option></term>
<term><option>-p</option></term>
<listitem><para>Configures what precisely to write to standard output. If not specified prints the
full, resolved path of the newest matching file in the <literal>.v/</literal> directory. This switch can be set to one of the following:</para>
<itemizedlist>
<listitem><para>If set to <literal>filename</literal>, will print only the filename instead of the full path of the resolved file.</para></listitem>
<listitem><para>If set to <literal>version</literal>, will print only the version of the resolved file.</para></listitem>
<listitem><para>If set to <literal>type</literal>, will print only the inode type of the resolved
file (i.e. a string such as <literal>reg</literal> for regular files, or <literal>dir</literal> for
directories).</para></listitem>
<listitem><para>If set to <literal>arch</literal>, will print only the architecture of the resolved
file.</para></listitem>
<listitem><para>If set to <literal>tries</literal>, will print only the tries left/tries done of the
resolved file.</para></listitem>
<listitem><para>If set to <literal>all</literal>, will print all of the above in a simple tabular
output.</para></listitem>
</itemizedlist>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--resolve=</option></term>
<listitem><para>Takes a boolean argument. If true the path to the versioned file is fully
canonicalized (i.e. symlinks resolved, and redundant path components removed) before it is shown. If
false (the default) this is not done, and the path is shown without canonicalization.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>Use a command like the following to automatically pick the newest raw disk image from a
<literal>.v/</literal> directory:</para>
<programlisting>$ systemd-vpick --suffix=.raw --type=reg /var/lib/machines/quux.raw.v/</programlisting>
<para>This will enumerate all regular files matching
<filename>/var/lib/machines/quux.raw.v/quux*.raw</filename>, filter and sort them according to the rules
described in
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>, and then
write the path to the newest (by version) file to standard output.</para>
<para>Use a command like the following to automatically pick the newest OS directory tree from a
<literal>.v/</literal> directory:</para>
<programlisting>$ systemd-vpick --type=dir /var/lib/machines/waldo.v/</programlisting>
<para>This will enumerate all directory inodes matching
<filename>/var/lib/machines/waldo.v/waldo*</filename>, filter and sort them according to the rules
described in
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>, and then
write the path to the newest (by version) directory to standard output.</para>
<para>For further examples see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</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><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>

View File

@@ -155,6 +155,10 @@
<programlisting>BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout</programlisting>
</example>
<para>In place of the directory path a <literal>.v/</literal> versioned directory may be specified,
see <citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
<xi:include href="system-or-user-ns.xml" xpointer="singular"/></listitem>
</varlistentry>
@@ -191,6 +195,10 @@
<citerefentry><refentrytitle>systemd-soft-reboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>),
in case the service is configured to survive it.</para>
<para>In place of the image path a <literal>.v/</literal> versioned directory may be specified, see
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
<xi:include href="system-only.xml" xpointer="singular"/>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>

163
man/systemd.v.xml Normal file
View File

@@ -0,0 +1,163 @@
<?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.v">
<refentryinfo>
<title>systemd.v</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd.v</refentrytitle>
<manvolnum>7</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd.v</refname>
<refpurpose>Directory with Versioned Resources</refpurpose>
</refnamediv>
<refsect1>
<title>Description</title>
<para>In various places systemd components accept paths whose trailing components have the
<literal>.v/</literal> suffix, pointing to a directory. These components will then automatically look for
suitable files inside the directory, do a version comparison and open the newest file found (by
version). Specifically, two expressions are supported:</para>
<itemizedlist>
<listitem><para>When looking for files with a suffix <replaceable>.SUFFIX</replaceable>, and a path
<filename><replaceable>PATH</replaceable>/<replaceable>NAME</replaceable><replaceable>.SUFFIX</replaceable>.v/</filename>
is specified, then all files
<filename><replaceable>PATH</replaceable>/<replaceable>NAME</replaceable><replaceable>.SUFFIX</replaceable>.v/<replaceable>NAME</replaceable>_*<replaceable>.SUFFIX</replaceable></filename>
are enumerated, filtered, sorted and the newest file used. The primary sorting key is the
<emphasis>variable part</emphasis>, here indicated by the wildcard
<literal>*</literal>.</para></listitem>
<listitem><para>When a path
<filename><replaceable>PATH</replaceable>.v/<replaceable>NAME</replaceable>___<replaceable>.SUFFIX</replaceable></filename>
is specified (i.e. the penultimate component of the path ends in <literal>.v</literal> and the final
component contains a triple underscore), then all files
<filename><replaceable>PATH</replaceable>.v/<replaceable>NAME</replaceable>_*<replaceable>.SUFFIX</replaceable></filename>
are enumerated, filtered, sorted and the newest file used (again, by the <emphasis>variable
part</emphasis>, here indicated by the wildcard <literal>*</literal>).</para></listitem>
</itemizedlist>
<para>To illustrate this in an example, consider a directory <filename>/var/lib/machines/mymachine.raw.v/</filename>, which is populated with three files:</para>
<itemizedlist>
<listitem><para><filename>mymachine_7.5.13.raw</filename></para></listitem>
<listitem><para><filename>mymachine_7.5.14.raw</filename></para></listitem>
<listitem><para><filename>mymachine_7.6.0.raw</filename></para></listitem>
</itemizedlist>
<para>Invoke a tool such as <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> with a command line like the following:</para>
<programlisting># systemd-nspawn --image=/var/lib/machines/mymachine.raw.v --boot</programlisting>
<para>Then this would automatically be resolved to the equivalent of:</para>
<programlisting># systemd-nspawn --image=/var/lib/machines/mymachine.raw.v/mymachine_7.6.0.raw --boot</programlisting>
<para>Much of systemd's functionality that expects a path to a disk image or OS directory hierarchy
support the <literal>.v/</literal> versioned directory mechanism, for example
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry> or
the <varname>RootDirectory=</varname>/<varname>RootImage=</varname> settings of service files (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>).</para>
<para>Use the
<citerefentry><refentrytitle>systemd-vpick</refentrytitle><manvolnum>1</manvolnum></citerefentry> tool to
resolve <literal>.v/</literal> paths from the command line, for example for usage in shell
scripts.</para>
</refsect1>
<refsect1>
<title>Filtering and Sorting</title>
<para>The variable part of the filenames in the <literal>.v/</literal> directories are filtered and
compared primarily with a version comparison, implementing <ulink
url="https://uapi-group.org/specifications/specs/version_format_specification/">Version Format
Specification</ulink>. However, additional rules apply:</para>
<itemizedlist>
<listitem><para>If the variable part is suffixed by one or two integer values ("tries left" and "tries
done") in the formats <filename>+<replaceable>LEFT</replaceable></filename> or
<filename>+<replaceable>LEFT</replaceable>-<replaceable>DONE</replaceable></filename>, then these
indicate usage attempt counters. The idea is that each time before a file is attempted to be used, its
"tries left" counter is decreased, and the "tries done" counter increased (simply by renaming the
file). When the file is successfully used (which for example could mean for an OS image: successfully
booted) the counters are removed from the file name, indicating that the file has been validated to
work correctly. This mechanism mirrors the boot assessment counters defined by <ulink
url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/">Automatic Boot Assessment</ulink>. Any filenames
with no boot counters or with a non-zero "tries left" counter are sorted before filenames with a zero
"tries left" counter.</para></listitem>
<listitem><para>Preceeding the use counters (if they are specified), an optional CPU architecture
identifier may be specified in the filename (separated from the version with an underscore), as defined
in the architecture vocabulary of the <varname>ConditionArchitecture=</varname> unit file setting, as
documented in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Files
whose name indicates an architecture not supported locally are filtered and not considered for the
version comparison.</para></listitem>
<listitem><para>The rest of the variable part is the version string.</para></listitem>
</itemizedlist>
<para>Or in other words, the files in the <literal>.v/</literal> directories should follow one of these
naming structures:</para>
<itemizedlist>
<listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
<listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>_<replaceable>ARCHITECTURE</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
<listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>+<replaceable>LEFT</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
<listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>+<replaceable>LEFT</replaceable>-<replaceable>DONE</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
<listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>_<replaceable>ARCHITECTURE</replaceable>+<replaceable>LEFT</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
<listitem><para><filename><replaceable>NAME</replaceable>_<replaceable>VERSION</replaceable>_<replaceable>ARCHITECTURE</replaceable>+<replaceable>LEFT</replaceable>-<replaceable>DONE</replaceable><replaceable>.SUFFIX</replaceable></filename></para></listitem>
</itemizedlist>
</refsect1>
<refsect1>
<title>Example</title>
<para>Here's a more comprehensive example, further extending the one described above. Consider a
directory <filename>/var/lib/machines/mymachine.raw.v/</filename>, which is populated with the following
files:</para>
<itemizedlist>
<listitem><para><filename>mymachine_7.5.13.raw</filename></para></listitem>
<listitem><para><filename>mymachine_7.5.14_x86-64.raw</filename></para></listitem>
<listitem><para><filename>mymachine_7.6.0_arm64.raw</filename></para></listitem>
<listitem><para><filename>mymachine_7.7.0_x86-64+0-5.raw</filename></para></listitem>
</itemizedlist>
<para>Now invoke the following command on an x86-64 machine:</para>
<programlisting>$ systemd-vpick --suffix=.raw /var/lib/machines/mymachine.raw.v/</programlisting>
<para>This would resolve the specified path to
<filename>/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw</filename>. Explanation: even
though <filename>mymachine_7.7.0_x86-64+0-5.raw</filename> has the newest version, it is not preferred
because its tries left counter is zero. And even though <filename>mymachine_7.6.0_arm64.raw</filename>
has the second newest version it is also not considered, in this case because we operate on an x86_64
system and the image is intended for arm64 CPUs. Finally, the <filename>mymachine_7.5.13.raw</filename>
image is not considered because it is older than <filename>mymachine_7.5.14_x86-64.raw</filename>.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-vpick</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-sysupdate</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>

View File

@@ -2230,6 +2230,7 @@ subdir('src/vconsole')
subdir('src/veritysetup')
subdir('src/vmspawn')
subdir('src/volatile-root')
subdir('src/vpick')
subdir('src/xdg-autostart-generator')
subdir('src/systemd')

View File

@@ -516,3 +516,25 @@ const char* inode_type_to_string(mode_t m) {
return NULL;
}
mode_t inode_type_from_string(const char *s) {
if (!s)
return MODE_INVALID;
if (streq(s, "reg"))
return S_IFREG;
if (streq(s, "dir"))
return S_IFDIR;
if (streq(s, "lnk"))
return S_IFLNK;
if (streq(s, "chr"))
return S_IFCHR;
if (streq(s, "blk"))
return S_IFBLK;
if (streq(s, "fifo"))
return S_IFIFO;
if (streq(s, "sock"))
return S_IFSOCK;
return MODE_INVALID;
}

View File

@@ -114,3 +114,4 @@ int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
const char* inode_type_to_string(mode_t m);
mode_t inode_type_from_string(const char *s);

View File

@@ -1519,3 +1519,26 @@ ssize_t strlevenshtein(const char *x, const char *y) {
return t1[yl];
}
char *strrstr(const char *haystack, const char *needle) {
const char *f = NULL;
size_t l;
/* Like strstr() but returns the last rather than the first occurence of "needle" in "haystack". */
if (!haystack || !needle)
return NULL;
l = strlen(needle);
/* Special case: for the empty string we return the very last possible occurence, i.e. *after* the
* last char, not before. */
if (l == 0)
return strchr(haystack, 0);
for (const char *p = haystack; *p; p++)
if (strncmp(p, needle, l) == 0)
f = p;
return (char*) f;
}

View File

@@ -322,3 +322,5 @@ static inline int strdup_or_null(const char *s, char **ret) {
*ret = c;
return 1;
}
char *strrstr(const char *haystack, const char *needle);

View File

@@ -905,3 +905,13 @@ int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const
}
DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free);
char* strv_endswith(const char *s, char **l) {
STRV_FOREACH(i, l) {
char *e = endswith(s, *i);
if (e)
return (char*) e;
}
return NULL;
}

View File

@@ -252,3 +252,5 @@ int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HA
int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS);
#define string_strv_hashmap_put(h, k, v) _string_strv_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS)
#define string_strv_ordered_hashmap_put(h, k, v) _string_strv_ordered_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS)
char* strv_endswith(const char *s, char **l);

View File

@@ -59,6 +59,7 @@
#include "strv.h"
#include "terminal-util.h"
#include "utmp-wtmp.h"
#include "vpick.h"
#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC)
#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC)
@@ -2859,13 +2860,33 @@ static bool insist_on_sandboxing(
return false;
}
static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) {
static int setup_ephemeral(
const ExecContext *context,
ExecRuntime *runtime,
char **root_image, /* both input and output! modified if ephemeral logic enabled */
char **root_directory) { /* ditto */
_cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *new_root = NULL;
int r;
assert(context);
assert(root_image);
assert(root_directory);
if (!*root_image && !*root_directory)
return 0;
if (!runtime || !runtime->ephemeral_copy)
return 0;
assert(runtime->ephemeral_storage_socket[0] >= 0);
assert(runtime->ephemeral_storage_socket[1] >= 0);
new_root = strdup(runtime->ephemeral_copy);
if (!new_root)
return log_oom_debug();
r = posix_lock(runtime->ephemeral_storage_socket[0], LOCK_EX);
if (r < 0)
return log_debug_errno(r, "Failed to lock ephemeral storage socket: %m");
@@ -2876,28 +2897,23 @@ static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) {
if (fd >= 0)
/* We got an fd! That means ephemeral has already been set up, so nothing to do here. */
return 0;
if (fd != -EAGAIN)
return log_debug_errno(fd, "Failed to receive file descriptor queued on ephemeral storage socket: %m");
log_debug("Making ephemeral snapshot of %s to %s",
context->root_image ?: context->root_directory, runtime->ephemeral_copy);
if (*root_image) {
log_debug("Making ephemeral copy of %s to %s", *root_image, new_root);
if (context->root_image)
fd = copy_file(context->root_image, runtime->ephemeral_copy, O_EXCL, 0600,
COPY_LOCK_BSD|COPY_REFLINK|COPY_CRTIME);
else
fd = btrfs_subvol_snapshot_at(AT_FDCWD, context->root_directory,
AT_FDCWD, runtime->ephemeral_copy,
BTRFS_SNAPSHOT_FALLBACK_COPY |
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
BTRFS_SNAPSHOT_RECURSIVE |
BTRFS_SNAPSHOT_LOCK_BSD);
if (fd < 0)
return log_debug_errno(fd, "Failed to snapshot %s to %s: %m",
context->root_image ?: context->root_directory, runtime->ephemeral_copy);
fd = copy_file(*root_image,
new_root,
O_EXCL,
0600,
COPY_LOCK_BSD|
COPY_REFLINK|
COPY_CRTIME);
if (fd < 0)
return log_debug_errno(fd, "Failed to copy image %s to %s: %m",
*root_image, new_root);
if (context->root_image) {
/* A root image might be subject to lots of random writes so let's try to disable COW on it
* which tends to not perform well in combination with lots of random writes.
*
@@ -2906,13 +2922,35 @@ static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) {
*/
r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
if (r < 0)
log_debug_errno(fd, "Failed to disable copy-on-write for %s, ignoring: %m", runtime->ephemeral_copy);
log_debug_errno(fd, "Failed to disable copy-on-write for %s, ignoring: %m", new_root);
} else {
assert(*root_directory);
log_debug("Making ephemeral snapshot of %s to %s", *root_directory, new_root);
fd = btrfs_subvol_snapshot_at(
AT_FDCWD, *root_directory,
AT_FDCWD, new_root,
BTRFS_SNAPSHOT_FALLBACK_COPY |
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
BTRFS_SNAPSHOT_RECURSIVE |
BTRFS_SNAPSHOT_LOCK_BSD);
if (fd < 0)
return log_debug_errno(fd, "Failed to snapshot directory %s to %s: %m",
*root_directory, new_root);
}
r = send_one_fd(runtime->ephemeral_storage_socket[1], fd, MSG_DONTWAIT);
if (r < 0)
return log_debug_errno(r, "Failed to queue file descriptor on ephemeral storage socket: %m");
if (*root_image)
free_and_replace(*root_image, new_root);
else {
assert(*root_directory);
free_and_replace(*root_directory, new_root);
}
return 1;
}
@@ -2972,6 +3010,63 @@ static int verity_settings_prepare(
return 0;
}
static int pick_versions(
const ExecContext *context,
const ExecParameters *params,
char **ret_root_image,
char **ret_root_directory) {
int r;
assert(context);
assert(params);
assert(ret_root_image);
assert(ret_root_directory);
if (context->root_image) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
context->root_image,
&pick_filter_image_raw,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0)
return r;
if (!result.path)
return log_exec_debug_errno(context, params, SYNTHETIC_ERRNO(ENOENT), "No matching entry in .v/ directory %s found.", context->root_image);
*ret_root_image = TAKE_PTR(result.path);
*ret_root_directory = NULL;
return r;
}
if (context->root_directory) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
context->root_directory,
&pick_filter_image_dir,
PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0)
return r;
if (!result.path)
return log_exec_debug_errno(context, params, SYNTHETIC_ERRNO(ENOENT), "No matching entry in .v/ directory %s found.", context->root_directory);
*ret_root_image = NULL;
*ret_root_directory = TAKE_PTR(result.path);
return r;
}
*ret_root_image = *ret_root_directory = NULL;
return 0;
}
static int apply_mount_namespace(
ExecCommandFlags command_flags,
const ExecContext *context,
@@ -2984,8 +3079,8 @@ static int apply_mount_namespace(
_cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL,
**read_write_paths_cleanup = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL,
*extension_dir = NULL, *host_os_release_stage = NULL;
const char *root_dir = NULL, *root_image = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
*extension_dir = NULL, *host_os_release_stage = NULL, *root_image = NULL, *root_dir = NULL;
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
char **read_write_paths;
bool needs_sandboxing, setup_os_release_symlink;
BindMount *bind_mounts = NULL;
@@ -2997,14 +3092,21 @@ static int apply_mount_namespace(
CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many);
if (params->flags & EXEC_APPLY_CHROOT) {
r = setup_ephemeral(context, runtime);
r = pick_versions(
context,
params,
&root_image,
&root_dir);
if (r < 0)
return r;
if (context->root_image)
root_image = (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_image;
else
root_dir = (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_directory;
r = setup_ephemeral(
context,
runtime,
&root_image,
&root_dir);
if (r < 0)
return r;
}
r = compile_bind_mounts(context, params, &bind_mounts, &n_bind_mounts, &empty_directories);

View File

@@ -48,6 +48,7 @@
#include "tmpfile-util.h"
#include "uid-alloc-range.h"
#include "user-util.h"
#include "vpick.h"
static enum {
ACTION_DISSECT,
@@ -1817,6 +1818,16 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
if (arg_image) {
r = path_pick_update_warn(
&arg_image,
&pick_filter_image_raw,
PICK_ARCHITECTURE|PICK_TRIES,
/* ret_result= */ NULL);
if (r < 0)
return r;
}
switch (arg_action) {
case ACTION_UMOUNT:
return action_umount(arg_path);

View File

@@ -112,6 +112,7 @@
#include "umask-util.h"
#include "unit-name.h"
#include "user-util.h"
#include "vpick.h"
/* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */
#define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify"
@@ -2911,14 +2912,72 @@ static int on_request_stop(sd_bus_message *m, void *userdata, sd_bus_error *erro
return 0;
}
static int pick_paths(void) {
int r;
if (arg_directory) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = pick_filter_image_dir;
filter.architecture = arg_architecture;
r = path_pick_update_warn(
&arg_directory,
&filter,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0) {
/* Accept ENOENT here so that the --template= logic can work */
if (r != -ENOENT)
return r;
} else
arg_architecture = result.architecture;
}
if (arg_image) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = pick_filter_image_raw;
filter.architecture = arg_architecture;
r = path_pick_update_warn(
&arg_image,
&filter,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0)
return r;
arg_architecture = result.architecture;
}
if (arg_template) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = pick_filter_image_dir;
filter.architecture = arg_architecture;
r = path_pick_update_warn(
&arg_template,
&filter,
PICK_ARCHITECTURE,
&result);
if (r < 0)
return r;
arg_architecture = result.architecture;
}
return 0;
}
static int determine_names(void) {
int r;
if (arg_template && !arg_directory && arg_machine) {
/* If --template= was specified then we should not
* search for a machine, but instead create a new one
* in /var/lib/machine. */
/* If --template= was specified then we should not search for a machine, but instead create a
* new one in /var/lib/machine. */
arg_directory = path_join("/var/lib/machines", arg_machine);
if (!arg_directory)
@@ -5406,6 +5465,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
goto finish;
r = pick_paths();
if (r < 0)
goto finish;
r = determine_names();
if (r < 0)
goto finish;

View File

@@ -45,6 +45,7 @@
#include "strv.h"
#include "time-util.h"
#include "utf8.h"
#include "vpick.h"
#include "xattr-util.h"
static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
@@ -215,40 +216,60 @@ static int image_new(
return 0;
}
static int extract_pretty(
static int extract_image_basename(
const char *path,
const char *class_suffix,
const char *format_suffix,
char **ret) {
const char *class_suffix, /* e.g. ".sysext" (this is an optional suffix) */
char **format_suffixes, /* e.g. ".raw" (one of these will be required) */
char **ret_basename,
char **ret_suffix) {
_cleanup_free_ char *name = NULL;
_cleanup_free_ char *name = NULL, *suffix = NULL;
int r;
assert(path);
assert(ret);
r = path_extract_filename(path, &name);
if (r < 0)
return r;
if (format_suffix) {
char *e = endswith(name, format_suffix);
if (format_suffixes) {
char *e = strv_endswith(name, format_suffixes);
if (!e) /* Format suffix is required */
return -EINVAL;
if (ret_suffix) {
suffix = strdup(e);
if (!suffix)
return -ENOMEM;
}
*e = 0;
}
if (class_suffix) {
char *e = endswith(name, class_suffix);
if (e) /* Class suffix is optional */
if (e) { /* Class suffix is optional */
if (ret_suffix) {
_cleanup_free_ char *j = strjoin(e, suffix);
if (!j)
return -ENOMEM;
free_and_replace(suffix, j);
}
*e = 0;
}
}
if (!image_name_is_valid(name))
return -EINVAL;
*ret = TAKE_PTR(name);
if (ret_suffix)
*ret_suffix = TAKE_PTR(suffix);
if (ret_basename)
*ret_basename = TAKE_PTR(name);
return 0;
}
@@ -303,7 +324,12 @@ static int image_make(
return 0;
if (!pretty) {
r = extract_pretty(filename, image_class_suffix_to_string(c), NULL, &pretty_buffer);
r = extract_image_basename(
filename,
image_class_suffix_to_string(c),
/* format_suffix= */ NULL,
&pretty_buffer,
/* ret_suffix= */ NULL);
if (r < 0)
return r;
@@ -390,7 +416,12 @@ static int image_make(
(void) fd_getcrtime_at(dfd, filename, AT_SYMLINK_FOLLOW, &crtime);
if (!pretty) {
r = extract_pretty(filename, image_class_suffix_to_string(c), ".raw", &pretty_buffer);
r = extract_image_basename(
filename,
image_class_suffix_to_string(c),
STRV_MAKE(".raw"),
&pretty_buffer,
/* ret_suffix= */ NULL);
if (r < 0)
return r;
@@ -424,7 +455,12 @@ static int image_make(
return 0;
if (!pretty) {
r = extract_pretty(filename, NULL, NULL, &pretty_buffer);
r = extract_image_basename(
filename,
/* class_suffix= */ NULL,
/* format_suffix= */ NULL,
&pretty_buffer,
/* ret_suffix= */ NULL);
if (r < 0)
return r;
@@ -488,6 +524,37 @@ static const char *pick_image_search_path(ImageClass class) {
return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
}
static char **make_possible_filenames(ImageClass class, const char *image_name) {
_cleanup_strv_free_ char **l = NULL;
assert(image_name);
FOREACH_STRING(v_suffix, "", ".v")
FOREACH_STRING(format_suffix, "", ".raw") {
_cleanup_free_ char *j = NULL;
const char *class_suffix;
class_suffix = image_class_suffix_to_string(class);
if (class_suffix) {
j = strjoin(image_name, class_suffix, format_suffix, v_suffix);
if (!j)
return NULL;
if (strv_consume(&l, TAKE_PTR(j)) < 0)
return NULL;
}
j = strjoin(image_name, format_suffix, v_suffix);
if (!j)
return NULL;
if (strv_consume(&l, TAKE_PTR(j)) < 0)
return NULL;
}
return TAKE_PTR(l);
}
int image_find(ImageClass class,
const char *name,
const char *root,
@@ -503,6 +570,10 @@ int image_find(ImageClass class,
if (!image_name_is_valid(name))
return -ENOENT;
_cleanup_strv_free_ char **names = make_possible_filenames(class, name);
if (!names)
return -ENOMEM;
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
@@ -519,43 +590,97 @@ int image_find(ImageClass class,
* to symlink block devices into the search path. (For now, we disable that when operating
* relative to some root directory.) */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
if (fstatat(dirfd(d), name, &st, flags) < 0) {
_cleanup_free_ char *raw = NULL;
if (errno != ENOENT)
return -errno;
STRV_FOREACH(n, names) {
_cleanup_free_ char *fname_buf = NULL;
const char *fname = *n;
raw = strjoin(name, ".raw");
if (!raw)
return -ENOMEM;
if (fstatat(dirfd(d), fname, &st, flags) < 0) {
if (errno != ENOENT)
return -errno;
if (fstatat(dirfd(d), raw, &st, flags) < 0) {
if (errno == ENOENT)
continue;
return -errno;
continue; /* Vanished while we were looking at it */
}
if (!S_ISREG(st.st_mode))
if (endswith(fname, ".raw")) {
if (!S_ISREG(st.st_mode)) {
log_debug("Ignoring non-regular file '%s' with .raw suffix.", fname);
continue;
}
} else if (endswith(fname, ".v")) {
if (!S_ISDIR(st.st_mode)) {
log_debug("Ignoring non-directory file '%s' with .v suffix.", fname);
continue;
}
_cleanup_free_ char *suffix = NULL;
suffix = strdup(ASSERT_PTR(startswith(fname, name)));
if (!suffix)
return -ENOMEM;
*ASSERT_PTR(endswith(suffix, ".v")) = 0;
_cleanup_free_ char *vp = path_join(resolved, fname);
if (!vp)
return -ENOMEM;
PickFilter filter = {
.type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
.basename = name,
.architecture = _ARCHITECTURE_INVALID,
.suffix = suffix,
};
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(root,
/* toplevel_fd= */ AT_FDCWD,
vp,
&filter,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0) {
log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
continue;
}
if (!result.path) {
log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
continue;
}
/* Refresh the stat data for the discovered target */
st = result.st;
_cleanup_free_ char *bn = NULL;
r = path_extract_filename(result.path, &bn);
if (r < 0) {
log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
continue;
}
fname_buf = path_join(fname, bn);
if (!fname_buf)
return log_oom();
fname = fname_buf;
} else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) {
log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname);
continue;
}
r = image_make(class, name, dirfd(d), resolved, raw, &st, ret);
} else {
if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
r = image_make(class, name, dirfd(d), resolved, fname, &st, ret);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
return r;
r = image_make(class, name, dirfd(d), resolved, name, &st, ret);
if (ret)
(*ret)->discoverable = true;
return 1;
}
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
return r;
if (ret)
(*ret)->discoverable = true;
return 1;
}
if (class == IMAGE_MACHINE && streq(name, ".host")) {
@@ -566,7 +691,7 @@ int image_find(ImageClass class,
if (ret)
(*ret)->discoverable = true;
return r;
return 1;
}
return -ENOENT;
@@ -613,43 +738,133 @@ int image_discover(
return r;
FOREACH_DIRENT_ALL(de, d, return -errno) {
_cleanup_free_ char *pretty = NULL, *fname_buf = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
_cleanup_free_ char *pretty = NULL;
const char *fname = de->d_name;
struct stat st;
int flags;
if (dot_or_dot_dot(de->d_name))
if (dot_or_dot_dot(fname))
continue;
/* As mentioned above, we follow symlinks on this fstatat(), because we want to
* permit people to symlink block devices into the search path. */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) {
if (fstatat(dirfd(d), fname, &st, flags) < 0) {
if (errno == ENOENT)
continue;
return -errno;
}
if (S_ISREG(st.st_mode))
r = extract_pretty(de->d_name, image_class_suffix_to_string(class), ".raw", &pretty);
else if (S_ISDIR(st.st_mode))
r = extract_pretty(de->d_name, image_class_suffix_to_string(class), NULL, &pretty);
else if (S_ISBLK(st.st_mode))
r = extract_pretty(de->d_name, NULL, NULL, &pretty);
else {
log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", de->d_name);
continue;
}
if (r < 0) {
log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", de->d_name);
if (S_ISREG(st.st_mode)) {
r = extract_image_basename(
fname,
image_class_suffix_to_string(class),
STRV_MAKE(".raw"),
&pretty,
/* suffix= */ NULL);
if (r < 0) {
log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
continue;
}
} else if (S_ISDIR(st.st_mode)) {
const char *v;
v = endswith(fname, ".v");
if (v) {
_cleanup_free_ char *suffix = NULL, *nov = NULL;
nov = strndup(fname, v - fname); /* Chop off the .v */
if (!nov)
return -ENOMEM;
r = extract_image_basename(
nov,
image_class_suffix_to_string(class),
STRV_MAKE(".raw", ""),
&pretty,
&suffix);
if (r < 0) {
log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like a versioned image.", fname);
continue;
}
_cleanup_free_ char *vp = path_join(resolved, fname);
if (!vp)
return -ENOMEM;
PickFilter filter = {
.type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
.basename = pretty,
.architecture = _ARCHITECTURE_INVALID,
.suffix = suffix,
};
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(root,
/* toplevel_fd= */ AT_FDCWD,
vp,
&filter,
PICK_ARCHITECTURE|PICK_TRIES,
&result);
if (r < 0) {
log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
continue;
}
if (!result.path) {
log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
continue;
}
/* Refresh the stat data for the discovered target */
st = result.st;
_cleanup_free_ char *bn = NULL;
r = path_extract_filename(result.path, &bn);
if (r < 0) {
log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
continue;
}
fname_buf = path_join(fname, bn);
if (!fname_buf)
return log_oom();
fname = fname_buf;
} else {
r = extract_image_basename(
fname,
image_class_suffix_to_string(class),
/* format_suffix= */ NULL,
&pretty,
/* ret_suffix= */ NULL);
if (r < 0) {
log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
continue;
}
}
} else if (S_ISBLK(st.st_mode)) {
r = extract_image_basename(
fname,
/* class_suffix= */ NULL,
/* format_suffix= */ NULL,
&pretty,
/* ret_v_suffix= */ NULL);
if (r < 0) {
log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
continue;
}
} else {
log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", fname);
continue;
}
if (hashmap_contains(h, pretty))
continue;
r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image);
r = image_make(class, pretty, dirfd(d), resolved, fname, &st, &image);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)

View File

@@ -189,6 +189,7 @@ shared_sources = files(
'verbs.c',
'vlan-util.c',
'volatile-util.c',
'vpick.c',
'wall.c',
'watchdog.c',
'web-util.c',

694
src/shared/vpick.c Normal file
View File

@@ -0,0 +1,694 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include "architecture.h"
#include "chase.h"
#include "fd-util.h"
#include "fs-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "recurse-dir.h"
#include "vpick.h"
void pick_result_done(PickResult *p) {
assert(p);
free(p->path);
safe_close(p->fd);
free(p->version);
*p = PICK_RESULT_NULL;
}
static int format_fname(
const PickFilter *filter,
PickFlags flags,
char **ret) {
_cleanup_free_ char *fn = NULL;
int r;
assert(filter);
assert(ret);
if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */
return -ENOEXEC;
/* The format for names we match goes like this:
*
* <basename><suffix>
* or:
* <basename>_<version><suffix>
* or:
* <basename>_<version>_<architecture><suffix>
* or:
* <basename>_<architecture><suffix>
*
* (Note that basename can be empty, in which case the leading "_" is suppressed)
*
* Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw
*
* Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used
* for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this
* usecase here. In Debian the underscore is not allowed (and it uses it itself for separating
* fields).
*
* This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the
* architecture field redundant.
*
* Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix).
*/
if (filter->basename) {
fn = strdup(filter->basename);
if (!fn)
return -ENOMEM;
}
if (filter->version) {
if (isempty(fn)) {
r = free_and_strdup(&fn, filter->version);
if (r < 0)
return r;
} else if (!strextend(&fn, "_", filter->version))
return -ENOMEM;
}
if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) {
const char *as = ASSERT_PTR(architecture_to_string(filter->architecture));
if (isempty(fn)) {
r = free_and_strdup(&fn, as);
if (r < 0)
return r;
} else if (!strextend(&fn, "_", as))
return -ENOMEM;
}
if (filter->suffix && !strextend(&fn, filter->suffix))
return -ENOMEM;
if (!filename_is_valid(fn))
return -EINVAL;
*ret = TAKE_PTR(fn);
return 0;
}
static int errno_from_mode(uint32_t type_mask, mode_t found) {
/* Returns the most appropriate error code if we are lookging for an inode of type of those in the
* 'type_mask' but found 'found' instead.
*
* type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, …
* mode value. */
if (type_mask == 0) /* type doesn't matter */
return 0;
if (FLAGS_SET(type_mask, UINT32_C(1) << IFTODT(found)))
return 0;
if (type_mask == (UINT32_C(1) << DT_BLK))
return -ENOTBLK;
if (type_mask == (UINT32_C(1) << DT_DIR))
return -ENOTDIR;
if (type_mask == (UINT32_C(1) << DT_SOCK))
return -ENOTSOCK;
if (S_ISLNK(found))
return -ELOOP;
if (S_ISDIR(found))
return -EISDIR;
return -EBADF;
}
static int pin_choice(
const char *toplevel_path,
int toplevel_fd,
const char *inode_path,
int _inode_fd, /* we always take ownership of the fd, even on failure */
unsigned tries_left,
unsigned tries_done,
const PickFilter *filter,
PickFlags flags,
PickResult *ret) {
_cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
_cleanup_free_ char *resolved_path = NULL;
int r;
assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
assert(inode_path);
assert(filter);
toplevel_path = strempty(toplevel_path);
if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) {
r = chaseat(toplevel_fd,
inode_path,
CHASE_AT_RESOLVE_IN_ROOT,
FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : 0,
inode_fd < 0 ? &inode_fd : NULL);
if (r < 0)
return r;
if (resolved_path)
inode_path = resolved_path;
}
struct stat st;
if (fstat(inode_fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat discovered inode '%s/%s': %m", toplevel_path, inode_path);
if (filter->type_mask != 0 &&
!FLAGS_SET(filter->type_mask, UINT32_C(1) << IFTODT(st.st_mode)))
return log_debug_errno(
SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)),
"Inode '%s/%s' has wrong type, found '%s'.",
toplevel_path, inode_path,
inode_type_to_string(st.st_mode));
_cleanup_(pick_result_done) PickResult result = {
.fd = TAKE_FD(inode_fd),
.st = st,
.architecture = filter->architecture,
.tries_left = tries_left,
.tries_done = tries_done,
};
result.path = strdup(inode_path);
if (!result.path)
return log_oom_debug();
if (filter->version) {
result.version = strdup(filter->version);
if (!result.version)
return log_oom_debug();
}
*ret = TAKE_PICK_RESULT(result);
return 1;
}
static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) {
unsigned left, done;
size_t n;
assert(s);
assert(ret_tries_left);
assert(ret_tries_done);
if (s[0] != '+')
goto nomatch;
s++;
n = strspn(s, DIGITS);
if (n == 0)
goto nomatch;
if (s[n] == 0) {
if (safe_atou(s, &left) < 0)
goto nomatch;
done = 0;
} else if (s[n] == '-') {
_cleanup_free_ char *c = NULL;
c = strndup(s, n);
if (!c)
return -ENOMEM;
if (safe_atou(c, &left) < 0)
goto nomatch;
s += n + 1;
if (!in_charset(s, DIGITS))
goto nomatch;
if (safe_atou(s, &done) < 0)
goto nomatch;
} else
goto nomatch;
*ret_tries_left = left;
*ret_tries_done = done;
return 1;
nomatch:
*ret_tries_left = *ret_tries_done = UINT_MAX;
return 0;
}
static int make_choice(
const char *toplevel_path,
int toplevel_fd,
const char *inode_path,
int _inode_fd, /* we always take ownership of the fd, even on failure */
const PickFilter *filter,
PickFlags flags,
PickResult *ret) {
static const Architecture local_architectures[] = {
/* In order of preference */
native_architecture(),
#ifdef ARCHITECTURE_SECONDARY
ARCHITECTURE_SECONDARY,
#endif
_ARCHITECTURE_INVALID, /* accept any arch, as last resort */
};
_cleanup_free_ DirectoryEntries *de = NULL;
_cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL;
_cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd);
const Architecture *architectures;
unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX;
size_t n_architectures, best_architecture_index = SIZE_MAX;
int r;
assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
assert(inode_path);
assert(filter);
toplevel_path = strempty(toplevel_path);
if (inode_fd < 0) {
r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd);
if (r < 0)
return r;
}
/* Maybe the filter is fully specified? Then we can generate the file name directly */
r = format_fname(filter, flags, &j);
if (r >= 0) {
_cleanup_free_ char *object_path = NULL;
/* Yay! This worked! */
p = path_join(inode_path, j);
if (!p)
return log_oom_debug();
r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd);
if (r < 0) {
if (r != -ENOENT)
return log_debug_errno(r, "Failed to open '%s/%s': %m", toplevel_path, p);
*ret = PICK_RESULT_NULL;
return 0;
}
return pin_choice(
toplevel_path,
toplevel_fd,
FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p,
TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */
/* tries_left= */ UINT_MAX,
/* tries_done= */ UINT_MAX,
filter,
flags & ~PICK_RESOLVE,
ret);
} else if (r != -ENOEXEC)
return log_debug_errno(r, "Failed to format file name: %m");
/* Underspecified, so we do our enumeration dance */
/* Convert O_PATH to a regular directory fd */
dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
if (dir_fd < 0)
return log_debug_errno(dir_fd, "Failed to reopen '%s/%s' as directory: %m", toplevel_path, inode_path);
r = readdir_all(dir_fd, 0, &de);
if (r < 0)
return log_debug_errno(r, "Failed to read directory '%s/%s': %m", toplevel_path, inode_path);
if (filter->architecture < 0) {
architectures = local_architectures;
n_architectures = ELEMENTSOF(local_architectures);
} else {
architectures = &filter->architecture;
n_architectures = 1;
}
FOREACH_ARRAY(entry, de->entries, de->n_entries) {
unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX;
_cleanup_free_ char *chopped = NULL;
size_t found_architecture_index = SIZE_MAX;
const char *e;
if (!isempty(filter->basename)) {
e = startswith((*entry)->d_name, filter->basename);
if (!e)
continue;
if (e[0] != '_')
continue;
e++;
} else
e = (*entry)->d_name;
if (!isempty(filter->suffix)) {
const char *sfx;
sfx = endswith(e, filter->suffix);
if (!sfx)
continue;
chopped = strndup(e, sfx - e);
if (!chopped)
return log_oom_debug();
e = chopped;
}
if (FLAGS_SET(flags, PICK_TRIES)) {
char *plus = strrchr(e, '+');
if (plus) {
r = parse_tries(plus, &found_tries_left, &found_tries_done);
if (r < 0)
return r;
if (r > 0) /* Found and parsed, now chop off */
*plus = 0;
}
}
if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
char *underscore = strrchr(e, '_');
Architecture a;
a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID;
for (size_t i = 0; i < n_architectures; i++)
if (architectures[i] == a) {
found_architecture_index = i;
break;
}
if (found_architecture_index == SIZE_MAX) { /* No matching arch found */
log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a));
continue;
}
/* Chop off architecture from string */
if (underscore)
*underscore = 0;
}
if (!version_is_valid(e)) {
log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name);
continue;
}
if (filter->version && !streq(filter->version, e)) {
log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version);
continue;
}
if (best_filename) { /* Already found one matching entry? Then figure out the better one */
int d = 0;
/* First, prefer entries with tries left over those without */
if (FLAGS_SET(flags, PICK_TRIES))
d = CMP(found_tries_left != 0, best_tries_left != 0);
/* Second, prefer newer versions */
if (d == 0)
d = strverscmp_improved(e, best_version);
/* Third, prefer native architectures over secondary architectures */
if (d == 0 &&
FLAGS_SET(flags, PICK_ARCHITECTURE) &&
found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX)
d = -CMP(found_architecture_index, best_architecture_index);
/* Fourth, prefer entries with more tries left */
if (FLAGS_SET(flags, PICK_TRIES)) {
if (d == 0)
d = CMP(found_tries_left, best_tries_left);
/* Fifth, prefer entries with fewer attempts done so far */
if (d == 0)
d = -CMP(found_tries_done, best_tries_done);
}
/* Last, just compare the filenames as strings */
if (d == 0)
d = strcmp((*entry)->d_name, best_filename);
if (d < 0) {
log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename);
continue;
}
}
r = free_and_strdup_warn(&best_version, e);
if (r < 0)
return r;
r = free_and_strdup_warn(&best_filename, (*entry)->d_name);
if (r < 0)
return r;
best_architecture_index = found_architecture_index;
best_tries_left = found_tries_left;
best_tries_done = found_tries_done;
}
if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */
*ret = PICK_RESULT_NULL;
return 0;
}
p = path_join(inode_path, best_filename);
if (!p)
return log_oom_debug();
object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH);
if (object_fd < 0)
return log_debug_errno(errno, "Failed to open '%s/%s': %m", toplevel_path, p);
return pin_choice(
toplevel_path,
toplevel_fd,
p,
TAKE_FD(object_fd),
best_tries_left,
best_tries_done,
&(const PickFilter) {
.type_mask = filter->type_mask,
.basename = filter->basename,
.version = empty_to_null(best_version),
.architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID,
.suffix = filter->suffix,
},
flags,
ret);
}
int path_pick(const char *toplevel_path,
int toplevel_fd,
const char *path,
const PickFilter *filter,
PickFlags flags,
PickResult *ret) {
_cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL;
const char *filter_suffix, *enumeration_path;
uint32_t filter_type_mask;
int r;
assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
assert(path);
toplevel_path = strempty(toplevel_path);
/* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports
* three ways to be called:
*
* • with a path referring a directory of any name, and filter→basename *explicitly* specified, in
* which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files.
*
* • with no filter→basename explicitly specified and a path referring to a directory named in format
* "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir
* is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for
* "/foo/bar/baz.suffix.v/baz_*.suffix".
*
* • with a path whose penultimate component ends in ".v/". In this case the final component of the
* path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for
* "/foo/bar/baz.v/waldo_*.suffix".
*/
/* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */
if (filter->basename)
return make_choice(
toplevel_path,
toplevel_fd,
path,
/* inode_fd= */ -EBADF,
filter,
flags,
ret);
r = path_extract_filename(path, &fname);
if (r < 0) {
if (r != -EADDRNOTAVAIL) /* root dir or "." */
return r;
/* If there's no path element we can derive a pattern off, the don't */
goto bypass;
}
/* Remember if the path ends in a dash suffix */
bool slash_suffix = r == O_DIRECTORY;
const char *e = endswith(fname, ".v");
if (e) {
/* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is
* "baz", possibly with a suffix chopped off if there's one specified. */
filter_bname = strndup(fname, e - fname);
if (!filter_bname)
return -ENOMEM;
if (filter->suffix) {
/* Chop off suffix, if specified */
char *f = endswith(filter_bname, filter->suffix);
if (f)
*f = 0;
}
filter_suffix = filter->suffix;
filter_type_mask = filter->type_mask;
enumeration_path = path;
} else {
/* The path does not end in '.v', hence see if the last element is a pattern. */
char *wildcard = strrstr(fname, "___");
if (!wildcard)
goto bypass; /* Not a pattern, then bypass */
/* We found the '___' wildcard, hence evertyhing after it is our filter suffix, and
* evertyhing before is our filter basename */
*wildcard = 0;
filter_suffix = empty_to_null(wildcard + 3);
filter_bname = TAKE_PTR(fname);
r = path_extract_directory(path, &dir);
if (r < 0) {
if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
return r;
goto bypass; /* No dir extractable, can't check if parent ends in ".v" */
}
r = path_extract_filename(dir, &parent);
if (r < 0) {
if (r != -EADDRNOTAVAIL) /* root dir or "." */
return r;
goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */
}
e = endswith(parent, ".v");
if (!e)
goto bypass; /* Doesn't end in ".v", shortcut */
filter_type_mask = filter->type_mask;
if (slash_suffix) {
/* If the pattern is suffixed by a / then we are looking for directories apparently. */
if (filter_type_mask != 0 && !FLAGS_SET(filter_type_mask, UINT32_C(1) << DT_DIR))
return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)),
"Specified pattern ends in '/', but not looking for directories, refusing.");
filter_type_mask = UINT32_C(1) << DT_DIR;
}
enumeration_path = dir;
}
return make_choice(
toplevel_path,
toplevel_fd,
enumeration_path,
/* inode_fd= */ -EBADF,
&(const PickFilter) {
.type_mask = filter_type_mask,
.basename = filter_bname,
.version = filter->version,
.architecture = filter->architecture,
.suffix = filter_suffix,
},
flags,
ret);
bypass:
/* Don't make any choice, but just use the passed path literally */
return pin_choice(
toplevel_path,
toplevel_fd,
path,
/* inode_fd= */ -EBADF,
/* tries_left= */ UINT_MAX,
/* tries_done= */ UINT_MAX,
filter,
flags,
ret);
}
int path_pick_update_warn(
char **path,
const PickFilter *filter,
PickFlags flags,
PickResult *ret_result) {
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
int r;
assert(path);
assert(*path);
/* This updates the first argument if needed! */
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
*path,
filter,
flags,
&result);
if (r == -ENOENT) {
log_debug("Path '%s' doesn't exist, leaving as is.", *path);
*ret_result = PICK_RESULT_NULL;
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to pick version on path '%s': %m", *path);
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path);
log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version));
if (ret_result) {
r = free_and_strdup_warn(path, result.path);
if (r < 0)
return r;
*ret_result = TAKE_PICK_RESULT(result);
} else
free_and_replace(*path, result.path);
return 1;
}
const PickFilter pick_filter_image_raw = {
.type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
.architecture = _ARCHITECTURE_INVALID,
.suffix = ".raw",
};
const PickFilter pick_filter_image_dir = {
.type_mask = UINT32_C(1) << DT_DIR,
.architecture = _ARCHITECTURE_INVALID,
};

59
src/shared/vpick.h Normal file
View File

@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "architecture.h"
typedef enum PickFlags {
PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */
PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */
PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */
} PickFlags;
typedef struct PickFilter {
uint32_t type_mask; /* A mask of 1U << DT_REG, 1U << DT_DIR, … */
const char *basename; /* Can be overriden by search pattern */
const char *version;
Architecture architecture;
const char *suffix; /* Can be overriden by search pattern */
} PickFilter;
typedef struct PickResult {
char *path;
int fd; /* O_PATH */
struct stat st;
char *version;
Architecture architecture;
unsigned tries_left;
unsigned tries_done;
} PickResult;
#define PICK_RESULT_NULL \
(const PickResult) { \
.fd = -EBADF, \
.st.st_mode = MODE_INVALID, \
.architecture = _ARCHITECTURE_INVALID, \
.tries_left = UINT_MAX, \
.tries_done = UINT_MAX, \
}
#define TAKE_PICK_RESULT(ptr) TAKE_GENERIC(ptr, PickResult, PICK_RESULT_NULL)
void pick_result_done(PickResult *p);
int path_pick(const char *toplevel_path,
int toplevel_fd,
const char *path,
const PickFilter *filter,
PickFlags flags,
PickResult *ret);
int path_pick_update_warn(
char **path,
const PickFilter *filter,
PickFlags flags,
PickResult *ret);
extern const PickFilter pick_filter_image_raw;
extern const PickFilter pick_filter_image_dir;

View File

@@ -178,6 +178,7 @@ simple_tests += files(
'test-user-util.c',
'test-utf8.c',
'test-verbs.c',
'test-vpick.c',
'test-web-util.c',
'test-xattr-util.c',
'test-xml.c',

View File

@@ -180,6 +180,21 @@ TEST(dir_is_empty) {
assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) > 0);
}
TEST(inode_type_from_string) {
static const mode_t types[] = {
S_IFREG,
S_IFDIR,
S_IFLNK,
S_IFCHR,
S_IFBLK,
S_IFIFO,
S_IFSOCK,
};
FOREACH_ARRAY(m, types, ELEMENTSOF(types))
assert_se(inode_type_from_string(inode_type_to_string(*m)) == *m);
}
static int intro(void) {
log_show_color(true);
return EXIT_SUCCESS;

View File

@@ -1324,4 +1324,27 @@ TEST(strlevenshtein) {
assert_se(strlevenshtein("sunday", "saturday") == 3);
}
TEST(strrstr) {
assert_se(!strrstr(NULL, NULL));
assert_se(!strrstr("foo", NULL));
assert_se(!strrstr(NULL, "foo"));
const char *p = "foo";
assert_se(strrstr(p, "foo") == p);
assert_se(strrstr(p, "fo") == p);
assert_se(strrstr(p, "f") == p);
assert_se(strrstr(p, "oo") == p + 1);
assert_se(strrstr(p, "o") == p + 2);
assert_se(strrstr(p, "") == p + strlen(p));
assert_se(!strrstr(p, "bar"));
p = "xoxoxox";
assert_se(strrstr(p, "") == p + strlen(p));
assert_se(strrstr(p, "x") == p + 6);
assert_se(strrstr(p, "ox") == p + 5);
assert_se(strrstr(p, "xo") == p + 4);
assert_se(strrstr(p, "xox") == p + 4);
assert_se(!strrstr(p, "xx"));
}
DEFINE_TEST_MAIN(LOG_DEBUG);

View File

@@ -1006,4 +1006,12 @@ TEST(strv_find_first_field) {
assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"));
}
TEST(strv_endswith) {
assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("xxx", "yyy", "ldo", "zzz")), "ldo"));
assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("xxx", "yyy", "zzz")), NULL));
assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("waldo")), "waldo"));
assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("w", "o", "ldo")), "o"));
assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("knurz", "", "waldo")), ""));
}
DEFINE_TEST_MAIN(LOG_INFO);

171
src/test/test-vpick.c Normal file
View File

@@ -0,0 +1,171 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "mkdir.h"
#include "path-util.h"
#include "rm-rf.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "vpick.h"
TEST(path_pick) {
_cleanup_(rm_rf_physical_and_freep) char *p = NULL;
_cleanup_close_ int dfd = -EBADF, sub_dfd = -EBADF;
dfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p);
assert(dfd >= 0);
sub_dfd = open_mkdir_at(dfd, "foo.v", O_CLOEXEC, 0777);
assert(sub_dfd >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_5.5.raw", "5.5", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_55.raw", "55", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_5.raw", "5", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_5_ia64.raw", "5", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_7.raw", "7", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_7_x86-64.raw", "7 64bit", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_55_x86-64.raw", "55 64bit", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_55_x86.raw", "55 32bit", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "foo_99_x86.raw", "99 32bit", WRITE_STRING_FILE_CREATE) >= 0);
/* Let's add an entry for sparc (which is a valid arch, but almost certainly not what we test
* on). This entry should hence always be ignored */
if (native_architecture() != ARCHITECTURE_SPARC)
assert_se(write_string_file_at(sub_dfd, "foo_100_sparc.raw", "100 sparc", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "quux_1_s390.raw", "waldo1", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "quux_2_s390+4-6.raw", "waldo2", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(write_string_file_at(sub_dfd, "quux_3_s390+0-10.raw", "waldo3", WRITE_STRING_FILE_CREATE) >= 0);
_cleanup_free_ char *pp = NULL;
pp = path_join(p, "foo.v");
assert_se(pp);
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
PickFilter filter = {
.architecture = _ARCHITECTURE_INVALID,
.suffix = ".raw",
};
if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "99"));
assert_se(result.architecture == ARCHITECTURE_X86);
assert_se(endswith(result.path, "/foo_99_x86.raw"));
pick_result_done(&result);
}
filter.architecture = ARCHITECTURE_X86_64;
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "55"));
assert_se(result.architecture == ARCHITECTURE_X86_64);
assert_se(endswith(result.path, "/foo_55_x86-64.raw"));
pick_result_done(&result);
filter.architecture = ARCHITECTURE_IA64;
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "5"));
assert_se(result.architecture == ARCHITECTURE_IA64);
assert_se(endswith(result.path, "/foo_5_ia64.raw"));
pick_result_done(&result);
filter.architecture = _ARCHITECTURE_INVALID;
filter.version = "5";
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "5"));
if (native_architecture() != ARCHITECTURE_IA64) {
assert_se(result.architecture == _ARCHITECTURE_INVALID);
assert_se(endswith(result.path, "/foo_5.raw"));
}
pick_result_done(&result);
filter.architecture = ARCHITECTURE_IA64;
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "5"));
assert_se(result.architecture == ARCHITECTURE_IA64);
assert_se(endswith(result.path, "/foo_5_ia64.raw"));
pick_result_done(&result);
filter.architecture = ARCHITECTURE_CRIS;
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == 0);
assert_se(result.st.st_mode == MODE_INVALID);
assert_se(!result.version);
assert_se(result.architecture < 0);
assert_se(!result.path);
assert_se(unlinkat(sub_dfd, "foo_99_x86.raw", 0) >= 0);
filter.architecture = _ARCHITECTURE_INVALID;
filter.version = NULL;
if (IN_SET(native_architecture(), ARCHITECTURE_X86_64, ARCHITECTURE_X86)) {
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "55"));
if (native_architecture() == ARCHITECTURE_X86_64) {
assert_se(result.architecture == ARCHITECTURE_X86_64);
assert_se(endswith(result.path, "/foo_55_x86-64.raw"));
} else {
assert_se(result.architecture == ARCHITECTURE_X86);
assert_se(endswith(result.path, "/foo_55_x86.raw"));
}
pick_result_done(&result);
}
/* Test explicit patterns in last component of path not being .v */
free(pp);
pp = path_join(p, "foo.v/foo___.raw");
assert_se(pp);
if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) {
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "55"));
assert_se(result.architecture == native_architecture());
assert_se(endswith(result.path, ".raw"));
assert_se(strrstr(result.path, "/foo_55_x86"));
pick_result_done(&result);
}
/* Specify an explicit path */
free(pp);
pp = path_join(p, "foo.v/foo_5.raw");
assert_se(pp);
filter.type_mask = UINT32_C(1) << DT_DIR;
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == -ENOTDIR);
filter.type_mask = UINT32_C(1) << DT_REG;
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(!result.version);
assert_se(result.architecture == _ARCHITECTURE_INVALID);
assert_se(path_equal(result.path, pp));
pick_result_done(&result);
free(pp);
pp = path_join(p, "foo.v");
assert_se(pp);
filter.architecture = ARCHITECTURE_S390;
filter.basename = "quux";
assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0);
assert_se(S_ISREG(result.st.st_mode));
assert_se(streq_ptr(result.version, "2"));
assert_se(result.tries_left == 4);
assert_se(result.tries_done == 6);
assert_se(endswith(result.path, "quux_2_s390+4-6.raw"));
assert_se(result.architecture == ARCHITECTURE_S390);
}
DEFINE_TEST_MAIN(LOG_DEBUG);

9
src/vpick/meson.build Normal file
View File

@@ -0,0 +1,9 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
executables += [
executable_template + {
'name' : 'systemd-vpick',
'public' : true,
'sources' : files('vpick-tool.c'),
},
]

350
src/vpick/vpick-tool.c Normal file
View File

@@ -0,0 +1,350 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "architecture.h"
#include "build.h"
#include "format-table.h"
#include "fs-util.h"
#include "main-func.h"
#include "path-util.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "vpick.h"
static char *arg_filter_basename = NULL;
static char *arg_filter_version = NULL;
static Architecture arg_filter_architecture = _ARCHITECTURE_INVALID;
static char *arg_filter_suffix = NULL;
static uint32_t arg_filter_type_mask = 0;
static enum {
PRINT_PATH,
PRINT_FILENAME,
PRINT_VERSION,
PRINT_TYPE,
PRINT_ARCHITECTURE,
PRINT_TRIES,
PRINT_ALL,
_PRINT_INVALID = -EINVAL,
} arg_print = _PRINT_INVALID;
static PickFlags arg_flags = PICK_ARCHITECTURE|PICK_TRIES;
STATIC_DESTRUCTOR_REGISTER(arg_filter_basename, freep);
STATIC_DESTRUCTOR_REGISTER(arg_filter_version, freep);
STATIC_DESTRUCTOR_REGISTER(arg_filter_suffix, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("systemd-vpick", "1", &link);
if (r < 0)
return log_oom();
printf("%1$s [OPTIONS...] PATH...\n"
"\n%5$sPick entry from versioned directory.%6$s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
"\n%3$sLookup Keys:%4$s\n"
" -B --basename=BASENAME\n"
" Look for specified basename\n"
" -V VERSION Look for specified version\n"
" -A ARCH Look for specified architecture\n"
" -S --suffix=SUFFIX Look for specified suffix\n"
" -t --type=TYPE Look for specified inode type\n"
"\n%3$sOutput:%4$s\n"
" -p --print=filename Print selected filename rather than path\n"
" -p --print=version Print selected version rather than path\n"
" -p --print=type Print selected inode type rather than path\n"
" -p --print=arch Print selected architecture rather than path\n"
" -p --print=tries Print selected tries left/tries done rather than path\n"
" -p --print=all Print all of the above\n"
" --resolve=yes Canonicalize the result path\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(), ansi_normal(),
ansi_highlight(), ansi_normal());
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_RESOLVE,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "basename", required_argument, NULL, 'B' },
{ "suffix", required_argument, NULL, 'S' },
{ "type", required_argument, NULL, 't' },
{ "print", required_argument, NULL, 'p' },
{ "resolve", required_argument, NULL, ARG_RESOLVE },
{}
};
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hB:V:A:S:t:p:", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case 'B':
if (!filename_part_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", optarg);
r = free_and_strdup_warn(&arg_filter_basename, optarg);
if (r < 0)
return r;
break;
case 'V':
if (!version_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", optarg);
r = free_and_strdup_warn(&arg_filter_version, optarg);
if (r < 0)
return r;
break;
case 'A':
if (streq(optarg, "native"))
arg_filter_architecture = native_architecture();
else if (streq(optarg, "secondary")) {
#ifdef ARCHITECTURE_SECONDARY
arg_filter_architecture = ARCHITECTURE_SECONDARY;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Local architecture has no secondary architecture.");
#endif
} else if (streq(optarg, "uname"))
arg_filter_architecture = uname_architecture();
else if (streq(optarg, "auto"))
arg_filter_architecture = _ARCHITECTURE_INVALID;
else {
arg_filter_architecture = architecture_from_string(optarg);
if (arg_filter_architecture < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", optarg);
}
break;
case 'S':
if (!filename_part_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", optarg);
r = free_and_strdup_warn(&arg_filter_suffix, optarg);
if (r < 0)
return r;
break;
case 't':
if (isempty(optarg))
arg_filter_type_mask = 0;
else {
mode_t m;
m = inode_type_from_string(optarg);
if (m == MODE_INVALID)
return log_error_errno(m, "Unknown inode type: %s", optarg);
arg_filter_type_mask |= UINT32_C(1) << IFTODT(m);
}
break;
case 'p':
if (streq(optarg, "path"))
arg_print = PRINT_PATH;
else if (streq(optarg, "filename"))
arg_print = PRINT_FILENAME;
else if (streq(optarg, "version"))
arg_print = PRINT_VERSION;
else if (streq(optarg, "type"))
arg_print = PRINT_TYPE;
else if (STR_IN_SET(optarg, "arch", "architecture"))
arg_print = PRINT_ARCHITECTURE;
else if (streq(optarg, "tries"))
arg_print = PRINT_TRIES;
else if (streq(optarg, "all"))
arg_print = PRINT_ALL;
else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --print= argument: %s", optarg);
break;
case ARG_RESOLVE:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse --resolve= value: %m");
SET_FLAG(arg_flags, PICK_RESOLVE, r);
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
}
if (arg_print < 0)
arg_print = PRINT_PATH;
return 1;
}
static int run(int argc, char *argv[]) {
int r;
log_show_color(true);
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
if (optind >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to resolve must be specified.");
for (int i = optind; i < argc; i++) {
_cleanup_free_ char *p = NULL;
r = path_make_absolute_cwd(argv[i], &p);
if (r < 0)
return log_error_errno(r, "Failed to make path '%s' absolute: %m", argv[i]);
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(/* toplevel_path= */ NULL,
/* toplevel_fd= */ AT_FDCWD,
p,
&(PickFilter) {
.basename = arg_filter_basename,
.version = arg_filter_version,
.architecture = arg_filter_architecture,
.suffix = arg_filter_suffix,
.type_mask = arg_filter_type_mask,
},
arg_flags,
&result);
if (r < 0)
return log_error_errno(r, "Failed to pick version for '%s': %m", p);
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching version for '%s' found.", p);
switch (arg_print) {
case PRINT_PATH:
fputs(result.path, stdout);
if (result.st.st_mode != MODE_INVALID && S_ISDIR(result.st.st_mode) && !endswith(result.path, "/"))
fputc('/', stdout);
fputc('\n', stdout);
break;
case PRINT_FILENAME: {
_cleanup_free_ char *fname = NULL;
r = path_extract_filename(result.path, &fname);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", result.path);
puts(fname);
break;
}
case PRINT_VERSION:
if (!result.version)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No version information discovered.");
puts(result.version);
break;
case PRINT_TYPE:
if (result.st.st_mode == MODE_INVALID)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No inode type information discovered.");
puts(inode_type_to_string(result.st.st_mode));
break;
case PRINT_ARCHITECTURE:
if (result.architecture < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No architecture information discovered.");
puts(architecture_to_string(result.architecture));
break;
case PRINT_TRIES:
if (result.tries_left == UINT_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No tries left/tries done information discovered.");
printf("+%u-%u", result.tries_left, result.tries_done);
break;
case PRINT_ALL: {
_cleanup_(table_unrefp) Table *t = NULL;
t = table_new_vertical();
if (!t)
return log_oom();
table_set_ersatz_string(t, TABLE_ERSATZ_NA);
r = table_add_many(
t,
TABLE_FIELD, "Path",
TABLE_PATH, result.path,
TABLE_FIELD, "Version",
TABLE_STRING, result.version,
TABLE_FIELD, "Type",
TABLE_STRING, result.st.st_mode == MODE_INVALID ? NULL : inode_type_to_string(result.st.st_mode),
TABLE_FIELD, "Architecture",
TABLE_STRING, result.architecture < 0 ? NULL : architecture_to_string(result.architecture));
if (r < 0)
return table_log_add_error(r);
if (result.tries_left != UINT_MAX) {
r = table_add_many(
t,
TABLE_FIELD, "Tries left",
TABLE_UINT, result.tries_left,
TABLE_FIELD, "Tries done",
TABLE_UINT, result.tries_done);
if (r < 0)
return table_log_add_error(r);
}
r = table_print(t, stdout);
if (r < 0)
return table_log_print_error(r);
break;
}
default:
assert_not_reached();
}
}
return 0;
}
DEFINE_MAIN_FUNCTION(run);

View File

@@ -493,6 +493,20 @@ systemd-sysext unmerge
rm -rf /run/extensions/app-reject
rm /var/lib/extensions/app-nodistro.raw
# Some super basic test that RootImage= works with .v/ dirs
VBASE="vtest$RANDOM"
VDIR="/tmp/${VBASE}.v"
mkdir "$VDIR"
ln -s "${image}.raw" "$VDIR/${VBASE}_33.raw"
ln -s "${image}.raw" "$VDIR/${VBASE}_34.raw"
ln -s "${image}.raw" "$VDIR/${VBASE}_35.raw"
systemd-run -P -p RootImage="$VDIR" cat /usr/lib/os-release | grep -q -F "MARKER=1"
rm "$VDIR/${VBASE}_33.raw" "$VDIR/${VBASE}_34.raw" "$VDIR/${VBASE}_35.raw"
rmdir "$VDIR"
mkdir -p /run/machines /run/portables /run/extensions
touch /run/machines/a.raw /run/portables/b.raw /run/extensions/c.raw

116
test/units/testsuite-74.vpick.sh Executable file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
at_exit() {
set +e
rm -rf /var/lib/machines/mymachine.raw.v
rm -rf /var/lib/machines/mytree.v
rm -rf /var/lib/machines/testroot.v
umount -l /tmp/dotvroot
rmdir /tmp/dotvroot
}
trap at_exit EXIT
mkdir -p /var/lib/machines/mymachine.raw.v
touch /var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw
touch /var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw
touch /var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw
touch /var/lib/machines/mymachine.raw.v/mymachine_7.7.0_x86-64+0-5.raw
mkdir -p /var/lib/machines/mytree.v
mkdir /var/lib/machines/mytree.v/mytree_33.4
mkdir /var/lib/machines/mytree.v/mytree_33.5
mkdir /var/lib/machines/mytree.v/mytree_36.0+0-5
mkdir /var/lib/machines/mytree.v/mytree_37.0_arm64+2-3
mkdir /var/lib/machines/mytree.v/mytree_38.0_arm64+0-5
ARCH="$(busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager Architecture | cut -d\" -f 2)"
export SYSTEMD_LOG_LEVEL=debug
if [ "$ARCH" = "x86-64" ] ; then
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.13)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.14)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.6.0)
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.7.0)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.7.0_x86-64+0-5.raw"
systemd-dissect --discover | grep "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
elif [ "$ARCH" = "arm64" ] ; then
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.13)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.14)
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.6.0)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.7.0)
systemd-dissect --discover | grep "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
else
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.13)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.5.14)
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.6.0)
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -V 7.7.0)
systemd-dissect --discover | grep "/var/lib/machines/mymachine.raw.v/mymachine_7.5.13.raw"
fi
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A x86-64)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.5.14_x86-64.raw"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A ia64)
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p version)" = "7.6.0"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p type)" = "reg"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p filename)" = "mymachine_7.6.0_arm64.raw"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -p arch)" = "arm64"
test "$(systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t reg)" = "/var/lib/machines/mymachine.raw.v/mymachine_7.6.0_arm64.raw"
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t dir)
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t fifo)
(! systemd-vpick /var/lib/machines/mymachine.raw.v --suffix=.raw -A arm64 -t sock)
if [ "$ARCH" != "arm64" ] ; then
test "$(systemd-vpick /var/lib/machines/mytree.v)" = "/var/lib/machines/mytree.v/mytree_33.5/"
test "$(systemd-vpick /var/lib/machines/mytree.v --type=dir)" = "/var/lib/machines/mytree.v/mytree_33.5/"
else
test "$(systemd-vpick /var/lib/machines/mytree.v)" = "/var/lib/machines/mytree.v/mytree_37.0_arm64+2-3/"
test "$(systemd-vpick /var/lib/machines/mytree.v --type=dir)" = "/var/lib/machines/mytree.v/mytree_37.0_arm64+2-3/"
fi
(! systemd-vpick /var/lib/machines/mytree.v --type=reg)
mkdir /tmp/dotvroot
mount --bind / /tmp/dotvroot
mkdir /var/lib/machines/testroot.v
mkdir /var/lib/machines/testroot.v/testroot_32
ln -s /tmp/dotvroot /var/lib/machines/testroot.v/testroot_33
mkdir /var/lib/machines/testroot.v/testroot_34
ls -l /var/lib/machines/testroot.v
test "$(systemd-vpick /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_34/
test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_34/
(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true)
find /var/lib/machines/testroot.v/testroot_34
rm -rf /var/lib/machines/testroot.v/testroot_34
test "$(systemd-vpick /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_33/
test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /tmp/dotvroot/
systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true
rm /var/lib/machines/testroot.v/testroot_33
test "$(systemd-vpick /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_32/
test "$(systemd-vpick --resolve=yes /var/lib/machines/testroot.v)" = /var/lib/machines/testroot.v/testroot_32/
(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true)
rm -rf /var/lib/machines/testroot.v/testroot_32
(! systemd-vpick /var/lib/machines/testroot.v)
(! systemd-run --wait -p RootDirectory=/var/lib/machines/testroot.v /bin/true)