Merge pull request #8620 from poettering/portablectl

an implementation of the "portable services" concept
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-05-25 10:48:12 +02:00 committed by GitHub
commit e2183b09ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
98 changed files with 6332 additions and 716 deletions

17
TODO
View file

@ -28,6 +28,12 @@ Features:
* nspawn: greater control over selinux label?
* sd-event: implement inotify events, as we can safely and robustly do that now
for any inode without fearing confusion by inodes appearing at multiple
places: we can open it with O_PATH first, then store its inode in a hash
table, to recognize duplicate watches before creating (and thus corrupting
pre-existing ones) them, and using /proc/self/fd/ to add it right after.
* the error paths in usbffs_dispatch_ep() leak memory
* cgroups: figure out if we can somehow communicate in a cleaner way whether a
@ -45,6 +51,17 @@ Features:
that our log messages could contain clickable links for example for unit
files and suchlike we operate on.
* introduce a new SystemCallFilters= group called "@system-service" with a
sensible default set for system services, then make use of them in portable
profiles
* add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration)
* add attach --enable and attach --now (for attach+enable+start)
* sync dynamic uids/gids between host+portable srvice (i.e. if DynamicUser=1 is set for a service, make sure that the
selected user is resolvable in the service even if it ships its own /etc/passwd)
* Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the
other doesn't. What a desaster. Probably to exclude it. Also
DECIMAL_STR_WIDTH should probably add an extra "-" into account for negative

251
doc/PORTABLE_SERVICES.md Normal file
View file

