Merge pull request #8620 from poettering/portablectl
an implementation of the "portable services" concept
This commit is contained in:
commit
e2183b09ed
17
TODO
17
TODO
|
@ -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
251
doc/PORTABLE_SERVICES.md
Normal 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
404
man/portablectl.xml
Normal 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>
|
|
@ -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'],
|
||||
|
|
61
man/systemd-portabled.service.xml
Normal file
61
man/systemd-portabled.service.xml
Normal 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>
|
24
meson.build
24
meson.build
|
@ -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'],
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, ...);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
117
src/basic/os-util.c
Normal 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
12
src/basic/os-util.h
Normal 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);
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 "\","
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
29
src/portable/meson.build
Normal 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
|
117
src/portable/org.freedesktop.portable1.conf
Normal file
117
src/portable/org.freedesktop.portable1.conf
Normal 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>
|
43
src/portable/org.freedesktop.portable1.policy
Normal file
43
src/portable/org.freedesktop.portable1.policy
Normal 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>
|
7
src/portable/org.freedesktop.portable1.service
Normal file
7
src/portable/org.freedesktop.portable1.service
Normal 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
1427
src/portable/portable.c
Normal file
File diff suppressed because it is too large
Load diff
77
src/portable/portable.h
Normal file
77
src/portable/portable.h
Normal 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
965
src/portable/portablectl.c
Normal 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;
|
||||
}
|
402
src/portable/portabled-bus.c
Normal file
402
src/portable/portabled-bus.c
Normal 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);
|
||||
}
|
10
src/portable/portabled-bus.h
Normal file
10
src/portable/portabled-bus.h
Normal 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);
|
733
src/portable/portabled-image-bus.c
Normal file
733
src/portable/portabled-image-bus.c
Normal 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, ©_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;
|
||||
}
|
40
src/portable/portabled-image-bus.h
Normal file
40
src/portable/portabled-image-bus.h
Normal 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);
|
105
src/portable/portabled-image.c
Normal file
105
src/portable/portabled-image.c
Normal 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;
|
||||
}
|
11
src/portable/portabled-image.h
Normal file
11
src/portable/portabled-image.h
Normal 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);
|
128
src/portable/portabled-operation.c
Normal file
128
src/portable/portabled-operation.c
Normal 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);
|
||||
}
|
29
src/portable/portabled-operation.h
Normal file
29
src/portable/portabled-operation.h
Normal 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
168
src/portable/portabled.c
Normal 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
25
src/portable/portabled.h
Normal 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;
|
||||
};
|
30
src/portable/profile/default/service.conf
Normal file
30
src/portable/profile/default/service.conf
Normal 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
|
30
src/portable/profile/nonetwork/service.conf
Normal file
30
src/portable/profile/nonetwork/service.conf
Normal 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
|
29
src/portable/profile/strict/service.conf
Normal file
29
src/portable/profile/strict/service.conf
Normal 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
|
7
src/portable/profile/trusted/service.conf
Normal file
7
src/portable/profile/trusted/service.conf
Normal 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
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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", ×tamp_str, NULL);
|
||||
r = parse_env_file(NULL, p, NULL, "TIMESTAMP_NSEC", ×tamp_str, NULL);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p);
|
||||
return true;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -242,6 +242,10 @@ tests += [
|
|||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-os-util.c'],
|
||||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-escape.c'],
|
||||
[],
|
||||
[]],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
22
src/test/test-os-util.c
Normal 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;
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'],
|
||||
|
|
4
tmpfiles.d/portables.conf
Normal file
4
tmpfiles.d/portables.conf
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
# See tmpfiles.d(5) for details
|
||||
|
||||
Q /var/lib/portables 0700
|
|
@ -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',
|
||||
|
|
26
units/systemd-portabled.service.in
Normal file
26
units/systemd-portabled.service.in
Normal 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
|
Loading…
Reference in a new issue