mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
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:
42
TODO
42
TODO
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
198
man/systemd-vpick.xml
Normal 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>
|
||||
@@ -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
163
man/systemd.v.xml
Normal 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>
|
||||
@@ -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')
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
694
src/shared/vpick.c
Normal 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
59
src/shared/vpick.h
Normal 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;
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
171
src/test/test-vpick.c
Normal 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
9
src/vpick/meson.build
Normal 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
350
src/vpick/vpick-tool.c
Normal 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);
|
||||
@@ -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
116
test/units/testsuite-74.vpick.sh
Executable 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)
|
||||
Reference in New Issue
Block a user