@ -0,0 +1,251 @@
# Portable Services Introduction
This systemd version includes a preview of the "portable service"
concept. "Portable Services" are supposed to be an incremental improvement over
traditional system services, making two specific facets of container management
available to system services more readily. Specifically:
1. The bundling of applications, i.e. packing up multiple services, their
binaries and all their dependencies in a single image, and running them
directly from it.
2. Stricter default security policies, i.e. sand-boxing of applications.
The primary tool for interfacing with "portable services" is the new
"portablectl" program. It's currently shipped in /usr/lib/systemd/portablectl
(i.e. not in the `$PATH`), since it's not yet considered part of the officially
supported systemd interfaces — it's a preview still after all.
Portable services don't bring anything inherently new to the table. All they do
is put together known concepts in a slightly nicer way to cover a specific set
of use-cases in a nicer way.
# So, what *is* a "Portable Service"?
A portable service is ultimately just an OS tree, either inside of a directory
tree, or inside a raw disk image containing a Linux file system. This tree is
called the "image". It can be "attached" or "detached" from the system. When
"attached" specific systemd units from the image are made available on the host
system, then behaving pretty much exactly like locally installed system
services. When "detached" these units are removed again from the host, leaving
no artifacts around (except maybe messages they might have logged).
The OS tree/image can be created with any tool of your choice. For example, you
can use `dnf --installroot=` if you like, or `debootstrap`, the image format is
entirely generic, and doesn't have to carry any specific metadata beyond what
distribution images carry anyway. Or to say this differently: the image format
doesn't define any new metadata as unit files and OS tree directories or disk
images are already sufficient, and pretty universally available these days. One
particularly nice tool for creating suitable images is
[mkosi](https://github.com/systemd/mkosi), but many other existing tools will
do too.
If you so will, "Portable Services" are a nicer way to manage chroot()
environments, with better security, tooling and behavior.
# Where's the difference to a "Container"?
"Container" is a very vague term, after all it is used for
systemd-nspawn/LXC-type OS containers, for Docker/rkt-like micro service
containers, and even certain 'lightweight' VM runtimes.
The "portable service" concept ultimately will not provide a fully isolated
environment to the payload, like containers mostly intend to. Instead they are
from the beginning more alike regular system services, can be controlled with
the same tools, are exposed the same way in all infrastructure and so on. Their
main difference is that the use a different root directory than the rest of the
system. Hence, the intention is not to run code in a different, isolated world
from the host — like most containers would do it —, but to run it in the same
world, but with stricter access controls on what the service can see and do.
As one point of differentiation: as programs run as "portable services" are
pretty much regular system services, they won't run as PID 1 (like Docker would
do it), but as normal process. A corollary of that is that they aren't supposed
to manage anything in their own environment (such as the network) as the
execution environment is mostly shared with the rest of the system.
The primary focus use-case of "portable services" is to extend the host system
with encapsulated extensions, but provide almost full integration with the rest
of the system, though possibly restricted by effective security knobs. This
focus includes system extensions otherwise sometimes called "super-privileged
containers".
Note that portable services are only available for system services, not for
user services. i.e. the functionality cannot be used for the stuff
bubblewrap/flatpak is focusing on.
# Mode of Operation
If you have portable service image, maybe in a raw disk image called
`foobar_0.7.23.raw`, then attaching the services to the host is as easy as:
```
# /usr/lib/systemd/portablectl attach foobar_0.7.23.raw
```
This command does the following:
1. It dissects the image, checks and validates the `/etc/os-release` data of
the image, and looks for all included unit files.
2. It copies out all unit files with a suffix of `.service`, `.socket`,
`.target`, `.timer` and `.path`. whose name begins with the image's name
(with the .raw removed), truncated at the first underscore (if there is
one). This prefix name generated from the image name must be followed by a
".", "-" or "@" character in the unit name. Or in other words, given the
image name of `foobar_0.7.23.raw` all unit files matching
`foobar-*.{service|socket|target|timer|path}`,
`foobar@.{service|socket|target|timer|path}` as well as
`foobar.*.{service|socket|target|timer|path}` and
`foobar.{service|socket|target|timer|path}` are copied out. These unit files
are placed in `/etc/systemd/system/` like regular unit files. Within the
images the unit files are looked for at the usual locations, i.e. in
`/usr/lib/systemd/system/` and `/etc/systemd/system/` and so on, relative to
the image's root.
3. For each such unit file a drop-in file is created. Let's say
`foobar-waldo.service` was one of the unit files copied to
`/etc/systemd/system/`, then a drop-in file
`/etc/systemd/system/foobar-waldo.service.d/20-portable.conf` is created,
containing a few lines of additional configuration:
```
[Service]
RootImage=/path/to/foobar.raw
Environment=PORTABLE=foobar
LogExtraFields=PORTABLE=foobar
```
4. For each such unit a "profile" drop-in is linked in. This "profile" drop-in
generally contains security options that lock down the service. By default
the `default` profile is used, which provides a medium level of
security. There's also `trusted` which runs the service at the highest
privileges, i.e. host's root and everything. The `strict' profile comes with
the toughest security restrictions. Finally, `nonetwork` is like `default`
but without network access. Users may define their own profiles too (or
modify the existing ones)
And that's already it.
Note that the images need to stay around (and the same location) as long as the
portable service is attached. If an image is moved, the `RootImage=` line
written to the unit drop-in would point to an non-existing place, and break the
logic.
The `portablectl detach` command executes the reverse operation: it looks for
the drop-ins and the unit files associated with the image, and removes them
again.
Note that `portable attach` won't enable or start any of the units it copies
out. This still has to take place in a second, separate step. (That said We
might add options to do this automatically later on.).
# Requirements on Images
Note that portable services don't introduce any new image format, but most OS
images should just work the way they are. Specifically, the following
requirements are made for an image that can be attached/detached with
`portablectl`.
1. It must contain a binary (and its dependencies) that shall be invoked,
including all its dependencies. If binary code, the code needs to be
compiled for an architecture compatible with the host.
2. The image must either be a plain sub-directory (or btrfs subvolume)
containing the binaries and its dependencies in a classic Linux OS tree, or
must be a raw disk image either containing only one, naked file system, or
an image with a partition table understood by the Linux kernel with only a
single partition defined, or alternatively, a GPT partition table with a set
of properly marked partitions following the [Discoverable Partitions
Specification](https://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/).
3. The image must at least contain one matching unit file, with the right name
prefix and suffix (see above). The unit file is searched in the usual paths,
i.e. primarily /etc/systemd/system/ and /usr/lib/systemd/system/ within the
image. (The implementation will check a couple of other paths too, but it's
recommended to use these two paths.)
4. The image must contain an os-release file, either in /etc/os-release or
/usr/lib/os-release. The file should follow the standard format.
Note that generally images created by tools such as `debootstrap`, `dnf
--installroot=` or `mkosi` qualify for all of the above in one way or
another. If you wonder what the most minimal image would be that complies with
the requirements above, it could consist of this:
```
/usr/bin/minimald # a statically compiled binary
/usr/lib/systemd/minimal-test.service # the unit file for the service, with ExecStart=/usr/bin/minimald
/usr/lib/os-release # an os-release file explaining what this is
```
And that's it.
Note that qualifying images do not have to contain an init system of their
own. If they do, it's fine, it will be ignored by the portable service logic,
but they generally don't have to, and it might make sense to avoid any, to keep
images minimal.
Note that as no new image format or metadata is defined, it's very
straight-forward to define images than can be made use of it a number of
different ways. For example, by using `mkosi -b` you can trivially build a
single, unified image that:
1. Can be attached as portable service, to run any container services natively
on the host.
2. Can be run as OS container, using `systemd-nspawn`, by booting the image
with `systemd-nspawn -i -b`.
3. Can be booted directly as VM image, using a generic VM executor such as
`virtualbox`/`qemu`/`kvm`
4. Can be booted directly on bare-metal systems.
Of course, to facilitate 2, 3 and 4 you need to include an init system in the
image. To facility 3 and 4 you also need to include a boot loader in the
image. As mentioned `mkosi -b` takes care of all of that for you, but any other
image generator should work too.
# Execution Environment
Note that the code in portable service images is run exactly like regular
services. Hence there's no new execution environment to consider. Oh, unlike
Docker would do it, as these are regular system services they aren't run as PID
1 either, but with regular PID values.
# Access to host resources
If services shipped with this mechanism shall be able to access host resources
(such as files or AF_UNIX sockets for IPC), use the normal `BindPaths=` and
`BindReadOnlyPaths=` settings in unit files to mount them in. In fact the
`default` profile mentioned above makes use of this to ensure
`/etc/resolv.conf`, the D-Bus system bus socket or write access to the logging
subsystem are available to the service.
# Instantiation
Sometimes it makes sense to instantiate the same set of services multiple
times. The portable service concept does not introduce a new logic for this. It
is recommended to use the regular unit templating of systemd for this, i.e. to
include template units such as `foobar@.service`, so that instantiation is as
simple as:
```
# /usr/lib/systemd/portablectl attach foobar_0.7.23.raw
# systemctl enable --now foobar@instancea.service
# systemctl enable --now foobar@instanceb.service
```
The benefit of this approach is that templating works exactly the same for
units shipped with the OS itself as for attached portable services.
# Immutable images with local data
It's a good idea to keep portable service images read-only during normal
operation. In fact all but the `trusted` profile will default to this kind of
behaviour, by setting the `ProtectSystem=strict` option. In this case writable
service data may be placed on the host file system. Use `StateDirectory=` in
the unit files to enable such behaviour and add a local data directory to the
services copied onto the host.

404
man/portablectl.xml Normal file
View file

@ -0,0 +1,404 @@
<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<refentry id="portablectl" conditional='ENABLE_PORTABLED'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>portablectl</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Lennart</firstname>
<surname>Poettering</surname>
<email>lennart@poettering.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>portablectl</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>portablectl</refname>
<refpurpose>Attach, detach or inspect portable service images</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>portablectl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="req">COMMAND</arg>
<arg choice="opt" rep="repeat">NAME</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>portablectl</command> may be used to attach, detach or inspect portable service images. It's
primarily a command interfacing with
<citerefentry><refentrytitle>systemd-portabled.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>Portable service images contain an OS file system tree along with
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> unit file
information. A service image may be "attached" to the local system. If attached, a set of unit files are copied
from the image to the host, and extended with <varname>RootDirectory=</varname> or <varname>RootImage=</varname>
assignments (in case of service units) pointing to the image file or directory, ensuring the services will run
within the file system context of the image.</para>
<para>Portable service images are an efficient way to bundle multiple related services and other units together,
and transfer them as a whole between systems. When these images are attached the local system the contained units
may run in most ways like regular system-provided units, either with full privileges or inside strict sandboxing,
depending on the selected configuration.</para>
<para>Specifically portable service images may be of the following kind:</para>
<itemizedlist>
<listitem><para>Directory trees containing an OS, including the top-level directories <filename>/usr/</filename>,
<filename>/etc/</filename>, and so on.</para></listitem>
<listitem><para>btrfs subvolumes containing OS trees, similar to normal directory trees.</para></listitem>
<listitem><para>Binary "raw" disk images containing MBR or GPT partition tables and Linux file system
partitions.</para></listitem>
</itemizedlist>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>-q</option></term>
<term><option>--quiet</option></term>
<listitem><para>Suppresses additional informational output while running.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-p</option> <replaceable>PROFILE</replaceable></term>
<term><option>--profile=</option><replaceable>PROFILE</replaceable></term>
<listitem><para>When attaching an image, select the profile to use. By default the <literal>default</literal>
profile is used. For details about profiles, see below.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--copy=</option></term>
<listitem><para>When attaching an image, select whether to prefer copying or symlinking of files installed into
the host system. Takes one of <literal>copy</literal> (to prefer copying of files), <literal>symlink</literal>
(to prefer creation of symbolic links) or <literal>auto</literal> for an intermediary mode where security
profile drop-ins are symlinked while unit files are copied. Note that this option expresses a preference only,
in cases where symbolic links cannot be created — for example when the image operated on is a raw disk image,
and hence not directly referentiable from the host file system — copying of files is used
unconditionally.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--runtime</option></term>
<listitem><para>When specified the unit and drop-in files are placed in
<filename>/run/systemd/system/</filename> instead of <filename>/etc/systemd/system/</filename>. Images attached
with this option set hence remain attached only until the next reboot, while they are normally attached
persistently.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-reload</option></term>
<listitem><para>Don't reload the service manager after attaching or detaching a portable service
image. Normally the service manager is reloaded to ensure it is aware of added or removed unit
files.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--cat</option></term>
<listitem><para>When inspecting portable service images, show the (unprocessed) contents of the metadata files
pulled from the image, instead of brief summaries. Specifically, this will show the
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> and unit file
contents of the image.</para></listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="no-ask-password" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Commands</title>
<para>The following commands are understood:</para>
<variablelist>
<varlistentry>
<term><command>list</command></term>
<listitem><para>List available portable service images. This will list all portable service images discovered
in the portable image search paths (see below), along with brief metadata and state information. Note that many
of the commands below may both operate on images inside and outside of the search paths. This command is hence
mostly a convenience option, the commands are generally not restricted to what this list
shows.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>attach</command> <replaceable>IMAGE</replaceable> [<replaceable>PREFIX…</replaceable>]</term>
<listitem><para>Attach a portable service image to the host system. Expects a file system path to a portable
service image file or directory as first argument. If the specified path contains no slash character
(<literal>/</literal>) it is understood as image filename that is searched for in the portable service image
search paths (see below). To reference a file in the current working directory prefix the filename with
<literal>./</literal> to avoid this search path logic.</para>
<para>When a portable service is attached four operations are executed:</para>
<orderedlist>
<listitem><para>All unit files of types <filename>.service</filename>, <filename>.socket</filename>,
<filename>.target</filename>, <filename>.timer</filename> and <filename>.path</filename> which match the
indicated unit file name prefix are copied from the image to the host's
<filename>/etc/systemd/system/</filename> directory (or <filename>/run/systemd/system/</filename> — depending
whether <option>--runtime</option> is specified, see above).</para></listitem>
<listitem><para>For unit files of type <filename>.service</filename> a drop-in is added to these copies that
adds <varname>RootDirectory=</varname> or <varname>RootImage=</varname> settings (see
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details), that ensures these services are run within the file system of the originating portable service
image.</para></listitem>
<listitem><para>A second drop-in is created: the "profile" drop-in, that may contain additional security
settings (and other settings). A number of profiles are available by default but administrators may define
their own ones. See below.</para></listitem>
<listitem><para>If the portable service image file is not already in the search path (see below), a symbolic
link to it is created in <filename>/etc/portables/</filename> or
<filename>/run/portables/</filename>, to make sure it is included in it.</para></listitem>
</orderedlist>
<para>By default all unit files whose names start with a prefix generated from the image's file name are copied
out. Specifically, the prefix is determined from the image file name with any suffix such as
<filename>.raw</filename> removed, truncated at the first occurence of and underscore character
(<literal>_</literal>), if there is one. The underscore logic is supposed to be used to versioning so that the
an image file <filename>foobar_47.11.raw</filename> will result in a unit file matching prefix of
<filename>foobar</filename>. This prefix is then compared with all unit files names contained in the image in
the usual directories, but only unit file names where the prefix is followed by <literal>-</literal>,
<literal>.</literal> or <literal>@</literal> are considered. Example: if a portable service image file is named
<filename>foobar_47.11.raw</filename> then by default all its unit files with names such as
<filename>foobar-quux-waldi.service</filename>, <filename>foobar.service</filename> or
<filename>foobar@.service</filename> will be considered. It's possible to override the matching prefix: all
strings listed on the command line after the image file name are considered prefixes, overriding the implicit
logic where the prefix is derived from the image file name.</para>
<para>By default, after the unit files are attached the service manager's configuration is reloaded, except
when <option>--no-reload</option> is specified (see above). This ensures that the new units made available to
the service manager are seen by it.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>detach</command> <replaceable>IMAGE</replaceable></term>
<listitem><para>Detaches a portable service image from the host. This undoes the operations executed by the
<command>attach</command> command above, and removes the unit file copies, drop-ins and image symlink
again. This command expects an image name or path as parameter. Note that if a path is specified only the last
component of it (i.e. the file or directory name itself, not the path to it) is used for finding matching unit
files. This is a convencience feature to allow all arguments passed as <command>attach</command> also to
<command>detach</command>.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>inspect</command> <replaceable>IMAGE</replaceable> [<replaceable>PREFIX…</replaceable>]</term>
<listitem><para>Extracts various metadata from a portable service image and presents it to the
caller. Specifically, the
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file of the
image is retrieved as well as all matching unit files. By default a short summary showing the most relevant
metadata in combination with a list of matching unit files is shown (that is the unit files
<command>attach</command> would install to the host system). If combined with <option>--cat</option> (see
above), the <filename>os-release</filename> data and the units files' contents is displayed unprocessed. This
command is useful to determine whether an image qualifies as portable service image, and which unit files are
included. This command expects the path to the image as parameter, optionally followed by a list of unit file
prefixes to consider, similar to the <command>attach</command> command described above.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>is-attached</command> <replaceable>IMAGE</replaceable></term>
<listitem><para>Determines whether the specified image is currently attached or not. Unless combined with the
<option>--quiet</option> switch this will show a short state identifier for the image. Specifically:</para>
<table>
<title>Image attachment states</title>
<tgroup cols='2'>
<colspec colname='state'/>
<colspec colname='description'/>
<thead>
<row>
<entry>State</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><option>detached</option></entry>
<entry>The image is currently not attached.</entry>
</row>
<row>
<entry><option>attached</option></entry>
<entry>The image is currently attached, i.e. its unit files have been made available to the host system.</entry>
</row>
<row>
<entry><option>attached-runtime</option></entry>
<entry>Like <option>attached</option>, but the the unit files have been made available transiently only, i.e. the <command>attach</command> command has been invoked with the <option>--runtime</option> option.</entry>
</row>
<row>
<entry><option>enabled</option></entry>
<entry>The image is currently attached, and at least one unit file associated with it has been enabled.</entry>
</row>
<row>
<entry><option>enabled-runtime</option></entry>
<entry>Like <option>enabled</option>, but the the unit files have been made available transiently only, i.e. the <command>attach</command> command has been invoked with the <option>--runtime</option> option.</entry>
</row>
<row>
<entry><option>running</option></entry>
<entry>The image is currently attached, and at least one unit file associated with it is running.</entry>
</row>
<row>
<entry><option>running-runtime</option></entry>
<entry>The image is currently attached transiently, and at least one unit file associated with it is running.</entry>
</row>
</tbody>
</tgroup>
</table>
</listitem>
</varlistentry>
<varlistentry>
<term><command>read-only</command> <replaceable>IMAGE</replaceable> [<replaceable>BOOL</replaceable>]</term>
<listitem><para>Marks or (unmarks) a portable service image read-only. Takes an image name, followed by a
boolean as arguments. If the boolean is omitted, positive is implied, i.e. the image is marked
read-only.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>remove</command> <replaceable>IMAGE</replaceable></term>
<listitem><para>Removes one or more portable service images. Note that this command will only remove the
specified image path itself — it it refers to a symbolic link then the symbolic link is removed and not the
image it points to.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>set-limit</command> [<replaceable>NAME</replaceable>] <replaceable>BYTES</replaceable></term>
<listitem><para>Sets the maximum size in bytes that a specific portable service image, or all images, may grow
up to on disk (disk quota). Takes either one or two parameters. The first, optional parameter refers to a
portable service image name. If specified, the size limit of the specified image is changed. If omitted, the
overall size limit of the sum of all images stored locally is changed. The final argument specifies the size
limit in bytes, possibly suffixed by the usual K, M, G, T units. If the size limit shall be disabled, specify
<literal>-</literal> as size.</para>
<para>Note that per-image size limits are only supported on btrfs file systems. Also, depending on
<varname>BindPaths=</varname> settings in the portable service's unit files directories from the host might be
visible in the image environment during runtime which are not affected by this setting, as only the image
itself is counted against this limit.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Files and Directories</title>
<para>Portable service images are preferably stored in <filename>/var/lib/portables/</filename>, but are also
searched for in <filename>/etc/portables/</filename>, <filename>/run/systemd/portables/</filename>,
<filename>/usr/local/lib/portables/</filename> and <filename>/usr/lib/portables/</filename>. It's recommended not
to place image files directly in <filename>/etc/portables/</filename> or
<filename>/run/systemd/portables/</filename> (as these are generally not suitable for storing large or non-textual
data), but use these directories only for linking images located elsewhere into the image search path.</para>
</refsect1>
<refsect1>
<title>Profiles</title>
<para>When portable service images are attached a "profile" drop-in is linked in, which may be used to enforce
additional security (and other) restrictions locally. Four profile drop-ins are defined by default, and shipped in
<filename>/usr/lib/systemd/portable/profile/</filename>. Additional, local profiles may be defined by placing them
in <filename>/etc/systemd/portable/profile/</filename>. The default profiles are:</para>
<table>
<title>Profiles</title>
<tgroup cols='2'>
<colspec colname='state'/>
<colspec colname='description'/>
<thead>
<row>
<entry>Name</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><filename>default</filename></entry>
<entry>This is the default profile if no other profile name is set via the <option>--profile=</option> (see above). It's fairly restrictive, but should be useful for common, unprivileged system workloads. This includes write access to the logging framework, as well as IPC access to the D-Bus system.</entry>
</row>
<row>
<entry><filename>nonetwork</filename></entry>
<entry>Very similar to <filename>default</filename>, but networking is turned off for any services of the portable service image.</entry>
</row>
<row>
<entry><filename>strict</filename></entry>
<entry>A profile with very strict settings. This profile excludes IPC (D-Bus) and network access.</entry>
</row>
<row>
<entry><filename>trusted</filename></entry>
<entry>A profile with very relaxed settings. In this profile the services run with full privileges.</entry>
</row>
</tbody>
</tgroup>
</table>
<para>For details on this profiles, and their effects please have a look at their precise definitions,
e.g. <filename>/usr/lib/systemd/portable/profile/default/service.conf</filename> and similar.</para>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<xi:include href="less-variables.xml" />
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-portabled.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View file

@ -43,6 +43,7 @@ manpages = [
['nss-systemd', '8', ['libnss_systemd.so.2'], 'ENABLE_NSS_SYSTEMD'],
['os-release', '5', [], ''],
['pam_systemd', '8', [], 'HAVE_PAM'],
['portablectl', '1', [], 'ENABLE_PORTABLED'],
['resolvectl', '1', ['resolvconf'], 'ENABLE_RESOLVE'],
['resolved.conf', '5', ['resolved.conf.d'], 'ENABLE_RESOLVE'],
['runlevel', '8', [], 'ENABLE_UTMP'],
@ -611,6 +612,7 @@ manpages = [
['systemd-notify', '1', [], ''],
['systemd-nspawn', '1', [], ''],
['systemd-path', '1', [], ''],
['systemd-portabled.service', '8', ['systemd-portabled'], 'ENABLE_PORTABLED'],
['systemd-quotacheck.service',
'8',
['systemd-quotacheck'],

View file

@ -0,0 +1,61 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<refentry id="systemd-portabled.service" conditional='ENABLE_PORTABLED'>
<refentryinfo>
<title>systemd-portabled.service</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Lennart</firstname>
<surname>Poettering</surname>
<email>lennart@poettering.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-portabled.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-portabled.service</refname>
<refname>systemd-portabled</refname>
<refpurpose>Portable service manager</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-portabled.service</filename></para>
<para><filename>/usr/lib/systemd/systemd-portabled</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-portabled</command> is a system service that may be used to attach, detach and inspect
portable service images.</para>
<para>Most of <command>systemd-portabled</command>'s functionality is accessible through the
<citerefentry><refentrytitle>portablectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> command.</para>
<para>See the <ulink url="https://github.com/systemd/systemd/blob/master/doc/PORTABLE_SERVICES.md">Portable
Services Documentation</ulink> for details about the concepts this service implements.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>portablectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View file

@ -138,6 +138,7 @@ testsdir = join_paths(prefixdir, 'lib/systemd/tests')
systemdstatedir = join_paths(localstatedir, 'lib/systemd')
catalogstatedir = join_paths(systemdstatedir, 'catalog')
randomseeddir = join_paths(localstatedir, 'lib/systemd')
profiledir = join_paths(rootlibexecdir, 'portable', 'profile')
docdir = get_option('docdir')
if docdir == ''
@ -1177,6 +1178,7 @@ foreach term : ['utmp',
'hostnamed',
'localed',
'machined',
'portabled',
'networkd',
'timedated',
'timesyncd',
@ -1355,6 +1357,7 @@ subdir('src/import')
subdir('src/kernel-install')
subdir('src/locale')
subdir('src/machine')
subdir('src/portable')
subdir('src/nspawn')
subdir('src/resolve')
subdir('src/timedate')
@ -1716,6 +1719,26 @@ exe = executable('systemctl', 'src/systemctl/systemctl.c',
install_dir : rootbindir)
public_programs += [exe]
if conf.get('ENABLE_PORTABLED') == 1
executable('systemd-portabled',
systemd_portabled_sources,
include_directories : includes,
link_with : [libshared],
dependencies : [threads],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
exe = executable('portablectl', 'src/portable/portablectl.c',
include_directories : includes,
link_with : [libshared],
dependencies : [threads],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
public_programs += [exe]
endif
foreach alias : ['halt', 'poweroff', 'reboot', 'runlevel', 'shutdown', 'telinit']
meson.add_install_script(meson_make_symlink,
join_paths(rootbindir, 'systemctl'),
@ -2895,6 +2918,7 @@ foreach tuple : [
['rfkill'],
['logind'],
['machined'],
['portabled'],
['importd'],
['hostnamed'],
['timedated'],

View file

@ -79,6 +79,8 @@ option('localed', type : 'boolean',
description : 'install the systemd-localed stack')
option('machined', type : 'boolean',
description : 'install the systemd-machined stack')
option('portabled', type : 'boolean',
description : 'install the systemd-portabled stack')
option('networkd', type : 'boolean',
description : 'install the systemd-networkd stack')
option('timedated', type : 'boolean',

View file

@ -21,18 +21,28 @@
#include "macro.h"
#include "missing.h"
#include "path-util.h"
#include "set.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "util.h"
static int files_add(Hashmap *h, const char *suffix, const char *root, unsigned flags, const char *path) {
static int files_add(
Hashmap *h,
Set *masked,
const char *suffix,
const char *root,
unsigned flags,
const char *path) {
_cleanup_closedir_ DIR *dir = NULL;
const char *dirpath;
struct dirent *de;
int r;
assert(h);
assert((flags & CONF_FILES_FILTER_MASKED) == 0 || masked);
assert(path);
dirpath = prefix_roota(root, path);
@ -41,62 +51,89 @@ static int files_add(Hashmap *h, const char *suffix, const char *root, unsigned
if (!dir) {
if (errno == ENOENT)
return 0;
return -errno;
return log_debug_errno(errno, "Failed to open directory '%s': %m", dirpath);
}
FOREACH_DIRENT(de, dir, return -errno) {
char *p;
struct stat st;
char *p, *key;
if (!dirent_is_file_with_suffix(de, suffix)) {
log_debug("Ignoring %s/%s, because it's not a regular file with suffix %s.", dirpath, de->d_name, strna(suffix));
/* Does this match the suffix? */
if (suffix && !endswith(de->d_name, suffix))
continue;
/* Has this file already been found in an earlier directory? */
if (hashmap_contains(h, de->d_name)) {
log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
continue;
}
if (flags & CONF_FILES_EXECUTABLE) {
struct stat st;
/* As requested: check if the file is marked exectuable. Note that we don't check access(X_OK)
* here, as we care about whether the file is marked executable at all, and not whether it is
* executable for us, because if such errors are stuff we should log about. */
/* Has this been masked in an earlier directory? */
if ((flags & CONF_FILES_FILTER_MASKED) && set_contains(masked, de->d_name)) {
log_debug("File '%s/%s' is masked by previous entry.", dirpath, de->d_name);
continue;
}
/* Read file metadata if we shall validate the check for file masks, for node types or whether the node is marked executable. */
if (flags & (CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR|CONF_FILES_DIRECTORY|CONF_FILES_EXECUTABLE))
if (fstatat(dirfd(dir), de->d_name, &st, 0) < 0) {
log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", dirpath, de->d_name);
log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m", dirpath, de->d_name);
continue;
}
if (!null_or_empty(&st)) {
/* A mask is a symlink to /dev/null or an empty file. It does not even
* have to be executable. Other entries must be regular executable files
* or symlinks to them. */
if (S_ISREG(st.st_mode)) {
if ((st.st_mode & 0111) == 0) { /* not executable */
log_debug("Ignoring %s/%s, as it is not marked executable.",
dirpath, de->d_name);
continue;
}
} else {
log_debug("Ignoring %s/%s, as it is neither a regular file nor a mask.",
dirpath, de->d_name);
continue;
}
/* Is this a masking entry? */
if ((flags & CONF_FILES_FILTER_MASKED))
if (null_or_empty(&st)) {
/* Mark this one as masked */
r = set_put_strdup(masked, de->d_name);
if (r < 0)
return r;
log_debug("File '%s/%s' is a mask.", dirpath, de->d_name);
continue;
}
/* Does this node have the right type? */
if (flags & (CONF_FILES_REGULAR|CONF_FILES_DIRECTORY))
if (!((flags & CONF_FILES_DIRECTORY) && S_ISDIR(st.st_mode)) &&
!((flags & CONF_FILES_REGULAR) && S_ISREG(st.st_mode))) {
log_debug("Ignoring '%s/%s', as it is not a of the right type.", dirpath, de->d_name);
continue;
}
/* Does this node have the executable bit set? */
if (flags & CONF_FILES_EXECUTABLE)
/* As requested: check if the file is marked exectuable. Note that we don't check access(X_OK)
* here, as we care about whether the file is marked executable at all, and not whether it is
* executable for us, because if so, such errors are stuff we should log about. */
if ((st.st_mode & 0111) == 0) { /* not executable */
log_debug("Ignoring '%s/%s', as it is not marked executable.", dirpath, de->d_name);
continue;
}
if (flags & CONF_FILES_BASENAME) {
p = strdup(de->d_name);
if (!p)
return -ENOMEM;
key = p;
} else {
p = strjoin(dirpath, "/", de->d_name);
if (!p)
return -ENOMEM;
key = basename(p);
}
p = strjoin(dirpath, "/", de->d_name);
if (!p)
return -ENOMEM;
r = hashmap_put(h, basename(p), p);
if (r == -EEXIST) {
log_debug("Skipping overridden file: %s.", p);
free(p);
} else if (r < 0) {
free(p);
return r;
} else if (r == 0) {
log_debug("Duplicate file %s", p);
r = hashmap_put(h, key, p);
if (r < 0) {
free(p);
return log_debug_errno(r, "Failed to add item to hashmap: %m");
}
assert(r > 0);
}
return 0;
@ -112,6 +149,7 @@ static int base_cmp(const void *a, const void *b) {
static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, unsigned flags, char **dirs) {
_cleanup_hashmap_free_ Hashmap *fh = NULL;
_cleanup_set_free_free_ Set *masked = NULL;
char **files, **p;
int r;
@ -121,12 +159,18 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const
if (!path_strv_resolve_uniq(dirs, root))
return -ENOMEM;
fh = hashmap_new(&string_hash_ops);
fh = hashmap_new(&path_hash_ops);
if (!fh)
return -ENOMEM;
if (flags & CONF_FILES_FILTER_MASKED) {
masked = set_new(&path_hash_ops);
if (!masked)
return -ENOMEM;
}
STRV_FOREACH(p, dirs) {
r = files_add(fh, suffix, root, flags, *p);
r = files_add(fh, masked, suffix, root, flags, *p);
if (r == -ENOMEM)
return r;
if (r < 0)

View file

@ -9,7 +9,11 @@
***/
enum {
CONF_FILES_EXECUTABLE = 1,
CONF_FILES_EXECUTABLE = 1U << 0,
CONF_FILES_REGULAR = 1U << 1,
CONF_FILES_DIRECTORY = 1U << 2,
CONF_FILES_BASENAME = 1U << 3,
CONF_FILES_FILTER_MASKED = 1U << 4,
};
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir, ...);

View file

@ -89,7 +89,7 @@ static int do_execute(
* If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
*/
r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE, (const char* const*) directories);
r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
if (r < 0)
return r;

View file

@ -12,10 +12,13 @@
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
#include "copy.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "io-util.h"
#include "macro.h"
#include "memfd-util.h"
#include "missing.h"
@ -562,6 +565,202 @@ try_dev_shm_without_o_tmpfile:
return -EOPNOTSUPP;
}
/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
#define DATA_FD_MEMORY_LIMIT (64U*1024U)
/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
#define DATA_FD_TMP_LIMIT (1024U*1024U)
int fd_duplicate_data_fd(int fd) {
_cleanup_close_ int copy_fd = -1, tmp_fd = -1;
_cleanup_free_ void *remains = NULL;
_cleanup_free_ char *t = NULL;
size_t remains_size = 0;
const char *td;
struct stat st;
int r;
/* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but
* independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be
* somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported
* uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in
* /var/tmp. */
if (fstat(fd, &st) < 0)
return -errno;
/* For now, let's only accept regular files, sockets, pipes and char devices */
if (S_ISDIR(st.st_mode))
return -EISDIR;
if (S_ISLNK(st.st_mode))
return -ELOOP;
if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode))
return -EBADFD;
/* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note
* that we use the reported regular file size only as a hint, given that there are plenty special files in
* /proc and /sys which report a zero file size but can be read from. */
if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
/* Try a memfd first */
copy_fd = memfd_new("data-fd");
if (copy_fd >= 0) {
off_t f;
r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0);
if (r < 0)
return r;
f = lseek(copy_fd, 0, SEEK_SET);
if (f != 0)
return -errno;
if (r == 0) {
/* Did it fit into the limit? If so, we are done. */
r = memfd_set_sealed(copy_fd);
if (r < 0)
return r;
return TAKE_FD(copy_fd);
}
/* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */
} else {
_cleanup_(close_pairp) int pipefds[2] = { -1, -1 };
int isz;
/* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather
* then block indefinitely when we hit the pipe size limit */
if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
return -errno;
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
/* Try to enlarge the pipe size if necessary */
if ((size_t) isz < DATA_FD_MEMORY_LIMIT) {
(void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT);
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
if (isz < 0)
return -errno;
}
if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) {
r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size);
if (r < 0 && r != -EAGAIN)
return r; /* If we get EAGAIN it could be because of the source or because of
* the destination fd, we can't know, as sendfile() and friends won't
* tell us. Hence, treat this as reason to fall back, just to be
* sure. */
if (r == 0) {
/* Everything fit in, yay! */
(void) fd_nonblock(pipefds[0], false);
return TAKE_FD(pipefds[0]);
}
/* Things didn't fit in. But we read data into the pipe, let's remember that, so that
* when writing the new file we incorporate this first. */
copy_fd = TAKE_FD(pipefds[0]);
}
}
}
/* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
(DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
off_t f;
tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC);
if (tmp_fd < 0)
return tmp_fd;
if (copy_fd >= 0) {
/* If we tried a memfd/pipe first and it ended up being too large, then copy this into the
* temporary file first. */
r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0);
if (r < 0)
return r;
assert(r == 0);
}
if (remains_size > 0) {
/* If there were remaining bytes (i.e. read into memory, but not written out yet) from the
* failed copy operation, let's flush them out next. */
r = loop_write(tmp_fd, remains, remains_size, false);
if (r < 0)
return r;
}
r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK);
if (r < 0)
return r;
if (r == 0)
goto finish; /* Yay, it fit in */
/* It didn't fit in. Let's not forget to use what we already used */
f = lseek(tmp_fd, 0, SEEK_SET);
if (f != 0)
return -errno;
safe_close(copy_fd);
copy_fd = TAKE_FD(tmp_fd);
remains = mfree(remains);
remains_size = 0;
}
/* As last fallback use /var/tmp */
r = var_tmp_dir(&td);
if (r < 0)
return r;
tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
if (tmp_fd < 0)
return tmp_fd;
if (copy_fd >= 0) {
/* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this
* into the temporary file first. */
r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
if (r < 0)
return r;
assert(r == 0);
}
if (remains_size > 0) {
/* Then, copy in any read but not yet written bytes. */
r = loop_write(tmp_fd, remains, remains_size, false);
if (r < 0)
return r;
}
/* Copy in the rest */
r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
if (r < 0)
return r;
assert(r == 0);
finish:
/* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the
* file again */
return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC);
}
int fd_move_above_stdio(int fd) {
int flags, copy;
PROTECT_ERRNO;

View file

@ -81,6 +81,8 @@ enum {
int acquire_data_fd(const void *data, size_t size, unsigned flags);
int fd_duplicate_data_fd(int fd);
/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
#define ERRNO_IS_DISCONNECT(r) \
IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)

View file

@ -263,29 +263,35 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
}
int read_full_stream(FILE *f, char **contents, size_t *size) {
size_t n, l;
_cleanup_free_ char *buf = NULL;
struct stat st;
size_t n, l;
int fd;
assert(f);
assert(contents);
if (fstat(fileno(f), &st) < 0)
return -errno;
n = LINE_MAX;
if (S_ISREG(st.st_mode)) {
fd = fileno(f);
if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
* optimize our buffering) */
/* Safety check */
if (st.st_size > READ_FULL_BYTES_MAX)
return -E2BIG;
if (fstat(fileno(f), &st) < 0)
return -errno;
/* Start with the right file size, but be prepared for files from /proc which generally report a file
* size of 0. Note that we increase the size to read here by one, so that the first read attempt
* already makes us notice the EOF. */
if (st.st_size > 0)
n = st.st_size + 1;
if (S_ISREG(st.st_mode)) {
/* Safety check */
if (st.st_size > READ_FULL_BYTES_MAX)
return -E2BIG;
/* Start with the right file size, but be prepared for files from /proc which generally report a file
* size of 0. Note that we increase the size to read here by one, so that the first read attempt
* already makes us notice the EOF. */
if (st.st_size > 0)
n = st.st_size + 1;
}
}
l = 0;
@ -676,21 +682,41 @@ static int parse_env_file_push(
return 0;
}
int parse_env_file(
int parse_env_filev(
FILE *f,
const char *fname,
const char *newline, ...) {
const char *newline,
va_list ap) {
va_list ap;
int r, n_pushed = 0;
va_list aq;
if (!newline)
newline = NEWLINE;
va_copy(aq, ap);
r = parse_env_file_internal(f, fname, newline, parse_env_file_push, &aq, &n_pushed);
va_end(aq);
if (r < 0)
return r;
return n_pushed;
}
int parse_env_file(
FILE *f,
const char *fname,
const char *newline,
...) {
va_list ap;
int r;
va_start(ap, newline);
r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
r = parse_env_filev(f, fname, newline, ap);
va_end(ap);
return r < 0 ? r : n_pushed;
return r;
}
static int load_env_file_push(

View file

@ -45,7 +45,8 @@ int read_full_stream(FILE *f, char **contents, size_t *size);
int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
int parse_env_filev(FILE *f, const char *fname, const char *separator, va_list ap);
int parse_env_file(FILE *f, const char *fname, const char *separator, ...) _sentinel_;
int load_env_file(FILE *f, const char *fname, const char *separator, char ***l);
int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l);

View file

@ -131,6 +131,8 @@ basic_sources = files('''
ordered-set.h
pager.c
pager.h
os-util.c
os-util.h
parse-util.c
parse-util.h
path-util.c

117
src/basic/os-util.c Normal file
View file

@ -0,0 +1,117 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "macro.h"
#include "os-util.h"
#include "strv.h"
#include "fileio.h"
#include "string-util.h"
int path_is_os_tree(const char *path) {
int r;
assert(path);
/* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
* always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
* the case where just the os-release file is missing. */
if (laccess(path, F_OK) < 0)
return -errno;
/* We use {/etc|/usr/lib}/os-release as flag file if something is an OS */
r = open_os_release(path, NULL, NULL);
if (r == -ENOENT) /* We got nothing */
return 0;
if (r < 0)
return r;
return 1;
}
int open_os_release(const char *root, char **ret_path, int *ret_fd) {
_cleanup_free_ char *q = NULL;
const char *p;
int k;
FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
k = chase_symlinks(p, root, CHASE_PREFIX_ROOT|(ret_fd ? CHASE_OPEN : 0), (ret_path ? &q : NULL));
if (k != -ENOENT)
break;
}
if (k < 0)
return k;
if (ret_fd) {
int real_fd;
/* Convert the O_PATH fd into a proper, readable one */
real_fd = fd_reopen(k, O_RDONLY|O_CLOEXEC|O_NOCTTY);
safe_close(k);
if (real_fd < 0)
return real_fd;
*ret_fd = real_fd;
}
if (ret_path)
*ret_path = TAKE_PTR(q);
return 0;
}
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
_cleanup_free_ char *p = NULL;
_cleanup_close_ int fd = -1;
FILE *f;
int r;
if (!ret_file)
return open_os_release(root, ret_path, NULL);
r = open_os_release(root, ret_path ? &p : NULL, &fd);
if (r < 0)
return r;
f = fdopen(fd, "re");
if (!f)
return -errno;
fd = -1;
*ret_file = f;
if (ret_path)
*ret_path = TAKE_PTR(p);
return 0;
}
int parse_os_release(const char *root, ...) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
va_list ap;
int r;
r = fopen_os_release(root, &p, &f);
if (r < 0)
return r;
va_start(ap, root);
r = parse_env_filev(f, p, NEWLINE, ap);
va_end(ap);
return r;
}
int load_os_release_pairs(const char *root, char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
r = fopen_os_release(root, &p, &f);
if (r < 0)
return r;
return load_env_file_pairs(f, p, NEWLINE, ret);
}

12
src/basic/os-util.h Normal file
View file

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <stdio.h>
int path_is_os_tree(const char *path);
int open_os_release(const char *root, char **ret_path, int *ret_fd);
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
int parse_os_release(const char *root, ...);
int load_os_release_pairs(const char *root, char ***ret);

View file

@ -17,6 +17,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/types.h>
@ -1344,6 +1345,16 @@ int safe_fork_full(
}
}
if ((flags & (FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE)) == (FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE)) {
/* Optionally, make sure we never propagate mounts to the host. */
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
_exit(EXIT_FAILURE);
}
}
if (flags & FORK_CLOSE_ALL_FDS) {
/* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
log_close();

View file

@ -163,6 +163,7 @@ typedef enum ForkFlags {
FORK_LOG = 1U << 5,
FORK_WAIT = 1U << 6,
FORK_NEW_MOUNTNS = 1U << 7,
FORK_MOUNTNS_SLAVE = 1U << 8,
} ForkFlags;
int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);

View file

@ -132,32 +132,6 @@ int path_is_read_only_fs(const char *path) {
return false;
}
int path_is_os_tree(const char *path) {
int r;
assert(path);
/* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
* always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
* the case where just the os-release file is missing. */
if (laccess(path, F_OK) < 0)
return -errno;
/* We use /usr/lib/os-release as flag file if something is an OS */
r = chase_symlinks("/usr/lib/os-release", path, CHASE_PREFIX_ROOT, NULL);
if (r == -ENOENT) {
/* Also check for the old location in /etc, just in case. */
r = chase_symlinks("/etc/os-release", path, CHASE_PREFIX_ROOT, NULL);
if (r == -ENOENT)
return 0; /* We got nothing */
}
if (r < 0)
return r;
return 1;
}
int files_same(const char *filea, const char *fileb, int flags) {
struct stat a, b;

View file

@ -35,7 +35,6 @@ int null_or_empty_path(const char *fn);
int null_or_empty_fd(int fd);
int path_is_read_only_fs(const char *path);
int path_is_os_tree(const char *path);
int files_same(const char *filea, const char *fileb, int flags);

View file

@ -264,7 +264,7 @@ int container_get_leader(const char *machine, pid_t *pid) {
return -EINVAL;
p = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
r = parse_env_file(NULL, p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
if (r == -ENOENT)
return -EHOSTDOWN;
if (r < 0)

View file

@ -26,6 +26,7 @@
#include "fs-util.h"
#include "install.h"
#include "log.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "selinux-access.h"

View file

@ -24,7 +24,7 @@ int locale_setup(char ***environment) {
int r = 0, i;
if (detect_container() <= 0) {
r = parse_env_file("/proc/cmdline", WHITESPACE,
r = parse_env_file(NULL, "/proc/cmdline", WHITESPACE,
"locale.LANG", &variables[VARIABLE_LANG],
"locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
"locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
@ -48,7 +48,7 @@ int locale_setup(char ***environment) {
/* Hmm, nothing set on the kernel cmd line? Then let's
* try /etc/locale.conf */
if (r <= 0) {
r = parse_env_file("/etc/locale.conf", NEWLINE,
r = parse_env_file(NULL, "/etc/locale.conf", NEWLINE,
"LANG", &variables[VARIABLE_LANG],
"LANGUAGE", &variables[VARIABLE_LANGUAGE],
"LC_CTYPE", &variables[VARIABLE_LC_CTYPE],

View file

@ -57,6 +57,7 @@
#include "manager.h"
#include "missing.h"
#include "mount-setup.h"
#include "os-util.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
@ -1215,23 +1216,18 @@ static int enforce_syscall_archs(Set *archs) {
static int status_welcome(void) {
_cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL;
const char *fn;
int r;
if (arg_show_status <= 0)
return 0;
FOREACH_STRING(fn, "/etc/os-release", "/usr/lib/os-release") {
r = parse_env_file(fn, NEWLINE,
"PRETTY_NAME", &pretty_name,
"ANSI_COLOR", &ansi_color,
NULL);
if (r != -ENOENT)
break;
}
if (r < 0 && r != -ENOENT)
log_warning_errno(r, "Failed to read os-release file, ignoring: %m");
r = parse_os_release(NULL,
"PRETTY_NAME", &pretty_name,
"ANSI_COLOR", &ansi_color,
NULL);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read os-release file, ignoring: %m");
if (log_get_show_color())
return status_printf(NULL, false, false,

View file

@ -33,6 +33,7 @@
#include "hostname-util.h"
#include "locale-util.h"
#include "mkdir.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
@ -79,27 +80,16 @@ static bool press_any_key(void) {
static void print_welcome(void) {
_cleanup_free_ char *pretty_name = NULL;
const char *os_release = NULL;
static bool done = false;
int r;
if (done)
return;
os_release = prefix_roota(arg_root, "/etc/os-release");
r = parse_env_file(os_release, NEWLINE,
"PRETTY_NAME", &pretty_name,
NULL);
if (r == -ENOENT) {
os_release = prefix_roota(arg_root, "/usr/lib/os-release");
r = parse_env_file(os_release, NEWLINE,
"PRETTY_NAME", &pretty_name,
NULL);
}
if (r < 0 && r != -ENOENT)
log_warning_errno(r, "Failed to read os-release file: %m");
r = parse_os_release(arg_root, "PRETTY_NAME", &pretty_name, NULL);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read os-release file, ignoring: %m");
printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
isempty(pretty_name) ? "Linux" : pretty_name);

View file

@ -16,6 +16,7 @@
#include "env-util.h"
#include "fileio-label.h"
#include "hostname-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "selinux-util.h"
@ -88,7 +89,7 @@ static int context_read_data(Context *c) {
if (r < 0 && r != -ENOENT)
return r;
r = parse_env_file("/etc/machine-info", NEWLINE,
r = parse_env_file(NULL, "/etc/machine-info", NEWLINE,
"PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
"ICON_NAME", &c->data[PROP_ICON_NAME],
"CHASSIS", &c->data[PROP_CHASSIS],
@ -98,18 +99,11 @@ static int context_read_data(Context *c) {
if (r < 0 && r != -ENOENT)
return r;
r = parse_env_file("/etc/os-release", NEWLINE,
"PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
"CPE_NAME", &c->data[PROP_OS_CPE_NAME],
"HOME_URL", &c->data[PROP_HOME_URL],
NULL);
if (r == -ENOENT)
r = parse_env_file("/usr/lib/os-release", NEWLINE,
"PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
"CPE_NAME", &c->data[PROP_OS_CPE_NAME],
"HOME_URL", &c->data[PROP_HOME_URL],
NULL);
r = parse_os_release(NULL,
"PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
"CPE_NAME", &c->data[PROP_OS_CPE_NAME],
"HOME_URL", &c->data[PROP_HOME_URL],
NULL);
if (r < 0 && r != -ENOENT)
return r;

View file

@ -69,13 +69,11 @@ static int export_tar(int argc, char *argv[], void *userdata) {
int r, fd;
if (machine_name_is_valid(argv[1])) {
r = image_find(argv[1], &image);
r = image_find(IMAGE_MACHINE, argv[1], &image);
if (r == -ENOENT)
return log_error_errno(r, "Machine image %s not found.", argv[1]);
if (r < 0)
return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]);
if (r == 0) {
log_error("Machine image %s not found.", argv[1]);
return -ENOENT;
}
local = image->path;
} else
@ -148,13 +146,11 @@ static int export_raw(int argc, char *argv[], void *userdata) {
int r, fd;
if (machine_name_is_valid(argv[1])) {
r = image_find(argv[1], &image);
r = image_find(IMAGE_MACHINE, argv[1], &image);
if (r == -ENOENT)
return log_error_errno(r, "Machine image %s not found.", argv[1]);
if (r < 0)
return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]);
if (r == 0) {
log_error("Machine image %s not found.", argv[1]);
return -ENOENT;
}
local = image->path;
} else

View file

@ -75,10 +75,11 @@ static int import_tar(int argc, char *argv[], void *userdata) {
}
if (!arg_force) {
r = image_find(local, NULL);
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
r = image_find(IMAGE_MACHINE, local, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
} else {
log_error("Image '%s' already exists.", local);
return -EEXIST;
}
@ -170,10 +171,11 @@ static int import_raw(int argc, char *argv[], void *userdata) {
}
if (!arg_force) {
r = image_find(local, NULL);
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
r = image_find(IMAGE_MACHINE, local, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
} else {
log_error("Image '%s' already exists.", local);
return -EEXIST;
}

View file

@ -83,10 +83,11 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
}
if (!arg_force) {
r = image_find(local, NULL);
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
r = image_find(IMAGE_MACHINE, local, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
} else {
log_error("Image '%s' already exists.", local);
return -EEXIST;
}
@ -169,10 +170,11 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
}
if (!arg_force) {
r = image_find(local, NULL);
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
r = image_find(IMAGE_MACHINE, local, NULL);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
} else {
log_error("Image '%s' already exists.", local);
return -EEXIST;
}

View file

@ -24,6 +24,7 @@
#include "log.h"
#include "logs-show.h"
#include "microhttpd-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "sigbus.h"
#include "util.h"
@ -777,10 +778,8 @@ static int request_handler_machine(
if (r < 0)
return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m");
if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
(void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
get_virtualization(&v);
(void) parse_os_release(NULL, "PRETTY_NAME", &os_name, NULL);
(void) get_virtualization(&v);
r = asprintf(&json,
"{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","

View file

@ -152,7 +152,7 @@ static int load_cursor_state(Uploader *u) {
if (!u->state_file)
return 0;
r = parse_env_file(u->state_file, NEWLINE,
r = parse_env_file(NULL, u->state_file, NEWLINE,
"LAST_CURSOR", &u->last_cursor,
NULL);

View file

@ -646,7 +646,7 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
return log_oom();
}
r = parse_env_file(stream->state_file, NEWLINE,
r = parse_env_file(NULL, stream->state_file, NEWLINE,
"PRIORITY", &priority,
"LEVEL_PREFIX", &level_prefix,
"FORWARD_TO_SYSLOG", &forward_to_syslog,

View file

@ -1894,7 +1894,7 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in
assert_return(machine_name_is_valid(machine), -EINVAL);
p = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(p, NEWLINE, "ROOT", &root, "CLASS", &class, NULL);
r = parse_env_file(NULL, p, NEWLINE, "ROOT", &root, "CLASS", &class, NULL);
if (r == -ENOENT)
return -EHOSTDOWN;
if (r < 0)

View file

@ -1034,7 +1034,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
if (r < 0)
return r;
r = parse_env_file(lease_file, NEWLINE,
r = parse_env_file(NULL, lease_file, NEWLINE,
"ADDRESS", &address,
"ROUTER", &router,
"NETMASK", &netmask,

View file

@ -41,6 +41,8 @@
#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping"
#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping"
#define BUS_ERROR_NO_SUCH_PORTABLE_IMAGE "org.freedesktop.portable1.NoSuchImage"
#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession"
#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID"
#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser"

View file

@ -273,7 +273,7 @@ _public_ int sd_uid_get_state(uid_t uid, char**state) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, "STATE", &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, "STATE", &s, NULL);
if (r == -ENOENT) {
free(s);
s = strdup("offline");
@ -304,7 +304,7 @@ _public_ int sd_uid_get_display(uid_t uid, char **session) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, "DISPLAY", &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, "DISPLAY", &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@ -359,7 +359,7 @@ _public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat)
variable = require_active ? "ACTIVE_UID" : "UIDS";
r = parse_env_file(p, NEWLINE, variable, &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, variable, &s, NULL);
if (r == -ENOENT)
return 0;
if (r < 0)
@ -388,7 +388,7 @@ static int uid_get_array(uid_t uid, const char *variable, char ***array) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, variable, &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, variable, &s, NULL);
if (r == -ENOENT || (r >= 0 && isempty(s))) {
if (array)
*array = NULL;
@ -466,7 +466,7 @@ _public_ int sd_session_is_active(const char *session) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, "ACTIVE", &s, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
@ -485,7 +485,7 @@ _public_ int sd_session_is_remote(const char *session) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, "REMOTE", &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, "REMOTE", &s, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
@ -506,7 +506,7 @@ _public_ int sd_session_get_state(const char *session, char **state) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, "STATE", &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, "STATE", &s, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
@ -529,7 +529,7 @@ _public_ int sd_session_get_uid(const char *session, uid_t *uid) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, "UID", &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, "UID", &s, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
@ -551,7 +551,7 @@ static int session_get_string(const char *session, const char *field, char **val
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE, field, &s, NULL);
r = parse_env_file(NULL, p, NEWLINE, field, &s, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
@ -643,7 +643,7 @@ _public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE,
r = parse_env_file(NULL, p, NEWLINE,
"ACTIVE", &s,
"ACTIVE_UID", &t,
NULL);
@ -681,7 +681,7 @@ _public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **ui
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE,
r = parse_env_file(NULL, p, NEWLINE,
"SESSIONS", &s,
"UIDS", &t,
NULL);
@ -750,7 +750,7 @@ static int seat_get_can(const char *seat, const char *variable) {
if (r < 0)
return r;
r = parse_env_file(p, NEWLINE,
r = parse_env_file(NULL, p, NEWLINE,
variable, &s,
NULL);
if (r == -ENOENT)
@ -899,7 +899,7 @@ _public_ int sd_machine_get_class(const char *machine, char **class) {
assert_return(class, -EINVAL);
p = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(p, NEWLINE, "CLASS", &c, NULL);
r = parse_env_file(NULL, p, NEWLINE, "CLASS", &c, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
@ -923,7 +923,7 @@ _public_ int sd_machine_get_ifindices(const char *machine, int **ifindices) {
assert_return(ifindices, -EINVAL);
p = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(p, NEWLINE, "NETIF", &netif, NULL);
r = parse_env_file(NULL, p, NEWLINE, "NETIF", &netif, NULL);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)

View file

@ -30,7 +30,7 @@ _public_ int sd_network_get_operational_state(char **state) {
assert_return(state, -EINVAL);
r = parse_env_file("/run/systemd/netif/state", NEWLINE, "OPER_STATE", &s, NULL);
r = parse_env_file(NULL, "/run/systemd/netif/state", NEWLINE, "OPER_STATE", &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@ -50,7 +50,7 @@ static int network_get_strv(const char *key, char ***ret) {
assert_return(ret, -EINVAL);
r = parse_env_file("/run/systemd/netif/state", NEWLINE, key, &s, NULL);
r = parse_env_file(NULL, "/run/systemd/netif/state", NEWLINE, key, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@ -98,7 +98,7 @@ static int network_link_get_string(int ifindex, const char *field, char **ret) {
xsprintf(path, "/run/systemd/netif/links/%i", ifindex);
r = parse_env_file(path, NEWLINE, field, &s, NULL);
r = parse_env_file(NULL, path, NEWLINE, field, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@ -121,7 +121,7 @@ static int network_link_get_strv(int ifindex, const char *key, char ***ret) {
assert_return(ret, -EINVAL);
xsprintf(path, "/run/systemd/netif/links/%i", ifindex);
r = parse_env_file(path, NEWLINE, key, &s, NULL);
r = parse_env_file(NULL, path, NEWLINE, key, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@ -218,7 +218,7 @@ static int network_link_get_ifindexes(int ifindex, const char *key, int **ret) {
assert_return(ret, -EINVAL);
xsprintf(path, "/run/systemd/netif/links/%i", ifindex);
r = parse_env_file(path, NEWLINE, key, &s, NULL);
r = parse_env_file(NULL, path, NEWLINE, key, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)

View file

@ -93,7 +93,7 @@ static int locale_read_data(Context *c) {
context_free_locale(c);
r = parse_env_file("/etc/locale.conf", NEWLINE,
r = parse_env_file(NULL, "/etc/locale.conf", NEWLINE,
"LANG", &c->locale[VARIABLE_LANG],
"LANGUAGE", &c->locale[VARIABLE_LANGUAGE],
"LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE],
@ -137,7 +137,7 @@ static int vconsole_read_data(Context *c) {
context_free_vconsole(c);
r = parse_env_file("/etc/vconsole.conf", NEWLINE,
r = parse_env_file(NULL, "/etc/vconsole.conf", NEWLINE,
"KEYMAP", &c->vc_keymap,
"KEYMAP_TOGGLE", &c->vc_keymap_toggle,
NULL);

View file

@ -61,7 +61,7 @@ static void print_overridden_variables(void) {
if (detect_container() > 0 || arg_host)
return;
r = parse_env_file("/proc/cmdline", WHITESPACE,
r = parse_env_file(NULL, "/proc/cmdline", WHITESPACE,
"locale.LANG", &variables[VARIABLE_LANG],
"locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
"locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],

View file

@ -198,7 +198,7 @@ int inhibitor_load(Inhibitor *i) {
char *cc;
int r;
r = parse_env_file(i->state_file, NEWLINE,
r = parse_env_file(NULL, i->state_file, NEWLINE,
"WHAT", &what,
"UID", &uid,
"PID", &pid,

View file

@ -365,7 +365,7 @@ int session_load(Session *s) {
assert(s);
r = parse_env_file(s->state_file, NEWLINE,
r = parse_env_file(NULL, s->state_file, NEWLINE,
"REMOTE", &remote,
"SCOPE", &s->scope,
"SCOPE_JOB", &s->scope_job,

View file

@ -287,7 +287,7 @@ int user_load(User *u) {
assert(u);
r = parse_env_file(u->state_file, NEWLINE,
r = parse_env_file(NULL, u->state_file, NEWLINE,
"SERVICE_JOB", &u->service_job,
"SLICE_JOB", &u->slice_job,
"DISPLAY", &display,

View file

@ -435,8 +435,10 @@ int image_object_find(sd_bus *bus, const char *path, const char *interface, void
if (r < 0)
return r;
r = image_find(e, &image);
if (r <= 0)
r = image_find(IMAGE_MACHINE, e, &image);
if (r == -ENOENT)
return 0;
if (r < 0)
return r;
image->userdata = m;
@ -478,7 +480,7 @@ int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char **
if (!images)
return -ENOMEM;
r = image_discover(images);
r = image_discover(IMAGE_MACHINE, images);
if (r < 0)
return r;

View file

@ -32,6 +32,7 @@
#include "machine-dbus.h"
#include "machine.h"
#include "mkdir.h"
#include "os-util.h"
#include "path-util.h"
#include "process-util.h"
#include "signal-util.h"
@ -330,7 +331,7 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
switch (m->class) {
case MACHINE_HOST:
r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l);
r = load_os_release_pairs(NULL, &l);
if (r < 0)
return r;
@ -361,13 +362,10 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
if (r < 0)
_exit(EXIT_FAILURE);
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && errno == ENOENT) {
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && errno == ENOENT)
_exit(EXIT_NOT_FOUND);
}
if (fd < 0)
r = open_os_release(NULL, NULL, &fd);
if (r == -ENOENT)
_exit(EXIT_NOT_FOUND);
if (r < 0)
_exit(EXIT_FAILURE);
r = copy_bytes(fd, pair[1], (uint64_t) -1, 0);

View file

@ -31,5 +31,3 @@ int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd
int machine_send_signal(Machine *m, bool new_machine);
int machine_send_create_reply(Machine *m, sd_bus_error *error);
int bus_reply_pair_array(sd_bus_message *m, char **l);

View file

@ -255,7 +255,7 @@ int machine_load(Machine *m) {
if (!m->state_file)
return 0;
r = parse_env_file(m->state_file, NEWLINE,
r = parse_env_file(NULL, m->state_file, NEWLINE,
"SCOPE", &m->unit,
"SCOPE_JOB", &m->scope_job,
"SERVICE", &m->service,

View file

@ -145,8 +145,8 @@ static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_erro
if (r < 0)
return r;
r = image_find(name, NULL);
if (r == 0)
r = image_find(IMAGE_MACHINE, name, NULL);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0)
return r;
@ -450,14 +450,14 @@ static int method_register_machine(sd_bus_message *message, void *userdata, sd_b
return method_register_machine_internal(message, false, userdata, error);
}
static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
static int redirect_method_to_machine(sd_bus_message *message, Manager *m, sd_bus_error *error, sd_bus_message_handler_t method) {
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
assert(method);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
@ -467,67 +467,23 @@ static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_terminate(message, machine, error);
return method(message, machine, error);
}
static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_machine(message, userdata, error, bus_machine_method_terminate);
}
static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return sd_bus_error_set_errno(error, r);
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_kill(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_kill);
}
static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return sd_bus_error_set_errno(error, r);
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_get_addresses(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_addresses);
}
static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return sd_bus_error_set_errno(error, r);
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_get_os_release(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_os_release);
}
static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@ -545,7 +501,7 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
if (!images)
return -ENOMEM;
r = image_discover(images);
r = image_discover(IMAGE_MACHINE, images);
if (r < 0)
return r;
@ -584,336 +540,89 @@ static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_er
}
static int method_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return sd_bus_error_set_errno(error, r);
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_open_pty(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_open_pty);
}
static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_open_login(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_open_login);
}
static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_open_shell(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_open_shell);
}
static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_bind_mount(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_bind_mount);
}
static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_copy(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_copy);
}
static int method_open_machine_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
const char *name;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
return bus_machine_method_open_root_directory(message, machine, error);
return redirect_method_to_machine(message, userdata, error, bus_machine_method_open_root_directory);
}
static int method_get_machine_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
Machine *machine;
return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_uid_shift);
}
static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_error *error, sd_bus_message_handler_t method) {
_cleanup_(image_unrefp) Image* i = NULL;
const char *name;
int r;
assert(message);
assert(m);
assert(method);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
machine = hashmap_get(m->machines, name);
if (!machine)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
return bus_machine_method_get_uid_shift(message, machine, error);
r = image_find(IMAGE_MACHINE, name, &i);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
if (r < 0)
return r;
i->userdata = m;
return method(message, i, error);
}
static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image* i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_remove(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_remove);
}
static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image* i = NULL;
const char *old_name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &old_name);
if (r < 0)
return r;
if (!image_name_is_valid(old_name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name);
r = image_find(old_name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name);
i->userdata = userdata;
return bus_image_method_rename(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_rename);
}
static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *old_name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &old_name);
if (r < 0)
return r;
if (!image_name_is_valid(old_name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name);
r = image_find(old_name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name);
i->userdata = userdata;
return bus_image_method_clone(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_clone);
}
static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_mark_read_only(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_mark_read_only);
}
static int method_get_image_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_get_hostname(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_get_hostname);
}
static int method_get_image_machine_id(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_get_machine_id(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_get_machine_id);
}
static int method_get_image_machine_info(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_get_machine_info(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_get_machine_info);
}
static int method_get_image_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_get_os_release(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_get_os_release);
}
static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) {
@ -1070,7 +779,7 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
goto child_fail;
}
r = image_discover(images);
r = image_discover(IMAGE_MACHINE, images);
if (r < 0)
goto child_fail;
@ -1198,27 +907,7 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus
}
static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_set_limit(message, i, error);
return redirect_method_to_image(message, userdata, error, bus_image_method_set_limit);
}
static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@ -1899,30 +1588,3 @@ int manager_add_machine(Manager *m, const char *name, Machine **_machine) {
return 0;
}
int bus_reply_pair_array(sd_bus_message *m, char **l) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
char **k, **v;
int r;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{ss}");
if (r < 0)
return r;
STRV_FOREACH_PAIR(k, v, l) {
r = sd_bus_message_append(reply, "{ss}", *k, *v);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}

View file

@ -2866,7 +2866,7 @@ static int link_load(Link *link) {
assert(link);
r = parse_env_file(link->state_file, NEWLINE,
r = parse_env_file(NULL, link->state_file, NEWLINE,
"NETWORK_FILE", &network_file,
"ADDRESSES", &addresses,
"ROUTES", &routes,

View file

@ -249,7 +249,7 @@ static int link_send_lldp(Link *link) {
return r;
(void) gethostname_strict(&hostname);
(void) parse_env_file("/etc/machine-info", NEWLINE, "PRETTY_HOSTNAME", &pretty_hostname, NULL);
(void) parse_env_file(NULL, "/etc/machine-info", NEWLINE, "PRETTY_HOSTNAME", &pretty_hostname, NULL);
assert_cc(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1 <= (UINT16_MAX - 1) * USEC_PER_SEC);
ttl = DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC);

View file

@ -77,6 +77,7 @@
#include "nspawn-settings.h"
#include "nspawn-setuid.h"
#include "nspawn-stub-pid1.h"
#include "os-util.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
@ -2398,13 +2399,11 @@ static int determine_names(void) {
if (arg_machine) {
_cleanup_(image_unrefp) Image *i = NULL;
r = image_find(arg_machine, &i);
r = image_find(IMAGE_MACHINE, arg_machine, &i);
if (r == -ENOENT)
return log_error_errno(r, "No image for machine '%s'.", arg_machine);
if (r < 0)
return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine);
if (r == 0) {
log_error("No image for machine '%s'.", arg_machine);
return -ENOENT;
}
if (IN_SET(i->type, IMAGE_RAW, IMAGE_BLOCK))
r = free_and_strdup(&arg_image, i->path);
@ -2883,7 +2882,9 @@ static int outer_child(
* makes sure ESP partitions and userns are compatible. */
r = dissected_image_mount(dissected_image, directory, arg_uid_shift,
DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0));
DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|
(arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0)|
(arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0));
if (r < 0)
return r;
}

29
src/portable/meson.build Normal file
View file

@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-2.1+
systemd_portabled_sources = files('''
portable.c
portable.h
portabled-bus.c
portabled-image-bus.c
portabled-image-bus.h
portabled-image.c
portabled-image.h
portabled-operation.c
portabled-operation.h
portabled.c
portabled.h
'''.split())
if conf.get('ENABLE_PORTABLED') == 1
install_data('org.freedesktop.portable1.conf',
install_dir : dbuspolicydir)
install_data('org.freedesktop.portable1.service',
install_dir : dbussystemservicedir)
install_data('org.freedesktop.portable1.policy',
install_dir : polkitpolicydir)
install_data('profile/default/service.conf', install_dir : join_paths(profiledir, 'default'))
install_data('profile/nonetwork/service.conf', install_dir : join_paths(profiledir, 'nonetwork'))
install_data('profile/strict/service.conf', install_dir : join_paths(profiledir, 'strict'))
install_data('profile/trusted/service.conf', install_dir : join_paths(profiledir, 'trusted'))
endif

View file

@ -0,0 +1,117 @@
<?xml version="1.0"?> <!--*-nxml-*-->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<busconfig>
<policy user="root">
<allow own="org.freedesktop.portable1"/>
<allow send_destination="org.freedesktop.portable1"/>
<allow receive_sender="org.freedesktop.portable1"/>
</policy>
<policy context="default">
<deny send_destination="org.freedesktop.portable1"/>
<!-- generic interfaces -->
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Peer"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Properties"
send_member="Get"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Properties"
send_member="GetAll"/>
<!-- Manager object -->
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="ListImages"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImageOSRelease"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImageUnitFiles"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImageState"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="AttachImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="DetachImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="RemoveImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="MarkImageReadOnly"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="SetImageLimit"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="SetPoolLimit"/>
<!-- Image object -->
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="GetOSRelease"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="GetUnitFiles"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="GetImageState"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="Attach"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="Detach"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="Remove"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="MarkReadOnly"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="SetLimit"/>
<allow receive_sender="org.freedesktop.portable1"/>
</policy>
</busconfig>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
<action id="org.freedesktop.portable1.inspect-images">
<description gettext-domain="systemd">Inspect a portable service</description>
<message gettext-domain="systemd">Authentication is required to inspect a portable service.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.freedesktop.portable1.attach-images">
<description gettext-domain="systemd">Attach or detach a portable service</description>
<message gettext-domain="systemd">Authentication is required to attach or detach a portable service.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.imply">org.freedesktop.systemd1.reload-daemon</annotate>
</action>
<action id="org.freedesktop.portable1.manage-images">
<description gettext-domain="systemd">Delete or modify portable service image</description>
<message gettext-domain="systemd">Authentication is required to delete or modify a portable service image.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1+
[D-BUS Service]
Name=org.freedesktop.portable1
Exec=/bin/false
User=root
SystemdService=dbus-org.freedesktop.portable1.service

1427
src/portable/portable.c Normal file

File diff suppressed because it is too large Load diff

77
src/portable/portable.h Normal file
View file

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "hashmap.h"
#include "macro.h"
#include "set.h"
#include "string-util.h"
typedef struct PortableMetadata {
int fd;
char *source;
char name[];
} PortableMetadata;
#define PORTABLE_METADATA_IS_OS_RELEASE(m) (streq((m)->name, "/etc/os-release"))
#define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/'))
typedef enum PortableFlags {
PORTABLE_PREFER_COPY = 1U << 0,
PORTABLE_PREFER_SYMLINK = 1U << 1,
PORTABLE_RUNTIME = 1U << 2,
} PortableFlags;
typedef enum PortableChangeType {
PORTABLE_COPY,
PORTABLE_SYMLINK,
PORTABLE_UNLINK,
PORTABLE_WRITE,
PORTABLE_MKDIR,
_PORTABLE_CHANGE_TYPE_MAX,
_PORTABLE_CHANGE_TYPE_INVALID = INT_MIN,
} PortableChangeType;
typedef enum PortableState {
PORTABLE_DETACHED,
PORTABLE_ATTACHED,
PORTABLE_ATTACHED_RUNTIME,
PORTABLE_ENABLED,
PORTABLE_ENABLED_RUNTIME,
PORTABLE_RUNNING,
PORTABLE_RUNNING_RUNTIME,
_PORTABLE_STATE_MAX,
_PORTABLE_STATE_INVALID = -1
} PortableState;
typedef struct PortableChange {
int type; /* PortableFileChangeType or negative error number */
char *path;
char *source;
} PortableChange;
PortableMetadata *portable_metadata_unref(PortableMetadata *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
Hashmap *portable_metadata_hashmap_unref(Hashmap *h);
DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, portable_metadata_hashmap_unref);
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
int portable_extract(const char *image, char **matches, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_get_state(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableState *ret, sd_bus_error *error);
int portable_get_profiles(char ***ret);
void portable_changes_free(PortableChange *changes, size_t n_changes);
const char *portable_change_type_to_string(PortableChangeType t) _const_;
PortableChangeType portable_change_type_from_string(const char *t) _pure_;
const char *portable_state_to_string(PortableState t) _const_;
PortableState portable_state_from_string(const char *t) _pure_;

965
src/portable/portablectl.c Normal file
View file

@ -0,0 +1,965 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <getopt.h>
#include "sd-bus.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
#include "def.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "fs-util.h"
#include "locale-util.h"
#include "machine-image.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
#include "spawn-polkit-agent.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "verbs.h"
static bool arg_no_pager = false;
static bool arg_legend = true;
static bool arg_ask_password = true;
static bool arg_quiet = false;
static const char *arg_profile = "default";
static const char* arg_copy_mode = NULL;
static bool arg_runtime = false;
static bool arg_reload = true;
static bool arg_cat = false;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static char *arg_host = NULL;
static int determine_image(const char *image, bool permit_non_existing, char **ret) {
int r;
/* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
* usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
* (among other things, to make the path independent of the client's working directory) before passing it
* over. */
if (image_name_is_valid(image)) {
char *c;
if (!arg_quiet && laccess(image, F_OK) >= 0)
log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
"Prefix argument with './' to force reference to file in current working directory.", image);
c = strdup(image);
if (!c)
return log_oom();
*ret = c;
return 0;
}
if (arg_transport != BUS_TRANSPORT_LOCAL) {
log_error("Operations on images by path not supported when connecting to remote systems.");
return -EOPNOTSUPP;
}
r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret);
if (r < 0)
return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
return 0;
}
static int extract_prefix(const char *path, char **ret) {
_cleanup_free_ char *name = NULL;
const char *bn, *underscore;
size_t m;
bn = basename(path);
underscore = strchr(bn, '_');
if (underscore)
m = underscore - bn;
else {
const char *e;
e = endswith(bn, ".raw");
if (!e)
e = strchr(bn, 0);
m = e - bn;
}
name = strndup(bn, m);
if (!name)
return -ENOMEM;
/* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
* which we use as delimiter for the second part of the image string, which we ignore for now. */
if (!in_charset(name, DIGITS LETTERS "-."))
return -EINVAL;
if (!filename_is_valid(name))
return -EINVAL;
*ret = name;
name = NULL;
return 0;
}
static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
char **k;
int r;
/* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
* contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
* permitted. */
if (strv_isempty(l)) {
char *prefix;
r = extract_prefix(image, &prefix);
if (r < 0)
return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
if (!arg_quiet)
log_info("(Matching unit files with prefix '%s'.)", prefix);
k = NULL;
r = strv_consume(&k, prefix);
if (r < 0)
return log_oom();
} else if (strv_equal(l, STRV_MAKE("-"))) {
if (!allow_any) {
log_error("Refusing all unit file match.");
return -EINVAL;
}
if (!arg_quiet)
log_info("(Matching all unit files.)");
k = NULL;
} else {
_cleanup_free_ char *joined = NULL;
k = strv_copy(l);
if (!k)
return log_oom();
joined = strv_join(k, "', '");
if (!joined)
return log_oom();
if (!arg_quiet)
log_info("(Matching unit files with prefixes '%s'.)", joined);
}
*ret = k;
return 0;
}
static int acquire_bus(sd_bus **bus) {
int r;
assert(bus);
if (*bus)
return 0;
r = bus_connect_transport(arg_transport, arg_host, false, bus);
if (r < 0)
return log_error_errno(r, "Failed to connect to bus: %m");
(void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
return 0;
}
static int maybe_reload(sd_bus **bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
int r;
if (!arg_reload)
return 0;
r = acquire_bus(bus);
if (r < 0)
return r;
r = sd_bus_message_new_method_call(
*bus,
&m,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"Reload");
if (r < 0)
return bus_log_create_error(r);
/* Reloading the daemon may take long, hence set a longer timeout here */
r = sd_bus_call(*bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
return 0;
}
static int inspect_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **matches = NULL;
_cleanup_free_ char *image = NULL;
bool nl = false, header = false;
const void *data;
const char *path;
size_t sz;
int r;
r = determine_image(argv[1], false, &image);
if (r < 0)
return r;
r = determine_matches(argv[1], argv + 2, true, &matches);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"GetImageMetadata");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", image);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, matches);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &path);
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_read_array(reply, 'y', &data, &sz);
if (r < 0)
return bus_log_parse_error(r);
(void) pager_open(arg_no_pager, false);
if (arg_cat) {
printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
fwrite(data, sz, 1, stdout);
fflush(stdout);
nl = true;
} else {
const char *pretty_portable = NULL, *pretty_os = NULL;
_cleanup_fclose_ FILE *f;
f = fmemopen((void*) data, sz, "re");
if (!f)
return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
r = parse_env_file(f, "/etc/os-release", NEWLINE,
"PORTABLE_PRETTY_NAME", &pretty_portable,
"PRETTY_NAME", &pretty_os,
NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse /etc/os-release: %m");
printf("Image:\n\t%s\n"
"Portable Service:\n\t%s\n"
"Operating System:\n\t%s\n",
path,
strna(pretty_portable),
strna(pretty_os));
}
r = sd_bus_message_enter_container(reply, 'a', "{say}");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *name;
r = sd_bus_message_enter_container(reply, 'e', "say");
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = sd_bus_message_read(reply, "s", &name);
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_read_array(reply, 'y', &data, &sz);
if (r < 0)
return bus_log_parse_error(r);
if (arg_cat) {
if (nl)
fputc('\n', stdout);
printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name, ansi_normal());
fwrite(data, sz, 1, stdout);
fflush(stdout);
nl = true;
} else {
if (!header) {
fputs("Unit files:\n", stdout);
header = true;
}
fputc('\t', stdout);
fputs(name, stdout);
fputc('\n', stdout);
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
return 0;
}
static int print_changes(sd_bus_message *m) {
int r;
if (arg_quiet)
return 0;
r = sd_bus_message_enter_container(m, 'a', "(sss)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *type, *path, *source;
r = sd_bus_message_read(m, "(sss)", &type, &path, &source);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
if (streq(type, "symlink"))
log_info("Created symlink %s %s %s.", path, special_glyph(ARROW), source);
else if (streq(type, "copy")) {
if (isempty(source))
log_info("Copied %s.", path);
else
log_info("Copied %s %s %s.", source, special_glyph(ARROW), path);
} else if (streq(type, "unlink"))
log_info("Removed %s.", path);
else if (streq(type, "write"))
log_info("Written %s.", path);
else if (streq(type, "mkdir"))
log_info("Created directory %s.", path);
else
log_error("Unexpected change: %s/%s/%s", type, path, source);
}
r = sd_bus_message_exit_container(m);
if (r < 0)
return r;
return 0;
}
static int attach_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **matches = NULL;
_cleanup_free_ char *image = NULL;
int r;
r = determine_image(argv[1], false, &image);
if (r < 0)
return r;
r = determine_matches(argv[1], argv + 2, false, &matches);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"AttachImage");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", image);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, matches);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to attach image: %s", bus_error_message(&error, r));
(void) maybe_reload(&bus);
print_changes(reply);
return 0;
}
static int detach_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *image = NULL;
int r;
r = determine_image(argv[1], true, &image);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"DetachImage",
&error,
&reply,
"sb", image, arg_runtime);
if (r < 0)
return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r));
(void) maybe_reload(&bus);
print_changes(reply);
return 0;
}
static int list_images(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"ListImages",
&error,
&reply,
NULL);
if (r < 0)
return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
table = table_new("NAME", "TYPE", "RO", "CRTIME", "MTIME", "USAGE", "STATE");
if (!table)
return log_oom();
r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *name, *type, *state, *object;
uint64_t crtime, mtime, usage;
TableCell *cell;
bool ro_bool;
int ro_int;
r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, &object);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = table_add_many(table,
TABLE_STRING, name,
TABLE_STRING, type);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
ro_bool = ro_int;
r = table_add_cell(table, &cell, TABLE_BOOLEAN, &ro_bool);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
if (ro_bool) {
r = table_set_color(table, cell, ansi_highlight_red());
if (r < 0)
return log_error_errno(r, "Failed to set table cell color: %m");
}
r = table_add_many(table,
TABLE_TIMESTAMP, crtime,
TABLE_TIMESTAMP, mtime,
TABLE_SIZE, usage);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
r = table_add_cell(table, &cell, TABLE_STRING, state);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
if (!streq(state, "detached")) {
r = table_set_color(table, cell, ansi_highlight_green());
if (r < 0)
return log_error_errno(r, "Failed to set table cell color: %m");
}
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
if (table_get_rows(table) > 1) {
r = table_set_sort(table, (size_t) 0, (size_t) -1);
if (r < 0)
return log_error_errno(r, "Failed to sort table: %m");
table_set_header(table, arg_legend);
r = table_print(table, NULL);
if (r < 0)
return log_error_errno(r, "Failed to show table: %m");
}
if (arg_legend) {
if (table_get_rows(table) > 1)
printf("\n%zu images listed.\n", table_get_rows(table) - 1);
else
printf("No images.\n");
}
return 0;
}
static int remove_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, i;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
for (i = 1; i < argc; i++) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"RemoveImage");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", argv[i]);
if (r < 0)
return bus_log_create_error(r);
/* This is a slow operation, hence turn off any method call timeouts */
r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
if (r < 0)
return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
}
return 0;
}
static int read_only_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int b = true, r;
if (argc > 2) {
b = parse_boolean(argv[2]);
if (b < 0)
return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
}
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"MarkImageReadOnly",
&error,
NULL,
"sb", argv[1], b);
if (r < 0)
return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
return 0;
}
static int set_limit(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
uint64_t limit;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
limit = (uint64_t) -1;
else {
r = parse_size(argv[argc-1], 1024, &limit);
if (r < 0)
return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
}
if (argc > 2)
/* With two arguments changes the quota limit of the specified image */
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"SetImageLimit",
&error,
NULL,
"st", argv[1], limit);
else
/* With one argument changes the pool quota limit */
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"SetPoolLimit",
&error,
NULL,
"t", limit);
if (r < 0)
return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
return 0;
}
static int is_image_attached(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *image = NULL;
const char *state;
int r;
r = determine_image(argv[1], true, &image);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"GetImageState",
&error,
&reply,
"s", image);
if (r < 0)
return log_error_errno(r, "Failed to get image state: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &state);
if (r < 0)
return r;
if (!arg_quiet)
puts(state);
return streq(state, "detached");
}
static int dump_profiles(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **l = NULL;
_cleanup_(closedirp) DIR *d = NULL;
char **i;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_get_property_strv(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"Profiles",
&error,
&l);
if (r < 0)
return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
if (arg_legend)
log_info("Available unit profiles:");
STRV_FOREACH(i, l) {
fputs(*i, stdout);
fputc('\n', stdout);
}
return 0;
}
static int help(int argc, char *argv[], void *userdata) {
(void) pager_open(arg_no_pager, false);
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Attach or detach portable services from the local system.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --no-ask-password Do not ask for system passwords\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" -q --quiet Suppress informational messages\n"
" -p --profile=PROFILE Pick security profile for portable service\n"
" --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
" --runtime Attach portable service until next reboot only\n"
" --no-reload Don't reload the system and service manager\n"
" --cat When inspecting include unit and os-release file\n"
" contents\n\n"
"Commands:\n"
" list List available portable service images\n"
" attach NAME|PATH [PREFIX...]\n"
" Attach the specified portable service image\n"
" detach NAME|PATH Detach the specified portable service image\n"
" inspect NAME|PATH [PREFIX...]\n"
" Show details of specified portable service image\n"
" is-attached NAME|PATH Query if portable service image is attached\n"
" read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
" remove NAME|PATH... Remove a portable service image\n"
" set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
, program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
ARG_COPY,
ARG_RUNTIME,
ARG_NO_RELOAD,
ARG_CAT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "quiet", no_argument, NULL, 'q' },
{ "profile", required_argument, NULL, 'p' },
{ "copy", required_argument, NULL, ARG_COPY },
{ "runtime", no_argument, NULL, ARG_RUNTIME },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "cat", no_argument, NULL, ARG_CAT },
{}
};
assert(argc >= 0);
assert(argv);
for (;;) {
int c;
c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
if (c < 0)
break;
switch (c) {
case 'h':
help(0, NULL, NULL);
return 0;
case ARG_VERSION:
return version();
case ARG_NO_PAGER:
arg_no_pager = true;
break;
case ARG_NO_LEGEND:
arg_legend = false;
break;
case ARG_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
break;
case 'M':
arg_transport = BUS_TRANSPORT_MACHINE;
arg_host = optarg;
break;
case 'q':
arg_quiet = true;
break;
case 'p':
if (!filename_is_valid(optarg)) {
log_error("Unit profile name not valid: %s", optarg);
return -EINVAL;
}
if (streq(optarg, "help"))
return dump_profiles();
arg_profile = optarg;
break;
case ARG_COPY:
if (streq(optarg, "auto"))
arg_copy_mode = NULL;
else if (STR_IN_SET(optarg, "copy", "symlink"))
arg_copy_mode = optarg;
else {
log_error("Failed to parse --copy= argument: %s", optarg);
return -EINVAL;
}
break;
case ARG_RUNTIME:
arg_runtime = true;
break;
case ARG_NO_RELOAD:
arg_reload = false;
break;
case ARG_CAT:
arg_cat = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
}
return 1;
}
int main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
{ "attach", 2, VERB_ANY, 0, attach_image },
{ "detach", 2, 2, 0, detach_image },
{ "inspect", 2, VERB_ANY, 0, inspect_image },
{ "is-attached", 2, 2, 0, is_image_attached },
{ "read-only", 2, 3, 0, read_only_image },
{ "remove", 2, VERB_ANY, 0, remove_image },
{ "set-limit", 3, 3, 0, set_limit },
{}
};
int r;
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
r = dispatch_verb(argc, argv, verbs, NULL);
finish:
pager_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

View file

@ -0,0 +1,402 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "btrfs-util.h"
#include "bus-common-errors.h"
#include "bus-util.h"
#include "fd-util.h"
#include "io-util.h"
#include "machine-image.h"
#include "portable.h"
#include "portabled-bus.h"
#include "portabled-image-bus.h"
#include "portabled-image.h"
#include "portabled.h"
#include "strv.h"
#include "user-util.h"
static int property_get_pool_path(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "s", "/var/lib/portables");
}
static int property_get_pool_usage(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
_cleanup_close_ int fd = -1;
uint64_t usage = (uint64_t) -1;
assert(bus);
assert(reply);
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (fd >= 0) {
BtrfsQuotaInfo q;
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
usage = q.referenced;
}
return sd_bus_message_append(reply, "t", usage);
}
static int property_get_pool_limit(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
_cleanup_close_ int fd = -1;
uint64_t size = (uint64_t) -1;
assert(bus);
assert(reply);
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (fd >= 0) {
BtrfsQuotaInfo q;
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
size = q.referenced_max;
}
return sd_bus_message_append(reply, "t", size);
}
static int property_get_profiles(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(bus);
assert(reply);
r = portable_get_profiles(&l);
if (r < 0)
return r;
return sd_bus_message_append_strv(reply, l);
}
static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *p = NULL;
Manager *m = userdata;
const char *name;
Image *image;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
r = bus_image_acquire(m, message, name, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
if (r < 0)
return r;
r = bus_image_path(image, &p);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "o", p);
}
static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(image_hashmap_freep) Hashmap *images = NULL;
Manager *m = userdata;
Image *image;
Iterator i;
int r;
assert(message);
assert(m);
images = hashmap_new(&string_hash_ops);
if (!images)
return -ENOMEM;
r = manager_image_cache_discover(m, images, error);
if (r < 0)
return r;
r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "(ssbtttso)");
if (r < 0)
return r;
HASHMAP_FOREACH(image, images, i) {
_cleanup_(sd_bus_error_free) sd_bus_error error_state = SD_BUS_ERROR_NULL;
PortableState state = _PORTABLE_STATE_INVALID;
_cleanup_free_ char *p = NULL;
r = bus_image_path(image, &p);
if (r < 0)
return r;
r = portable_get_state(
sd_bus_message_get_bus(message),
image->path,
0,
&state,
&error_state);
if (r < 0)
log_debug_errno(r, "Failed to get state of image '%s', ignoring: %s",
image->path, bus_error_message(&error_state, r));
r = sd_bus_message_append(reply, "(ssbtttso)",
image->name,
image_type_to_string(image->type),
image->read_only,
image->crtime,
image->mtime,
image->usage,
portable_state_to_string(state),
p);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
static int redirect_method_to_image(
Manager *m,
sd_bus_message *message,
sd_bus_error *error,
int (*method)(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error* error)) {
const char *name_or_path;
int r;
assert(m);
assert(message);
assert(method);
r = sd_bus_message_read(message, "s", &name_or_path);
if (r < 0)
return r;
return method(m, message, name_or_path, NULL, error);
}
static int method_get_image_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_get_os_release);
}
static int method_get_image_metadata(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_get_metadata);
}
static int method_get_image_state(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *name_or_path;
PortableState state;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name_or_path);
if (r < 0)
return r;
r = portable_get_state(
sd_bus_message_get_bus(message),
name_or_path,
0,
&state,
error);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "s", portable_state_to_string(state));
}
static int method_attach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_attach);
}
static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
PortableChange *changes = NULL;
Manager *m = userdata;
size_t n_changes = 0;
const char *name_or_path;
int r, runtime;
assert(message);
assert(m);
/* Note that we do not redirect detaching to the image object here, because we want to allow that users can
* detach already deleted images too, in case the user already deleted an image before properly detaching
* it. */
r = sd_bus_message_read(message, "sb", &name_or_path, &runtime);
if (r < 0)
return r;
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
"org.freedesktop.portable1.attach-images",
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = portable_detach(
sd_bus_message_get_bus(message),
name_or_path,
runtime ? PORTABLE_RUNTIME : 0,
&changes,
&n_changes,
error);
if (r < 0)
goto finish;
r = reply_portable_changes(message, changes, n_changes);
finish:
portable_changes_free(changes, n_changes);
return r;
}
static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_remove);
}
static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_mark_read_only);
}
static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_set_limit);
}
static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
uint64_t limit;
int r;
assert(message);
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
if (!FILE_SIZE_VALID_OR_INFINITY(limit))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
"org.freedesktop.portable1.manage-images",
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
(void) btrfs_qgroup_set_limit("/var/lib/portables", 0, limit);
r = btrfs_subvol_set_subtree_quota_limit("/var/lib/portables", 0, limit);
if (r == -ENOTTY)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs.");
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m");
return sd_bus_reply_method_return(message, NULL);
}
const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0),
SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0),
SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0),
SD_BUS_PROPERTY("Profiles", "as", property_get_profiles, 0, 0),
SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ListImages", NULL, "a(ssbtttso)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageOSRelease", "s", "a{ss}", method_get_image_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageState", "s", "s", method_get_image_state, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageMetadata", "sas", "saya{say}", method_get_image_metadata, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("AttachImage", "sassbs", "a(sss)", method_attach_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("DetachImage", "sb", "a(sss)", method_detach_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
int reply_portable_changes(sd_bus_message *m, const PortableChange *changes, size_t n_changes) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
size_t i;
int r;
assert(m);
assert(changes || n_changes == 0);
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "(sss)");
if (r < 0)
return r;
for (i = 0; i < n_changes; i++) {
r = sd_bus_message_append(reply, "(sss)",
portable_change_type_to_string(changes[i].type),
changes[i].path,
changes[i].source);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}

View file

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "portable.h"
extern const sd_bus_vtable manager_vtable[];
int reply_portable_changes(sd_bus_message *m, const PortableChange *changes, size_t n_changes);

View file

@ -0,0 +1,733 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-label.h"
#include "bus-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "machine-image.h"
#include "portable.h"
#include "portabled-bus.h"
#include "portabled-image-bus.h"
#include "portabled-image.h"
#include "portabled.h"
#include "process-util.h"
#include "strv.h"
#include "user-util.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType);
int bus_image_common_get_os_release(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
int r;
assert(name_or_path || image);
assert(message);
if (!m) {
assert(image);
m = image->userdata;
}
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_BY_PATH,
"org.freedesktop.portable1.inspect-images",
&image,
error);
if (r < 0)
return r;
if (r == 0) /* Will call us back */
return 1;
if (!image->metadata_valid) {
r = image_read_metadata(image);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
return bus_reply_pair_array(message, image->os_release);
}
static int bus_image_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_get_os_release(NULL, message, NULL, userdata, error);
}
static int append_fd(sd_bus_message *m, PortableMetadata *d) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *buf = NULL;
size_t n;
int r;
assert(m);
assert(d);
assert(d->fd >= 0);
f = fdopen(d->fd, "re");
if (!f)
return -errno;
d->fd = -1;
r = read_full_stream(f, &buf, &n);
if (r < 0)
return r;
return sd_bus_message_append_array(m, 'y', buf, n);
}
int bus_image_common_get_metadata(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_(portable_metadata_hashmap_unrefp) Hashmap *unit_files = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ PortableMetadata **sorted = NULL;
_cleanup_strv_free_ char **matches = NULL;
size_t i;
int r;
assert(name_or_path || image);
assert(message);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_BY_PATH,
"org.freedesktop.portable1.inspect-images",
&image,
error);
if (r < 0)
return r;
if (r == 0) /* Will call us back */
return 1;
r = portable_extract(
image->path,
matches,
&os_release,
&unit_files,
error);
if (r < 0)
return r;
r = portable_metadata_hashmap_to_sorted_array(unit_files, &sorted);
if (r < 0)
return r;
r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", image->path);
if (r < 0)
return r;
r = append_fd(reply, os_release);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{say}");
if (r < 0)
return r;
for (i = 0; i < hashmap_size(unit_files); i++) {
r = sd_bus_message_open_container(reply, 'e', "say");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", sorted[i]->name);
if (r < 0)
return r;
r = append_fd(reply, sorted[i]);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
static int bus_image_method_get_metadata(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_get_metadata(NULL, message, NULL, userdata, error);
}
static int bus_image_method_get_state(
sd_bus_message *message,
void *userdata,
sd_bus_error *error) {
Image *image = userdata;
PortableState state;
int r;
assert(message);
assert(image);
r = portable_get_state(
sd_bus_message_get_bus(message),
image->path,
0,
&state,
error);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "s", portable_state_to_string(state));
}
int bus_image_common_attach(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
_cleanup_free_ char **matches = NULL;
PortableChange *changes = NULL;
PortableFlags flags = 0;
const char *copy_mode;
size_t n_changes = 0;
const char *profile;
int runtime, r;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode);
if (r < 0)
return r;
if (streq(copy_mode, "symlink"))
flags |= PORTABLE_PREFER_SYMLINK;
else if (streq(copy_mode, "copy"))
flags |= PORTABLE_PREFER_COPY;
else if (!isempty(copy_mode))
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
if (runtime)
flags |= PORTABLE_RUNTIME;
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.attach-images",
&image,
error);
if (r < 0)
return r;
if (r == 0) /* Will call us back */
return 1;
r = portable_attach(
sd_bus_message_get_bus(message),
image->path,
matches,
profile,
flags,
&changes,
&n_changes,
error);
if (r < 0)
goto finish;
r = reply_portable_changes(message, changes, n_changes);
finish:
portable_changes_free(changes, n_changes);
return r;
}
static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_attach(NULL, message, NULL, userdata, error);
}
static int bus_image_method_detach(
sd_bus_message *message,
void *userdata,
sd_bus_error *error) {
PortableChange *changes = NULL;
Image *image = userdata;
Manager *m = image->userdata;
size_t n_changes = 0;
int r, runtime;
assert(message);
assert(image);
assert(m);
r = sd_bus_message_read(message, "b", &runtime);
if (r < 0)
return r;
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
"org.freedesktop.portable1.attach-images",
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = portable_detach(
sd_bus_message_get_bus(message),
image->path,
runtime ? PORTABLE_RUNTIME : 0,
&changes,
&n_changes,
error);
if (r < 0)
goto finish;
r = reply_portable_changes(message, changes, n_changes);
finish:
portable_changes_free(changes, n_changes);
return r;
}
int bus_image_common_remove(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
_cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
_cleanup_(sigkill_waitp) pid_t child = 0;
PortableState state;
int r;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
if (m->n_operations >= OPERATIONS_MAX)
return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.manage-images",
&image,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = portable_get_state(
sd_bus_message_get_bus(message),
image->path,
0,
&state,
error);
if (r < 0)
return r;
if (state != PORTABLE_DETACHED)
return sd_bus_error_set_errnof(error, EBUSY, "Image '%s' is not detached, refusing.", image->path);
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
r = safe_fork("(sd-imgrm)", FORK_RESET_SIGNALS, &child);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = image_remove(image);
if (r < 0) {
(void) write(errno_pipe_fd[1], &r, sizeof(r));
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
r = operation_new(m, child, message, errno_pipe_fd[0], NULL);
if (r < 0)
return r;
child = 0;
errno_pipe_fd[0] = -1;
return 1;
}
static int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_remove(NULL, message, NULL, userdata, error);
}
int bus_image_common_mark_read_only(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
int r, read_only;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read(message, "b", &read_only);
if (r < 0)
return r;
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.manage-images",
&image,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = image_read_only(image, read_only);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
static int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_mark_read_only(NULL, message, NULL, userdata, error);
}
int bus_image_common_set_limit(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
uint64_t limit;
int r;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
if (!FILE_SIZE_VALID_OR_INFINITY(limit))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.manage-images",
&image,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = image_set_limit(image, limit);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
static int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_set_limit(NULL, message, NULL, userdata, error);
}
const sd_bus_vtable image_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetMedatadata", "as", "saya{say}", bus_image_method_get_metadata, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetState", NULL, "s", bus_image_method_get_state, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Attach", "assbs", "a(sss)", bus_image_method_attach, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Detach", "b", "a(sss)", bus_image_method_detach, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
int bus_image_path(Image *image, char **ret) {
assert(image);
assert(ret);
if (!image->discoverable)
return -EINVAL;
return sd_bus_path_encode("/org/freedesktop/portable1/image", image->name, ret);
}
int bus_image_acquire(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
ImageAcquireMode mode,
const char *polkit_action,
Image **ret,
sd_bus_error *error) {
_cleanup_(image_unrefp) Image *loaded = NULL;
Image *cached;
int r;
assert(m);
assert(message);
assert(name_or_path || image);
assert(mode >= 0);
assert(mode < _BUS_IMAGE_ACQUIRE_MODE_MAX);
assert(polkit_action || mode == BUS_IMAGE_REFUSE_BY_PATH);
assert(ret);
/* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
polkit_action,
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0) { /* Will call us back */
*ret = NULL;
return 0;
}
}
/* Already passed in? */
if (image) {
*ret = image;
return 1;
}
/* Let's see if this image is already cached? */
cached = manager_image_cache_get(m, name_or_path);
if (cached) {
*ret = cached;
return 1;
}
if (image_name_is_valid(name_or_path)) {
/* If it's a short name, let's search for it */
r = image_find(IMAGE_PORTABLE, name_or_path, &loaded);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);
/* other errors are handled below… */
} else {
/* Don't accept path if this is always forbidden */
if (mode == BUS_IMAGE_REFUSE_BY_PATH)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Expected image name, not path in place of '%s'.", name_or_path);
if (!path_is_absolute(name_or_path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is not valid or not a valid path.", name_or_path);
if (!path_is_normalized(name_or_path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image path '%s' is not normalized.", name_or_path);
if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
polkit_action,
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0) { /* Will call us back */
*ret = NULL;
return 0;
}
}
r = image_from_path(name_or_path, &loaded);
}
if (r < 0)
return r;
/* Add what we just loaded to the cache. This has as side-effect that the object stays in memory until the
* cache is purged again, i.e. at least for the current event loop iteration, which is all we need, and which
* means we don't actually need to ref the return object. */
r = manager_image_cache_add(m, loaded);
if (r < 0)
return r;
*ret = loaded;
return 1;
}
int bus_image_object_find(
sd_bus *bus,
const char *path,
const char *interface,
void *userdata,
void **found,
sd_bus_error *error) {
_cleanup_free_ char *e = NULL;
Manager *m = userdata;
Image *image = NULL;
int r;
assert(bus);
assert(path);
assert(interface);
assert(found);
r = sd_bus_path_decode(path, "/org/freedesktop/portable1/image", &e);
if (r < 0)
return 0;
if (r == 0)
goto not_found;
r = bus_image_acquire(m, sd_bus_get_current_message(bus), e, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
if (r == -ENOENT)
goto not_found;
if (r < 0)
return r;
*found = image;
return 1;
not_found:
*found = NULL;
return 0;
}
int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
_cleanup_(image_hashmap_freep) Hashmap *images = NULL;
_cleanup_strv_free_ char **l = NULL;
size_t n_allocated = 0, n = 0;
Manager *m = userdata;
Image *image;
Iterator i;
int r;
assert(bus);
assert(path);
assert(nodes);
images = hashmap_new(&string_hash_ops);
if (!images)
return -ENOMEM;
r = manager_image_cache_discover(m, images, error);
if (r < 0)
return r;
HASHMAP_FOREACH(image, images, i) {
char *p;
r = bus_image_path(image, &p);
if (r < 0)
return r;
if (!GREEDY_REALLOC(l, n_allocated, n+2)) {
free(p);
return -ENOMEM;
}
l[n++] = p;
l[n] = NULL;
}
*nodes = TAKE_PTR(l);
return 1;
}

View file

@ -0,0 +1,40 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "sd-bus.h"
#include "machine-image.h"
#include "portabled.h"
int bus_image_common_get_os_release(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_get_metadata(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_attach(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_remove(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_mark_read_only(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_set_limit(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
extern const sd_bus_vtable image_vtable[];
int bus_image_path(Image *image, char **ret);
/* So here's some complexity: some of operations can either take an image name, or a fully qualified file system path
* to an image. We need to authenticate differently when processing these two: images referenced via simple image names
* mean the images are located in the image search path and thus safe for limited read access for unprivileged
* clients. For operations on images located anywhere else we need explicit authentication however, so that
* unprivileged clients can't make us open arbitrary files in the file system.
*
* The "Image" bus objects directly represent images in the image search path, but do not exist for path-referenced
* images. Hence, when requesting a bus object we need to refuse references by file system path, but still allow
* references by image name. Depending on the operation to execute potentially we need to authenticate in all cases. */
typedef enum ImageAcquireMode {
BUS_IMAGE_REFUSE_BY_PATH, /* allow by name + prohibit by path */
BUS_IMAGE_AUTHENTICATE_BY_PATH, /* allow by name + polkit by path */
BUS_IMAGE_AUTHENTICATE_ALL, /* polkit by name + polkit by path */
_BUS_IMAGE_ACQUIRE_MODE_MAX,
_BUS_IMAGE_ACQUIRE_MODE_INVALID = -1
} ImageAcquireMode;
int bus_image_acquire(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, ImageAcquireMode mode, const char *polkit_action, Image **ret, sd_bus_error *error);
int bus_image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);

View file

@ -0,0 +1,105 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "portable.h"
#include "portabled-image.h"
#include "portabled.h"
Image *manager_image_cache_get(Manager *m, const char *name_or_path) {
assert(m);
return hashmap_get(m->image_cache, name_or_path);
}
static int image_cache_flush(sd_event_source *s, void *userdata) {
Manager *m = userdata;
assert(s);
assert(m);
hashmap_clear_with_destructor(m->image_cache, image_unref);
return 0;
}
static int manager_image_cache_initialize(Manager *m) {
int r;
assert(m);
r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops);
if (r < 0)
return r;
/* We flush the cache as soon as we are idle again */
if (!m->image_cache_defer_event) {
r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_cache_flush, m);
if (r < 0)
return r;
r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE);
if (r < 0)
return r;
}
r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT);
if (r < 0)
return r;
return 0;
}
int manager_image_cache_add(Manager *m, Image *image) {
int r;
assert(m);
/* We add the specified image to the cache under two keys.
*
* 1. Always under its path
*
* 2. If the image was discovered in the search path (i.e. its discoverable boolean set) we'll also add it
* under its short name.
*
*/
r = manager_image_cache_initialize(m);
if (r < 0)
return r;
image->userdata = m;
r = hashmap_put(m->image_cache, image->path, image);
if (r < 0)
return r;
image_ref(image);
if (image->discoverable) {
r = hashmap_put(m->image_cache, image->name, image);
if (r < 0)
return r;
image_ref(image);
}
return 0;
}
int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *error) {
Image *image;
Iterator i;
int r;
assert(m);
/* A wrapper around image_discover() (for finding images in search path) and portable_discover_attached() (for
* finding attached images). */
r = image_discover(IMAGE_PORTABLE, images);
if (r < 0)
return r;
HASHMAP_FOREACH(image, images, i)
(void) manager_image_cache_add(m, image);
return 0;
}

View file

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "hashmap.h"
#include "machine-image.h"
#include "portabled.h"
Image *manager_image_cache_get(Manager *m, const char *name_or_path);
int manager_image_cache_add(Manager *m, Image *image);
int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *error);

View file

@ -0,0 +1,128 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "fd-util.h"
#include "portabled-operation.h"
#include "process-util.h"
static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
Operation *o = userdata;
int r;
assert(o);
assert(si);
log_debug("Operating " PID_FMT " is now complete with code=%s status=%i",
o->pid,
sigchld_code_to_string(si->si_code), si->si_status);
o->pid = 0;
if (si->si_code != CLD_EXITED) {
r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
goto fail;
}
if (si->si_status == EXIT_SUCCESS)
r = 0;
else if (read(o->errno_fd, &r, sizeof(r)) != sizeof(r)) { /* Try to acquire error code for failed operation */
r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed.");
goto fail;
}
if (o->done) {
/* A completion routine is set for this operation, call it. */
r = o->done(o, r, &error);
if (r < 0) {
if (!sd_bus_error_is_set(&error))
sd_bus_error_set_errno(&error, r);
goto fail;
}
} else {
/* The default operation when done is to simply return an error on failure or an empty success
* message on success. */
if (r < 0) {
sd_bus_error_set_errno(&error, r);
goto fail;
}
r = sd_bus_reply_method_return(o->message, NULL);
if (r < 0)
log_error_errno(r, "Failed to reply to message: %m");
}
operation_free(o);
return 0;
fail:
r = sd_bus_reply_method_error(o->message, &error);
if (r < 0)
log_error_errno(r, "Failed to reply to message: %m");
operation_free(o);
return 0;
}
int operation_new(Manager *manager, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret) {
Operation *o;
int r;
assert(manager);
assert(child > 1);
assert(message);
assert(errno_fd >= 0);
o = new0(Operation, 1);
if (!o)
return -ENOMEM;
o->extra_fd = -1;
r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o);
if (r < 0) {
free(o);
return r;
}
o->pid = child;
o->message = sd_bus_message_ref(message);
o->errno_fd = errno_fd;
LIST_PREPEND(operations, manager->operations, o);
manager->n_operations++;
o->manager = manager;
log_debug("Started new operation " PID_FMT ".", child);
/* At this point we took ownership of both the child and the errno file descriptor! */
if (ret)
*ret = o;
return 0;
}
Operation *operation_free(Operation *o) {
if (!o)
return NULL;
sd_event_source_unref(o->event_source);
safe_close(o->errno_fd);
safe_close(o->extra_fd);
if (o->pid > 1)
(void) sigkill_wait(o->pid);
sd_bus_message_unref(o->message);
if (o->manager) {
LIST_REMOVE(operations, o->manager->operations, o);
o->manager->n_operations--;
}
return mfree(o);
}

View file

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <sys/types.h>
#include "sd-bus.h"
#include "sd-event.h"
#include "list.h"
typedef struct Operation Operation;
#include "portabled.h"
#define OPERATIONS_MAX 64
struct Operation {
Manager *manager;
pid_t pid;
sd_bus_message *message;
int errno_fd;
int extra_fd;
sd_event_source *event_source;
int (*done)(Operation *o, int ret, sd_bus_error *error);
LIST_FIELDS(Operation, operations);
};
int operation_new(Manager *manager, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret);
Operation *operation_free(Operation *o);

168
src/portable/portabled.c Normal file
View file

@ -0,0 +1,168 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "sd-bus.h"
#include "sd-daemon.h"
#include "alloc-util.h"
#include "bus-util.h"
#include "def.h"
#include "portabled-bus.h"
#include "portabled-image-bus.h"
#include "portabled.h"
#include "process-util.h"
#include "signal-util.h"
static Manager* manager_unref(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
static int manager_new(Manager **ret) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
assert(ret);
m = new0(Manager, 1);
if (!m)
return -ENOMEM;
r = sd_event_default(&m->event);
if (r < 0)
return r;
r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
if (r < 0)
return r;
r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
if (r < 0)
return r;
(void) sd_event_set_watchdog(m->event, true);
*ret = TAKE_PTR(m);
return 0;
}
static Manager* manager_unref(Manager *m) {
assert(m);
hashmap_free_with_destructor(m->image_cache, image_unref);
sd_event_source_unref(m->image_cache_defer_event);
bus_verify_polkit_async_registry_free(m->polkit_registry);
sd_bus_unref(m->bus);
sd_event_unref(m->event);
return mfree(m);
}
static int manager_connect_bus(Manager *m) {
int r;
assert(m);
assert(!m->bus);
r = sd_bus_default_system(&m->bus);
if (r < 0)
return log_error_errno(r, "Failed to connect to system bus: %m");
r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/portable1", "org.freedesktop.portable1.Manager", manager_vtable, m);
if (r < 0)
return log_error_errno(r, "Failed to add manager object vtable: %m");
r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/portable1/image", "org.freedesktop.portable1.Image", image_vtable, bus_image_object_find, m);
if (r < 0)
return log_error_errno(r, "Failed to add image object vtable: %m");
r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/portable1/image", bus_image_node_enumerator, m);
if (r < 0)
return log_error_errno(r, "Failed to add image enumerator: %m");
r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.portable1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
r = sd_bus_attach_event(m->bus, m->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
(void) sd_bus_set_exit_on_disconnect(m->bus, true);
return 0;
}
static int manager_startup(Manager *m) {
int r;
assert(m);
r = manager_connect_bus(m);
if (r < 0)
return r;
return 0;
}
static bool check_idle(void *userdata) {
Manager *m = userdata;
return !m->operations;
}
static int manager_run(Manager *m) {
assert(m);
return bus_event_loop_with_idle(
m->event,
m->bus,
"org.freedesktop.portable1",
DEFAULT_EXIT_USEC,
check_idle, m);
}
int main(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
umask(0022);
if (argc != 1) {
log_error("This program takes no arguments.");
r = -EINVAL;
goto finish;
}
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, -1) >= 0);
r = manager_new(&m);
if (r < 0) {
log_error_errno(r, "Failed to allocate manager object: %m");
goto finish;
}
r = manager_startup(m);
if (r < 0) {
log_error_errno(r, "Failed to fully start up daemon: %m");
goto finish;
}
log_debug("systemd-portabled running as pid " PID_FMT, getpid_cached());
sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");
r = manager_run(m);
log_debug("systemd-portabled stopped as pid " PID_FMT, getpid_cached());
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

25
src/portable/portabled.h Normal file
View file

@ -0,0 +1,25 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "sd-event.h"
#include "hashmap.h"
#include "list.h"
typedef struct Manager Manager;
#include "portabled-operation.h"
struct Manager {
sd_event *event;
sd_bus *bus;
Hashmap *polkit_registry;
Hashmap *image_cache;
sd_event_source *image_cache_defer_event;
LIST_HEAD(Operation, operations);
unsigned n_operations;
};

View file

@ -0,0 +1,30 @@
# The "default" security profile for services, i.e. a number of useful restrictions
[Service]
MountAPIVFS=yes
TemporaryFileSystem=/run
BindReadOnlyPaths=/run/systemd/notify
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
BindReadOnlyPaths=/etc/machine-id
BindReadOnlyPaths=/etc/resolv.conf
BindReadOnlyPaths=/run/dbus/system_bus_socket
DynamicUser=yes
RemoveIPC=yes
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_NET_ADMIN \
CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_SETGID CAP_SETPCAP \
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
PrivateTmp=yes
PrivateDevices=yes
PrivateUsers=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
SystemCallArchitectures=native

View file

@ -0,0 +1,30 @@
# The "nonetwork" security profile for services, i.e. like "default" but without networking
[Service]
MountAPIVFS=yes
TemporaryFileSystem=/run
BindReadOnlyPaths=/run/systemd/notify
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
BindReadOnlyPaths=/etc/machine-id
BindReadOnlyPaths=/run/dbus/system_bus_socket
DynamicUser=yes
RemoveIPC=yes
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_SETGID CAP_SETPCAP \
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
PrivateTmp=yes
PrivateDevices=yes
PrivateUsers=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
PrivateNetwork=yes
IPAddressDeny=any

View file

@ -0,0 +1,29 @@
# The "strict" security profile for services, all options turned on
[Service]
MountAPIVFS=yes
TemporaryFileSystem=/run
BindReadOnlyPaths=/run/systemd/notify
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
BindReadOnlyPaths=/etc/machine-id
DynamicUser=yes
RemoveIPC=yes
CapabilityBoundingSet=
PrivateTmp=yes
PrivateDevices=yes
PrivateUsers=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX
LockPersonality=yes
NoNewPrivileges=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
PrivateNetwork=yes
IPAddressDeny=any
TasksMax=4

View file

@ -0,0 +1,7 @@
# The "trusted" profile for services, i.e. no restrictions are applied
[Service]
MountAPIVFS=yes
BindPaths=/run
BindReadOnlyPaths=/etc/machine-id
BindReadOnlyPaths=/etc/resolv.conf

View file

@ -1202,7 +1202,7 @@ int link_load_user(Link *l) {
if (l->is_managed)
return 0; /* if the device is managed, then networkd is our configuration source, not the bus API */
r = parse_env_file(l->state_file, NEWLINE,
r = parse_env_file(NULL, l->state_file, NEWLINE,
"LLMNR", &llmnr,
"MDNS", &mdns,
"DNSSEC", &dnssec,

View file

@ -1847,3 +1847,34 @@ int bus_request_name_async_may_reload_dbus(sd_bus *bus, sd_bus_slot **ret_slot,
return sd_bus_request_name_async(bus, ret_slot, name, flags, request_name_handler_may_reload_dbus, data);
}
int bus_reply_pair_array(sd_bus_message *m, char **l) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
char **k, **v;
int r;
assert(m);
/* Reply to the specified message with a message containing a dictionary put together from the specified
* strv */
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{ss}");
if (r < 0)
return r;
STRV_FOREACH_PAIR(k, v, l) {
r = sd_bus_message_append(reply, "{ss}", *k, *v);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}

View file

@ -184,3 +184,5 @@ static inline int bus_open_system_watch_bind(sd_bus **ret) {
}
int bus_request_name_async_may_reload_dbus(sd_bus *bus, sd_bus_slot **ret_slot, const char *name, uint64_t flags, void *userdata);
int bus_reply_pair_array(sd_bus_message *m, char **l);

View file

@ -344,7 +344,7 @@ int show_cgroup_get_path_and_warn(
const char *m;
m = strjoina("/run/systemd/machines/", machine);
r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL);
r = parse_env_file(NULL, m, NEWLINE, "SCOPE", &unit, NULL);
if (r < 0)
return log_error_errno(r, "Failed to load machine data: %m");

View file

@ -464,7 +464,7 @@ static int condition_test_needs_update(Condition *c) {
uint64_t timestamp;
int r;
r = parse_env_file(p, NULL, "TIMESTAMP_NSEC", &timestamp_str, NULL);
r = parse_env_file(NULL, p, NULL, "TIMESTAMP_NSEC", &timestamp_str, NULL);
if (r < 0) {
log_error_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p);
return true;

View file

@ -30,6 +30,7 @@
#include "linux-3.13/dm-ioctl.h"
#include "missing.h"
#include "mount-util.h"
#include "os-util.h"
#include "path-util.h"
#include "process-util.h"
#include "raw-clone.h"
@ -779,6 +780,14 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
r = mount_partition(m->partitions + PARTITION_ROOT, where, NULL, uid_shift, flags);
if (r < 0)
return r;
if (flags & DISSECT_IMAGE_VALIDATE_OS) {
r = path_is_os_tree(where);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
}
}
if ((flags & DISSECT_IMAGE_MOUNT_ROOT_ONLY))
@ -1270,15 +1279,11 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
if (r < 0)
goto finish;
r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS, &child);
r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child);
if (r < 0)
goto finish;
if (r == 0) {
/* Make sure we never propagate to the host */
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
_exit(EXIT_FAILURE);
r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY);
r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_VALIDATE_OS);
if (r < 0) {
log_debug_errno(r, "Failed to mount dissected image: %m");
_exit(EXIT_FAILURE);

View file

@ -62,6 +62,7 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_REQUIRE_ROOT = 1 << 5, /* Don't accept disks without root partition */
DISSECT_IMAGE_MOUNT_ROOT_ONLY = 1 << 6, /* Mount only the root partition */
DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY = 1 << 7, /* Mount only non-root partitions */
DISSECT_IMAGE_VALIDATE_OS = 1 << 8, /* Refuse mounting images that aren't identifyable as OS images */
} DissectImageFlags;
struct DissectedImage {

View file

@ -99,8 +99,6 @@ static inline void presets_freep(Presets *p) {
p->n_rules = 0;
}
static int unit_file_lookup_state(UnitFileScope scope, const LookupPaths *paths, const char *name, UnitFileState *ret);
bool unit_type_may_alias(UnitType type) {
return IN_SET(type,
UNIT_SERVICE,
@ -2643,7 +2641,7 @@ int unit_file_get_default(
return 0;
}
static int unit_file_lookup_state(
int unit_file_lookup_state(
UnitFileScope scope,
const LookupPaths *paths,
const char *name,

View file

@ -207,6 +207,12 @@ int unit_file_add_dependency(
UnitFileChange **changes,
size_t *n_changes);
int unit_file_lookup_state(
UnitFileScope scope,
const LookupPaths *paths,
const char *name,
UnitFileState *ret);
int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *name);

View file

@ -35,6 +35,7 @@
#include "machine-image.h"
#include "macro.h"
#include "mkdir.h"
#include "os-util.h"
#include "path-util.h"
#include "rm-rf.h"
#include "string-table.h"
@ -45,16 +46,31 @@
#include "util.h"
#include "xattr-util.h"
static const char image_search_path[] =
"/var/lib/machines\0"
"/var/lib/container\0" /* legacy */
"/usr/local/lib/machines\0"
"/usr/lib/machines\0";
static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
"/run/machines\0" /* and here too */
"/var/lib/machines\0" /* the main place for images */
"/var/lib/container\0" /* legacy */
"/usr/local/lib/machines\0"
"/usr/lib/machines\0",
[IMAGE_PORTABLE] = "/etc/portables\0" /* only place symlinks here */
"/run/portables\0" /* and here too */
"/var/lib/portables\0" /* the main place for images */
"/usr/local/lib/portables\0"
"/usr/lib/portables\0",
};
Image *image_unref(Image *i) {
if (!i)
return NULL;
assert(i->n_ref > 0);
i->n_ref--;
if (i->n_ref > 0)
return NULL;
free(i->name);
free(i->path);
@ -65,6 +81,16 @@ Image *image_unref(Image *i) {
return mfree(i);
}
Image *image_ref(Image *i) {
if (!i)
return NULL;
assert(i->n_ref > 0);
i->n_ref++;
return i;
}
static char **image_settings_path(Image *image) {
_cleanup_strv_free_ char **l = NULL;
const char *fn, *s;
@ -125,6 +151,7 @@ static int image_new(
if (!i)
return -ENOMEM;
i->n_ref = 1;
i->type = t;
i->read_only = read_only;
i->crtime = crtime;
@ -140,7 +167,6 @@ static int image_new(
i->path = strjoin(path, "/", filename);
else
i->path = strdup(filename);
if (!i->path)
return -ENOMEM;
@ -151,45 +177,92 @@ static int image_new(
return 0;
}
static int extract_pretty(const char *path, const char *suffix, char **ret) {
_cleanup_free_ char *name = NULL;
const char *p;
size_t n;
assert(path);
assert(ret);
p = last_path_component(path);
n = strcspn(p, "/");
name = strndup(p, n);
if (!name)
return -ENOMEM;
if (suffix) {
char *e;
e = endswith(name, suffix);
if (!e)
return -EINVAL;
*e = 0;
}
if (!image_name_is_valid(name))
return -EINVAL;
*ret = TAKE_PTR(name);
return 0;
}
static int image_make(
const char *pretty,
int dfd,
const char *path,
const char *filename,
const struct stat *st,
Image **ret) {
struct stat st;
_cleanup_free_ char *pretty_buffer = NULL;
struct stat stbuf;
bool read_only;
int r;
assert(dfd >= 0 || dfd == AT_FDCWD);
assert(filename);
/* We explicitly *do* follow symlinks here, since we want to allow symlinking trees, raw files and block
* devices into /var/lib/machines/, and treat them normally. */
* devices into /var/lib/machines/, and treat them normally.
*
* This function returns -ENOENT if we can't find the image after all, and -EMEDIUMTYPE if it's not a file we
* recognize. */
if (fstatat(dfd, filename, &st, 0) < 0)
return -errno;
if (!st) {
if (fstatat(dfd, filename, &stbuf, 0) < 0)
return -errno;
st = &stbuf;
}
read_only =
(path && path_startswith(path, "/usr")) ||
(faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
if (S_ISDIR(st.st_mode)) {
if (S_ISDIR(st->st_mode)) {
_cleanup_close_ int fd = -1;
unsigned file_attr = 0;
if (!ret)
return 1;
return 0;
if (!pretty)
pretty = filename;
if (!pretty) {
r = extract_pretty(filename, NULL, &pretty_buffer);
if (r < 0)
return r;
pretty = pretty_buffer;
}
fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
if (fd < 0)
return -errno;
/* btrfs subvolumes have inode 256 */
if (st.st_ino == 256) {
if (st->st_ino == 256) {
r = btrfs_is_filesystem(fd);
if (r < 0)
@ -227,7 +300,7 @@ static int image_make(
}
}
return 1;
return 0;
}
}
@ -248,56 +321,69 @@ static int image_make(
if (r < 0)
return r;
return 1;
return 0;
} else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) {
} else if (S_ISREG(st->st_mode) && endswith(filename, ".raw")) {
usec_t crtime = 0;
/* It's a RAW disk image */
if (!ret)
return 1;
return 0;
fd_getcrtime_at(dfd, filename, &crtime, 0);
(void) fd_getcrtime_at(dfd, filename, &crtime, 0);
if (!pretty)
pretty = strndupa(filename, strlen(filename) - 4);
if (!pretty) {
r = extract_pretty(filename, ".raw", &pretty_buffer);
if (r < 0)
return r;
pretty = pretty_buffer;
}
r = image_new(IMAGE_RAW,
pretty,
path,
filename,
!(st.st_mode & 0222) || read_only,
!(st->st_mode & 0222) || read_only,
crtime,
timespec_load(&st.st_mtim),
timespec_load(&st->st_mtim),
ret);
if (r < 0)
return r;
(*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512;
(*ret)->limit = (*ret)->limit_exclusive = st.st_size;
(*ret)->usage = (*ret)->usage_exclusive = st->st_blocks * 512;
(*ret)->limit = (*ret)->limit_exclusive = st->st_size;
return 1;
return 0;
} else if (S_ISBLK(st.st_mode)) {
} else if (S_ISBLK(st->st_mode)) {
_cleanup_close_ int block_fd = -1;
uint64_t size = UINT64_MAX;
/* A block device */
if (!ret)
return 1;
return 0;
if (!pretty)
pretty = filename;
if (!pretty) {
r = extract_pretty(filename, NULL, &pretty_buffer);
if (r < 0)
return r;
pretty = pretty_buffer;
}
block_fd = openat(dfd, filename, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
if (block_fd < 0)
log_debug_errno(errno, "Failed to open block device %s/%s, ignoring: %m", path, filename);
else {
if (fstat(block_fd, &st) < 0)
/* Refresh stat data after opening the node */
if (fstat(block_fd, &stbuf) < 0)
return -errno;
if (!S_ISBLK(st.st_mode)) /* Verify that what we opened is actually what we think it is */
st = &stbuf;
if (!S_ISBLK(st->st_mode)) /* Verify that what we opened is actually what we think it is */
return -ENOTTY;
if (!read_only) {
@ -310,7 +396,7 @@ static int image_make(
}
if (ioctl(block_fd, BLKGETSIZE64, &size) < 0)
log_debug_errno(errno, "Failed to issue BLKFLSBUF on device %s/%s, ignoring: %m", path, filename);
log_debug_errno(errno, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path, filename);
block_fd = safe_close(block_fd);
}
@ -319,7 +405,7 @@ static int image_make(
pretty,
path,
filename,
!(st.st_mode & 0222) || read_only,
!(st->st_mode & 0222) || read_only,
0,
0,
ret);
@ -329,24 +415,27 @@ static int image_make(
if (size != 0 && size != UINT64_MAX)
(*ret)->usage = (*ret)->usage_exclusive = (*ret)->limit = (*ret)->limit_exclusive = size;
return 1;
return 0;
}
return 0;
return -EMEDIUMTYPE;
}
int image_find(const char *name, Image **ret) {
int image_find(ImageClass class, const char *name, Image **ret) {
const char *path;
int r;
assert(class >= 0);
assert(class < _IMAGE_CLASS_MAX);
assert(name);
/* There are no images with invalid names */
if (!image_name_is_valid(name))
return 0;
return -ENOENT;
NULSTR_FOREACH(path, image_search_path) {
NULSTR_FOREACH(path, image_search_path[class]) {
_cleanup_closedir_ DIR *d = NULL;
struct stat st;
d = opendir(path);
if (!d) {
@ -356,37 +445,90 @@ int image_find(const char *name, Image **ret) {
return -errno;
}
r = image_make(NULL, dirfd(d), path, name, ret);
if (IN_SET(r, 0, -ENOENT)) {
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people to
* symlink block devices into the search path */
if (fstatat(dirfd(d), name, &st, 0) < 0) {
_cleanup_free_ char *raw = NULL;
if (errno != ENOENT)
return -errno;
raw = strappend(name, ".raw");
if (!raw)
return -ENOMEM;
r = image_make(NULL, dirfd(d), path, raw, ret);
if (IN_SET(r, 0, -ENOENT))
if (fstatat(dirfd(d), raw, &st, 0) < 0) {
if (errno == ENOENT)
continue;
return -errno;
}
if (!S_ISREG(st.st_mode))
continue;
r = image_make(name, dirfd(d), path, raw, &st, ret);
} else {
if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
continue;
r = image_make(name, dirfd(d), path, name, &st, ret);
}
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
return r;
if (ret)
(*ret)->discoverable = true;
return 1;
}
if (streq(name, ".host"))
return image_make(".host", AT_FDCWD, NULL, "/", ret);
if (class == IMAGE_MACHINE && streq(name, ".host")) {
r = image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
if (r < 0)
return r;
return 0;
if (ret)
(*ret)->discoverable = true;
return r;
}
return -ENOENT;
};
int image_discover(Hashmap *h) {
int image_from_path(const char *path, Image **ret) {
/* Note that we don't set the 'discoverable' field of the returned object, because we don't check here whether
* the image is in the image search path. And if it is we don't know if the path we used is actually not
* overriden by another, different image earlier in the search path */
if (path_equal(path, "/"))
return image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
return image_make(NULL, AT_FDCWD, NULL, path, NULL, ret);
}
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret) {
if (image_name_is_valid(name_or_path))
return image_find(class, name_or_path, ret);
return image_from_path(name_or_path, ret);
}
int image_discover(ImageClass class, Hashmap *h) {
const char *path;
int r;
assert(class >= 0);
assert(class < _IMAGE_CLASS_MAX);
assert(h);
NULSTR_FOREACH(path, image_search_path) {
NULSTR_FOREACH(path, image_search_path[class]) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
@ -400,19 +542,53 @@ int image_discover(Hashmap *h) {
FOREACH_DIRENT_ALL(de, d, return -errno) {
_cleanup_(image_unrefp) Image *image = NULL;
_cleanup_free_ char *truncated = NULL;
const char *pretty;
struct stat st;
if (!image_name_is_valid(de->d_name))
if (dot_or_dot_dot(de->d_name))
continue;
if (hashmap_contains(h, de->d_name))
/* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people
* to symlink block devices into the search path */
if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
if (errno == ENOENT)
continue;
return -errno;
}
if (S_ISREG(st.st_mode)) {
const char *e;
e = endswith(de->d_name, ".raw");
if (!e)
continue;
truncated = strndup(de->d_name, e - de->d_name);
if (!truncated)
return -ENOMEM;
pretty = truncated;
} else if (S_ISDIR(st.st_mode) || S_ISBLK(st.st_mode))
pretty = de->d_name;
else
continue;
r = image_make(NULL, dirfd(d), path, de->d_name, &image);
if (IN_SET(r, 0, -ENOENT))
if (!image_name_is_valid(pretty))
continue;
if (hashmap_contains(h, pretty))
continue;
r = image_make(pretty, dirfd(d), path, de->d_name, &st, &image);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
return r;
image->discoverable = true;
r = hashmap_put(h, image->name, image);
if (r < 0)
return r;
@ -421,19 +597,20 @@ int image_discover(Hashmap *h) {
}
}
if (!hashmap_contains(h, ".host")) {
if (class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
_cleanup_(image_unrefp) Image *image = NULL;
r = image_make(".host", AT_FDCWD, NULL, "/", &image);
r = image_make(".host", AT_FDCWD, NULL, "/", NULL, &image);
if (r < 0)
return r;
image->discoverable = true;
r = hashmap_put(h, image->name, image);
if (r < 0)
return r;
image = NULL;
}
return 0;
@ -566,11 +743,11 @@ int image_rename(Image *i, const char *new_name) {
if (r < 0)
return r;
r = image_find(new_name, NULL);
if (r < 0)
return r;
if (r > 0)
r = image_find(IMAGE_MACHINE, new_name, NULL);
if (r >= 0)
return -EEXIST;
if (r != -ENOENT)
return r;
switch (i->type) {
@ -679,11 +856,11 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
if (r < 0)
return r;
r = image_find(new_name, NULL);
if (r < 0)
return r;
if (r > 0)
r = image_find(IMAGE_MACHINE, new_name, NULL);
if (r >= 0)
return -EEXIST;
if (r != -ENOENT)
return r;
switch (i->type) {
@ -923,6 +1100,7 @@ int image_read_metadata(Image *i) {
sd_id128_t machine_id = SD_ID128_NULL;
_cleanup_free_ char *hostname = NULL;
_cleanup_free_ char *path = NULL;
_cleanup_fclose_ FILE *f = NULL;
r = chase_symlinks("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path);
if (r < 0 && r != -ENOENT)
@ -962,18 +1140,9 @@ int image_read_metadata(Image *i) {
log_debug_errno(r, "Failed to parse machine-info data of %s: %m", i->name);
}
path = mfree(path);
r = chase_symlinks("/etc/os-release", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path);
if (r == -ENOENT)
r = chase_symlinks("/usr/lib/os-release", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path);
if (r < 0 && r != -ENOENT)
log_debug_errno(r, "Failed to chase os-release in image: %m");
else if (r >= 0) {
r = load_env_file_pairs(NULL, path, NULL, &os_release);
if (r < 0)
log_debug_errno(r, "Failed to parse os-release data of %s: %m", i->name);
}
r = load_os_release_pairs(i->path, &os_release);
if (r < 0)
log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
free_and_replace(i->hostname, hostname);
i->machine_id = machine_id;
@ -1059,6 +1228,35 @@ bool image_name_is_valid(const char *s) {
return true;
}
bool image_in_search_path(ImageClass class, const char *image) {
const char *path;
assert(image);
NULSTR_FOREACH(path, image_search_path[class]) {
const char *p;
size_t k;
p = path_startswith(image, path);
if (!p)
continue;
/* Make sure there's a filename following */
k = strcspn(p, "/");
if (k == 0)
continue;
p += k;
/* Accept trailing slashes */
if (p[strspn(p, "/")] == 0)
return true;
}
return false;
}
static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
[IMAGE_DIRECTORY] = "directory",
[IMAGE_SUBVOLUME] = "subvolume",

View file

@ -17,6 +17,13 @@
#include "string-util.h"
#include "time-util.h"
typedef enum ImageClass {
IMAGE_MACHINE,
IMAGE_PORTABLE,
_IMAGE_CLASS_MAX,
_IMAGE_CLASS_INVALID = -1
} ImageClass;
typedef enum ImageType {
IMAGE_DIRECTORY,
IMAGE_SUBVOLUME,
@ -27,6 +34,8 @@ typedef enum ImageType {
} ImageType;
typedef struct Image {
unsigned n_ref;
ImageType type;
char *name;
char *path;
@ -45,12 +54,15 @@ typedef struct Image {
char **machine_info;
char **os_release;
bool metadata_valid;
bool metadata_valid:1;
bool discoverable:1; /* true if we know for sure that image_find() would find the image given just the short name */
void *userdata;
} Image;
Image *image_unref(Image *i);
Image *image_ref(Image *i);
static inline Hashmap* image_hashmap_free(Hashmap *map) {
return hashmap_free_with_destructor(map, image_unref);
}
@ -58,8 +70,10 @@ static inline Hashmap* image_hashmap_free(Hashmap *map) {
DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free);
int image_find(const char *name, Image **ret);
int image_discover(Hashmap *map);
int image_find(ImageClass class, const char *name, Image **ret);
int image_from_path(const char *path, Image **ret);
int image_find_harder(ImageClass class, const char *name_or_path, Image **ret);
int image_discover(ImageClass class, Hashmap *map);
int image_remove(Image *i);
int image_rename(Image *i, const char *new_name);
@ -78,6 +92,8 @@ int image_set_limit(Image *i, uint64_t referenced_max);
int image_read_metadata(Image *i);
bool image_in_search_path(ImageClass class, const char *image);
static inline bool IMAGE_IS_HIDDEN(const struct Image *i) {
assert(i);

View file

@ -263,10 +263,8 @@ static int acquire_generator_dirs(
if (tempdir)
prefix = tempdir;
else if (scope == UNIT_FILE_SYSTEM)
prefix = "/run/systemd";
else if (scope == UNIT_FILE_USER) {
const char *e;
@ -484,6 +482,10 @@ int lookup_paths_init(
assert(scope >= 0);
assert(scope < _UNIT_FILE_SCOPE_MAX);
#if HAVE_SPLIT_USR
flags |= LOOKUP_PATHS_SPLIT_USR;
#endif
if (!empty_or_root(root_dir)) {
if (scope == UNIT_FILE_USER)
return -EINVAL;
@ -582,9 +584,7 @@ int lookup_paths_init(
"/usr/local/lib/systemd/system",
SYSTEM_DATA_UNIT_PATH,
"/usr/lib/systemd/system",
#if HAVE_SPLIT_USR
"/lib/systemd/system",
#endif
STRV_IFNOTNULL(flags & LOOKUP_PATHS_SPLIT_USR ? "/lib/systemd/system" : NULL),
STRV_IFNOTNULL(generator_late),
NULL);
break;

View file

@ -15,8 +15,9 @@ typedef struct LookupPaths LookupPaths;
#include "macro.h"
typedef enum LookupPathsFlags {
LOOKUP_PATHS_EXCLUDE_GENERATED = 1 << 0,
LOOKUP_PATHS_TEMPORARY_GENERATED = 1 << 1,
LOOKUP_PATHS_EXCLUDE_GENERATED = 1U << 0,
LOOKUP_PATHS_TEMPORARY_GENERATED = 1U << 1,
LOOKUP_PATHS_SPLIT_USR = 1U << 2,
} LookupPathsFlags;
struct LookupPaths {

View file

@ -18,7 +18,7 @@ int udev_parse_config(void) {
size_t n;
int r;
r = parse_env_file("/etc/udev/udev.conf", NEWLINE, "udev_log", &val, NULL);
r = parse_env_file(NULL, "/etc/udev/udev.conf", NEWLINE, "udev_log", &val, NULL);
if (r == -ENOENT || !val)
return 0;
if (r < 0)

View file

@ -5664,7 +5664,7 @@ static int switch_root(int argc, char *argv[], void *userdata) {
if (argc >= 3)
init = argv[2];
else {
r = parse_env_file("/proc/cmdline", WHITESPACE,
r = parse_env_file(NULL, "/proc/cmdline", WHITESPACE,
"init", &cmdline_init,
NULL);
if (r < 0)

View file

@ -242,6 +242,10 @@ tests += [
[],
[]],
[['src/test/test-os-util.c'],
[],
[]],
[['src/test/test-escape.c'],
[],
[]],

View file

@ -10,8 +10,10 @@
#include "alloc-util.h"
#include "conf-files.h"
#include "fileio.h"
#include "fs-util.h"
#include "macro.h"
#include "mkdir.h"
#include "parse-util.h"
#include "rm-rf.h"
#include "string-util.h"
@ -22,12 +24,16 @@
static void setup_test_dir(char *tmp_dir, const char *files, ...) {
va_list ap;
assert_se(mkdtemp(tmp_dir) != NULL);
assert_se(mkdtemp(tmp_dir));
va_start(ap, files);
while (files != NULL) {
_cleanup_free_ char *path = strappend(tmp_dir, files);
assert_se(touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID) == 0);
while (files) {
_cleanup_free_ char *path;
assert_se(path = strappend(tmp_dir, files));
(void) mkdir_parents(path, 0755);
assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0);
files = va_arg(ap, const char *);
}
va_end(ap);
@ -36,7 +42,7 @@ static void setup_test_dir(char *tmp_dir, const char *files, ...) {
static void test_conf_files_list(bool use_root) {
char tmp_dir[] = "/tmp/test-conf-files-XXXXXX";
_cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL;
const char *root_dir, *search_1, *search_2, *expect_a, *expect_b, *expect_c;
const char *root_dir, *search_1, *search_2, *expect_a, *expect_b, *expect_c, *mask;
log_debug("/* %s */", __func__);
@ -45,8 +51,12 @@ static void test_conf_files_list(bool use_root) {
"/dir2/a.conf",
"/dir2/b.conf",
"/dir2/c.foo",
"/dir2/d.conf",
NULL);
mask = strjoina(tmp_dir, "/dir1/d.conf");
assert_se(symlink("/dev/null", mask) >= 0);
if (use_root) {
root_dir = tmp_dir;
search_1 = "/dir1";
@ -63,23 +73,23 @@ static void test_conf_files_list(bool use_root) {
log_debug("/* Check when filtered by suffix */");
assert_se(conf_files_list(&found_files, ".conf", root_dir, 0, search_1, search_2, NULL) == 0);
assert_se(conf_files_list(&found_files, ".conf", root_dir, CONF_FILES_FILTER_MASKED, search_1, search_2, NULL) == 0);
strv_print(found_files);
assert_se(found_files);
assert_se(streq_ptr(found_files[0], expect_a));
assert_se(streq_ptr(found_files[1], expect_b));
assert_se(found_files[2] == NULL);
assert_se(!found_files[2]);
log_debug("/* Check when unfiltered */");
assert_se(conf_files_list(&found_files2, NULL, root_dir, 0, search_1, search_2, NULL) == 0);
assert_se(conf_files_list(&found_files2, NULL, root_dir, CONF_FILES_FILTER_MASKED, search_1, search_2, NULL) == 0);
strv_print(found_files2);
assert_se(found_files2);
assert_se(streq_ptr(found_files2[0], expect_a));
assert_se(streq_ptr(found_files2[1], expect_b));
assert_se(streq_ptr(found_files2[2], expect_c));
assert_se(found_files2[3] == NULL);
assert_se(!found_files2[3]);
assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
}

View file

@ -228,6 +228,93 @@ static void test_rearrange_stdio(void) {
}
}
static void assert_equal_fd(int fd1, int fd2) {
for (;;) {
uint8_t a[4096], b[4096];
ssize_t x, y;
x = read(fd1, a, sizeof(a));
assert(x >= 0);
y = read(fd2, b, sizeof(b));
assert(y >= 0);
assert(x == y);
if (x == 0)
break;
assert(memcmp(a, b, x) == 0);
}
}
static void test_fd_duplicate_data_fd(void) {
_cleanup_close_ int fd1 = -1, fd2 = -1;
_cleanup_(close_pairp) int sfd[2] = { -1, -1 };
_cleanup_(sigkill_waitp) pid_t pid = -1;
uint64_t i, j;
int r;
fd1 = open("/etc/fstab", O_RDONLY|O_CLOEXEC);
if (fd1 >= 0) {
fd2 = fd_duplicate_data_fd(fd1);
assert_se(fd2 >= 0);
assert_se(lseek(fd1, 0, SEEK_SET) == 0);
assert_equal_fd(fd1, fd2);
}
fd1 = safe_close(fd1);
fd2 = safe_close(fd2);
fd1 = acquire_data_fd("hallo", 6, 0);
assert_se(fd1 >= 0);
fd2 = fd_duplicate_data_fd(fd1);
assert_se(fd2 >= 0);
safe_close(fd1);
fd1 = acquire_data_fd("hallo", 6, 0);
assert_se(fd1 >= 0);
assert_equal_fd(fd1, fd2);
fd1 = safe_close(fd1);
fd2 = safe_close(fd2);
assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, sfd) >= 0);
r = safe_fork("(sd-pipe)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
assert_se(r >= 0);
if (r == 0) {
/* child */
sfd[0] = safe_close(sfd[0]);
for (i = 0; i < 1536*1024 / sizeof(uint64_t); i++)
assert_se(write(sfd[1], &i, sizeof(i)) == sizeof(i));
sfd[1] = safe_close(sfd[1]);
_exit(EXIT_SUCCESS);
}
sfd[1] = safe_close(sfd[1]);
fd2 = fd_duplicate_data_fd(sfd[0]);
assert_se(fd2 >= 0);
for (i = 0; i < 1536*1024 / sizeof(uint64_t); i++) {
assert_se(read(fd2, &j, sizeof(j)) == sizeof(j));
assert_se(i == j);
}
assert_se(read(fd2, &j, sizeof(j)) == 0);
}
int main(int argc, char *argv[]) {
test_close_many();
test_close_nointr();
@ -236,6 +323,7 @@ int main(int argc, char *argv[]) {
test_acquire_data_fd();
test_fd_move_above_stdio();
test_rearrange_stdio();
test_fd_duplicate_data_fd();
return 0;
}

View file

@ -95,7 +95,7 @@ static void test_parse_env_file(void) {
}
r = parse_env_file(
t, NULL,
NULL, t, NULL,
"one", &one,
"two", &two,
"three", &three,

22
src/test/test-os-util.c Normal file
View file

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include "log.h"
#include "os-util.h"
static void test_path_is_os_tree(void) {
assert_se(path_is_os_tree("/") > 0);
assert_se(path_is_os_tree("/etc") == 0);
assert_se(path_is_os_tree("/idontexist") == -ENOENT);
}
int main(int argc, char *argv[]) {
log_set_max_level(LOG_DEBUG);
log_parse_environment();
log_open();
test_path_is_os_tree();
return 0;
}

View file

@ -52,12 +52,6 @@ static void test_is_symlink(void) {
unlink(name_link);
}
static void test_path_is_os_tree(void) {
assert_se(path_is_os_tree("/") > 0);
assert_se(path_is_os_tree("/etc") == 0);
assert_se(path_is_os_tree("/idontexist") == -ENOENT);
}
static void test_path_is_fs_type(void) {
/* run might not be a mount point in build chroots */
if (path_is_mount_point("/run", NULL, AT_SYMLINK_FOLLOW) > 0) {
@ -81,7 +75,6 @@ static void test_path_is_temporary_fs(void) {
int main(int argc, char *argv[]) {
test_files_same();
test_is_symlink();
test_path_is_os_tree();
test_path_is_fs_type();
test_path_is_temporary_fs();

View file

@ -420,7 +420,7 @@ int main(int argc, char **argv) {
utf8 = is_locale_utf8();
r = parse_env_file("/etc/vconsole.conf", NEWLINE,
r = parse_env_file(NULL, "/etc/vconsole.conf", NEWLINE,
"KEYMAP", &vc_keymap,
"KEYMAP_TOGGLE", &vc_keymap_toggle,
"FONT", &vc_font,
@ -432,7 +432,7 @@ int main(int argc, char **argv) {
/* Let the kernel command line override /etc/vconsole.conf */
if (detect_container() <= 0) {
r = parse_env_file("/proc/cmdline", WHITESPACE,
r = parse_env_file(NULL, "/proc/cmdline", WHITESPACE,
"vconsole.keymap", &vc_keymap,
"vconsole.keymap_toggle", &vc_keymap_toggle,
"vconsole.font", &vc_font,

View file

@ -8,6 +8,7 @@ tmpfiles = [['home.conf', ''],
['journal-nocow.conf', ''],
['systemd-nologin.conf', ''],
['systemd-nspawn.conf', 'ENABLE_MACHINED'],
['portables.conf', 'ENABLE_PORTABLED'],
['tmp.conf', ''],
['x11.conf', ''],
['legacy.conf', 'HAVE_SYSV_COMPAT'],

View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: LGPL-2.1+
# See tmpfiles.d(5) for details
Q /var/lib/portables 0700

View file

@ -177,6 +177,8 @@ in_units = [
['systemd-networkd-wait-online.service', 'ENABLE_NETWORKD',
join_paths(pkgsysconfdir, 'system/network-online.target.wants/')],
['systemd-nspawn@.service', ''],
['systemd-portabled.service', 'ENABLE_PORTABLED',
'dbus-org.freedesktop.portable1.service'],
['systemd-poweroff.service', ''],
['systemd-quotacheck.service', 'ENABLE_QUOTACHECK'],
['systemd-random-seed.service', 'ENABLE_RANDOMSEED',

View file

@ -0,0 +1,26 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Portable Service Manager
Documentation=man:systemd-portabled.service(8)
RequiresMountsFor=/var/lib/portables
[Service]
ExecStart=@rootlibexecdir@/systemd-portabled
BusName=org.freedesktop.portable1
WatchdogSec=3min
CapabilityBoundingSet=CAP_KILL CAP_SYS_PTRACE CAP_SYS_ADMIN CAP_SETGID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
SystemCallFilter=~@clock @cpu-emulation @debug @keyring @module @obsolete @raw-io @reboot @swap
SystemCallArchitectures=native
LockPersonality=yes
IPAddressDeny=any