"--optimization=0"
"--optimization=s"
"--optimization=3 -Db_lto=true -Ddns-over-tls=false"
- "--optimization=3 -Db_lto=false"
+ "--optimization=3 -Db_lto=false -Dtpm2=false -Dlibfido2=false -Dp11kit=false"
"--optimization=3 -Ddns-over-tls=openssl"
"--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true"
"-Db_ndebug=true"
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- name: Initialize CodeQL
- uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5
+ uses: github/codeql-action/init@04df1262e6247151b5ac09cd2c303ac36ad3f62b
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql-config.yml
- run: sudo -E .github/workflows/unit_tests.sh SETUP
- name: Autobuild
- uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5
+ uses: github/codeql-action/autobuild@04df1262e6247151b5ac09cd2c303ac36ad3f62b
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5
+ uses: github/codeql-action/analyze@04df1262e6247151b5ac09cd2c303ac36ad3f62b
steps:
- name: Label PR based on policy in labeler.yml
- uses: actions/labeler@5c7539237e04b714afd8ad9b4aed733815b9fab4
+ uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b
if: github.event_name == 'pull_request_target' && github.event.action != 'closed'
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
fetch-depth: 0
- name: Lint Code Base
- uses: github/super-linter/slim@bb2d833b08b6c288608686672b93a8a4589cdc49
+ uses: github/super-linter/slim@454ba4482ce2cd0c505bc592e83c06e1e37ade61
env:
DEFAULT_BRANCH: main
MULTI_STATUS: false
--- /dev/null
+name: Make a Github release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Release
+ uses: softprops/action-gh-release@v1
+ with:
+ prerelease: ${{ contains(github.ref_name, '-rc') }}
+ draft: ${{ github.repository == 'systemd/systemd' }}
---
# vi: ts=2 sw=2 et:
# SPDX-License-Identifier: LGPL-2.1-or-later
-# Simple boot tests that build and boot the mkosi images generated by the mkosi config files in mkosi.default.d/.
+# Simple boot tests that build and boot the mkosi images generated by the mkosi config files in mkosi.conf.d/.
name: mkosi
on:
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - uses: systemd/mkosi@c1f1e9ab2fe89f21ebdb4984b676f9a489081a64
+ - uses: systemd/mkosi@f219c1125893e8773efed5ec8a1226f3bd8a00cb
- name: Configure
run: |
- tee mkosi.default <<- EOF
+ tee mkosi.conf <<- EOF
[Distribution]
Distribution=${{ matrix.distro }}
Release=${{ matrix.release }}
persist-credentials: false
- name: Run analysis
- uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # tag=v2.1.2
+ uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # tag=v2.1.3
with:
results_file: results.sarif
results_format: sarif
# Upload the results to GitHub's code scanning dashboard.
- name: Upload to code-scanning
if: github.event_name != 'pull_request'
- uses: github/codeql-action/upload-sarif@32dc499307d133bb5085bae78498c0ac2cf762d5 # tag=v1.0.26
+ uses: github/codeql-action/upload-sarif@04df1262e6247151b5ac09cd2c303ac36ad3f62b # tag=v1.0.26
with:
sarif_file: results.sarif
/.mkosi-*
/mkosi.builddir/
/mkosi.output/
-/mkosi.default
/mkosi.installdir/
/mkosi.secure-boot.*
# Ignore any mkosi config files with "local" in the name
-/mkosi.default.d/**/*local*.conf
+/mkosi.conf.d/**/*local*.conf
/tags
.dir-locals-2.el
.vscode/
Features:
+* rework journalctl -M to be based on a machined method that generates a mount
+ fd of the relevant journal dirs in the container with uidmapping applied to
+ allow the host to read it, while making everything read-only.
+
* fix our various hwdb lookup keys to end with ":" again. The original idea was
that hwdb patters can match arbitrary fields with expressions like
"*:foobar:*", to wildcard match both the start and the end of the string.
always end in a colon. This requires updating our udev rules, as well as
checking if the various hwdb files are fine with that.
-* Add a bus API to enumerate contents of the fdstore of a service,
- handle/display similar to querying the process tree. Should probably just an
- array of inode/devnum of fd, plus fd_get_name() data
-
* mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user
among other things such as dynamic uid ranges for containers and so on. That
way noone can create files there with these uids and we enforce they are only
used transiently, never persistently.
-* set MS_NOSYMFOLLOW for ESP and XBOOTLDR mounts both in gpt-generator and in
- dissect.c
-
* rework loopback support in fstab: when "loop" option is used, then
instantiate a new systemd-loop@.service for the source path, set the
lo_file_name field for it to something recognizable derived from the fstab
* add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing
an encrypted image on some host given a public key belonging to a specific
other host, so that only hosts possessing the private key in the TPM2 chip
- can decrypt the volume key and activate the volume. Usecase: systemd-syscfg
- for a central orchestrator to generate syscfg images securely that can only
+ can decrypt the volume key and activate the volume. Usecase: systemd-confext
+ for a central orchestrator to generate confext images securely that can only
be activated on one specific host (which can be used for installing a bunch
of creds in /etc/credstore/ for example). Extending on this: allow binding
LUKS2 TPM based encryption also to the TPM2 internal clock. Net result:
- prepare a syscfg image that can only be activated on a specific host that
- runs a specific software in a specific time window. syscfg would be
+ prepare a confext image that can only be activated on a specific host that
+ runs a specific software in a specific time window. confext would be
automatically invalidated outside of it.
* maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of
this: have the report tool upload these reports every 3min somewhere. Then
have the orchestrator collect these reports centrally over a 3min time
window, and use them to determine what which node should now start/stop what,
- and generate a small syscfg for each node, that uses Uphold= to pin services
- on each node. The syscfg would be encrypted using the asymmetric encryption
+ and generate a small confext for each node, that uses Uphold= to pin services
+ on each node. The confext would be encrypted using the asymmetric encryption
proposed above, so that it can only be activated on the specific host, if the
software is in a good state, and within a specific time frame. Then run a
loop on each node that sends report to orchestrator and then sysupdate to
- update syscfg. Orchestrator would be stateless, i.e. operate on desired
+ update confext. Orchestrator would be stateless, i.e. operate on desired
config and collected reports in the last 3min time window only, and thus can
be trivially scaled up since all instances of the orchestrator should come to
the same conclusions given the same inputs of reports/desired workload info.
Could also be used to deliver Wireguard secrets and thus to clients, thus
- permitting zero-trust networking: secrets are rolled over via syscfg updates,
+ permitting zero-trust networking: secrets are rolled over via confext updates,
and via the time window TPM logic invalidated if node doesn't keep itself
updated, or becomes corrupted in some way.
keyring, so that the kernel does this validation for us for verity and kernel
modules
-* for systemd-syscfg: add a tool that can generate suitable DDIs with verity +
+* for systemd-confext: add a tool that can generate suitable DDIs with verity +
sig using squashfs-tools-ng's library. Maybe just systemd-repart called under
a new name with a built-in config?
-* gpt-auto: generate mount units that reference partitions via
- /dev/disk/by-diskseq/… so that they can't be swapped out behind our back.
-
* lock down acceptable encrypted credentials at boot, via simple allowlist,
maybe on kernel command line:
systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked
* chase(): refuse resolution if trailing slash is specified on input,
but final node is not a directory
-* chase(): add new flag that simply refuses all symlink use in a path,
- then use that for accessing XBOOTLDR/ESP
-
* document in boot loader spec that symlinks in XBOOTLDR/ESP are not OK even if
non-VFAT fs is used.
* pick up creds from EFI vars
+* Add and pickup tpm2 metadata for creds structure.
+
* sd-boot: we probably should include all BootXY EFI variable defined boot
entries in our menu, and then suppress ourselves. Benefit: instant
compatibility with all other OSes which register things there, in particular
* implement varlink introspection
-* we should probably drop all use of prefix_roota() and friends, and use
- chase() instead
-
* make persistent restarts easier by adding a new setting OpenPersistentFile=
or so, which allows opening one or more files that is "persistent" across
service restarts, hot reboot, cold reboots (depending on configuration): the
not unprivileged code.
* given that /etc/ssh/ssh_config.d/ is a thing now, ship a drop-in for that
- that hooks up userbdctl ssh-key stuff.
+ that hooks up userdbctl ssh-key stuff.
* maybe add support for binding and connecting AF_UNIX sockets in the file
system outside of the 108ch limit. When connecting, open O_PATH fd to socket
signal for setting service log level, that carries the level via the
sigqueue() data parameter. Enable this via unit file setting.
-* firstboot: maybe just default to C.UTF-8 locale if nothing is set, so that we
- don't query this unnecessarily in entirely uninitialized
- containers. (i.e. containers with empty /etc).
-
* sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services,
then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically
fixed to "2", i.e. the official host cid) and the expected guest cid, for the
directly to host service manager.
* maybe write a tool that binds an AF_VFSOCK socket, then invokes qemu,
- extending the command line to enable vsock on the VM, and using fw_cfg to
- configure socket address.
+ extending the command line to enable vsock on the VM, and using SMBIOS
+ credentials to configure socket address.
* sd-boot: add menu item for shutdown? or hotkey?
* sd-boot: maybe add support for embedding the various auxiliary resources we
look for right in the sd-boot binary. i.e. take inspiration from sd-stub
- logic: allow combining sd-boot via objcopy with kernels to enumerate, .conf
+ logic: allow combining sd-boot via ukify with kernels to enumerate, .conf
files, drivers, keys to enroll and so on. Then, add whatever we find that way
to the menu. Usecase: allow building a single PE image you can boot into via
UEFI HTTP boot.
* sysext: measure all activated sysext into a TPM PCR
-* maybe add a "syscfg" concept, that is almost entirely identical to "sysext",
- but operates on /etc/ instead of /usr/ and /opt/. Use case would be: trusted,
- authenticated, atomic, additive configuration management primitive: drop in a
- configuration bundle, and activate it, so that it is instantly visible,
- comprehensively.
-
* systemd-dissect: show available versions inside of a disk image, i.e. if
multiple versions are around of the same resource, show which ones. (in other
words: show partition labels).
* kernel-install:
- add --all switch for rerunning kernel-install for all installed kernels
- - maybe add env var that shortcuts kernel-install for installers that want to
- call it at the end only
* doc: prep a document explaining resolved's internal objects, i.e. Query
vs. Question vs. Transaction vs. Stream and so on.
* introduce a new group to own TPM devices
-* cyptsetup: add option for automatically removing empty password slot on boot
+* cryptsetup: add option for automatically removing empty password slot on boot
* cryptsetup: optionally, when run during boot-up and password is never
entered, and we are on battery power (or so), power off machine again
* busctl: maybe expose a verb "ping" for pinging a dbus service to see if it
exists and responds.
-* Maybe add a separate GPT partition type to the discoverable partition spec
- for "hibernate" partitions, that are exactly like swap partitions but only
- activated right before hibernation and thus never used for regular swapping.
-
* socket units: allow creating a udev monitor socket with ListenDevices= or so,
with matches, then activate app through that passing socket over
* teach parse_timestamp() timezones like the calendar spec already knows it
-* beef up hibernation to optionally do swapon/swapoff immediately before/after
- the hibernation
-
* beef up s2h to implement a battery watch loop: instead of entering
hibernation unconditionally after coming back from resume make a decision
based on the battery load level: if battery level is above a specific
* deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char
-* add a new RuntimeDirectoryPreserve= mode that defines a similar lifecycle for
- the runtime dir as we maintain for the fdstore: i.e. keep it around as long
- as the unit is running or has a job queued.
-
* support projid-based quota in machinectl for containers
* add a way to lock down cgroup migration: a boolean, which when set for a unit
* mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units.
-* systemd-firstboot: make sure to always use chase() before
- reading/writing files
-
-* firstboot: make it useful to be run immediately after yum --installroot to set up a machine. (most specifically, make --copy-root-password work even if /etc/passwd already exists
-
* EFI:
- honor language efi variables for default language selection (if there are any?)
- honor timezone efi variables for default timezone selection (if there are any?)
- check if we can make journalctl by default use --follow mode inside of less if called without args?
- maybe add API to send pairs of iovecs via sd_journal_send
- journal: add a setgid "systemd-journal" utility to invoke from libsystemd-journal, which passes fds via STDOUT and does PK access
- - journactl: support negative filtering, i.e. FOOBAR!="waldo",
+ - journalctl: support negative filtering, i.e. FOOBAR!="waldo",
and !FOOBAR for events without FOOBAR.
- journal: store timestamp of journal_file_set_offline() in the header,
so it is possible to display when the file was last synced.
properties as JSON, similar to busctl's new JSON output. In contrast to that
it should skip the variant type string though.
-* add an explicit "vertical" mode to format-table, so that "systemctl
- status"-like outputs (i.e. with a series of field names left and values
- right) become genuine first class citizens, and we gain automatic, sane JSON
- output for them.
-
* Add a "systemctl list-units --by-slice" mode or so, which rearranges the
output of "systemctl list-units" slightly by showing the tree structure of
the slices, and the units attached to them.
--- /dev/null
+---
+title: systemd Coredump Handling
+category: Concepts
+layout: default
+SPDX-License-Identifier: LGPL-2.1-or-later
+---
+
+# systemd Coredump Handling
+
+## Support in the Service Manager (PID 1)
+
+The systemd service manager natively provides coredump handling functionality,
+as implemented by the Linux kernel. Specifically, PID 1 provides the following
+functionality:
+
+1. During very early boot it will raise the
+ [`LIMIT_CORE`](https://man7.org/linux/man-pages/man2/getrlimit.2.html)
+ resource limit for itself to infinity (and thus implicitly also all its
+ children). This removes any limits on the size of generated coredumps, for
+ all invoked processes, from earliest boot on. (The Linux kernel sets the
+ limit to 0 by default.)
+
+2. At the same time it will turn off coredump handling in the kernel by writing
+ `|/bin/false` into `/proc/sys/kernel/core_pattern` (also known as the
+ "`kernel.core_pattern` sysctl"; see
+ [core(5)](https://man7.org/linux/man-pages/man5/core.5.html) for
+ details). This means that coredumps are not actually processed. (The Linux
+ kernel sets the pattern to `core` by default, so that coredumps are written
+ to the current working directory of the crashing process.)
+
+Net effect: after PID1 has started and performed this setup coredumps are
+disabled, but by means of the the `kernel.core_pattern` sysctl rather than by
+size limit. This is generally preferable, since the pattern can be updated
+trivially at the right time to enable coredumping once the system is ready,
+taking comprehensive effect on all userspace. (Or to say this differently:
+disabling coredumps via the size limit is problematic, since it cannot easily
+be undone without iterating through all already running processes once the
+system is ready for coredump handling.)
+
+Processing of core dumps may be enabled at the appropriate time by updating the
+`kernel.core_pattern` sysctl. Only coredumps that happen later will be
+processed.
+
+During the final shutdown phase the `kernel.core_pattern` sysctl is updated
+again to `|/bin/false`, disabling coredump support again, should it have been
+enabled in the meantime.
+
+This means coredump handling is generally not available during earliest boot
+and latest shutdown, reflecting the fact that storage is typically not
+available in these environments, and many other facilities are missing too that
+are required to collect and process a coredump successfully.
+
+## `systemd-coredump` Handler
+
+The systemd suite provides a coredump handler
+[`systemd-coredump`](https://www.freedesktop.org/software/systemd/man/systemd-coredump.html)
+which can be enabled at build-time. It is activated during boot via the
+`/usr/lib/sysctl.d/50-coredump.conf` drop-in file for
+`systemd-sysctl.service`. It registers the `systemd-coredump` tool as
+`kernel.core_pattern` sysctl.
+
+`systemd-coredump` is implemented as socket activated service: when the kernel
+invokes the userspace coredump handler, the received coredump file descriptor
+is immediately handed off to the socket activated service
+`systemd-coredump@.service` via the `systemd-coredump.socket` socket unit. This
+means the coredump handler runs for a very short time only, and the potentially
+*heavy* and security sensitive coredump processing work is done as part of the
+specified service unit, and thus can take benefit of regular service resource
+management and sandboxing.
+
+The `systemd-coredump` handler will extract a backtrace and [ELF packaging
+metadata](https://systemd.io/ELF_PACKAGE_METADATA) from any coredumps it
+receieves and log both. The information about coredumps stored in the journal
+can be enumerated and queried with the
+[`coredumpctl`](https://www.freedesktop.org/software/systemd/man/coredumpctl.html)
+tool, for example for directly invoking a debugger such as `gdb` on a collected
+coredump.
+
+The handler writes coredump files to `/var/lib/systemd/coredump/`. Old files
+are cleaned up periodically by
+[`systemd-tmpfiles(8)`](https://www.freedesktop.org/software/systemd/man/systemd-tmpfiles.html).
+
+## User Experience
+
+With the above, any coredumps generated on the system are by default collected
+and turned into logged events — except during very early boot and late
+shutdown. Individual services, processes or users can opt-out of coredump
+collection, by setting `LIMIT_CORE` to 0 (or alternatively invoke
+[`PR_SET_DUMPABLE`](https://man7.org/linux/man-pages/man2/prctl.2.html)). The
+resource limit can be set freely by daemons/processes/users to arbitrary
+values, which the coredump handler will respect. The `coredumpctl` tool may be
+used to further analyze/debug coredumps.
+
+## Alternative Coredump Handlers
+
+While we recommend usage of the `systemd-coredump` handler, it's fully
+supported to use alternative coredump handlers instead. A similar
+implementation pattern is recommended. Specifically:
+
+1. Use a `sysctl.d/` drop-in to register your handler with the kernel. Make
+ sure to include the `%c` specifier in the pattern (which reflects the
+ crashing process' `RLIMIT_CORE`) and act on it: limit the stored coredump
+ file to the specified limit.
+
+2. Do not do heavy processing directly in the coredump handler. Instead,
+ quickly pass off the kernel's coredump file descriptor to an
+ auxiliary service running as service under the service manager, so that it
+ can be done under supervision, sandboxing and resource management.
+
+Note that at any given time only a single handler can be enabled, i.e. the
+`kernel.core_pattern` sysctl cannot reference multiple executables.
+
+## Packaging
+
+It might make sense to split `systemd-coredump` into a separate distribution
+package. If doing so, make sure that `/usr/lib/sysctl.d/50-coredump.conf` and
+the associated service and socket units are also added to the split off package.
+
+Note that in a scenario where `systemd-coredump` is split out and not
+installed, coredumping is turned off during the entire runtime of the system —
+unless an alternative handler is installed, or behaviour is manually reverted
+to legacy style handling (see below).
+
+## Restoring Legacy Coredump Handling
+
+The default policy of the kernel to write coredumps into the current working
+directory of the crashing process is considered highly problematic by many,
+including by the systemd maintainers. Nonetheless, if users locally want to
+return to this behaviour, two changes must be made (followed by a reboot):
+
+```console
+$ mkdir -p /etc/sysctl.d
+$ cat >/etc/sysctl.d/50-coredump.conf <<EOF
+# Party like it's 1995!
+kernel.core_pattern=core
+EOF
+```
+
+and
+
+```console
+$ mkdir -p /etc/systemd/system.conf.d
+$ cat >/etc/systemd/system.conf.d/50-coredump.conf <<EOF
+[Manager]
+DefaultLimitCORE=0:infinity
+EOF
+```
paths. Only "real" file systems and directories that only contain "real" file
systems as submounts should be used. Do not specify API file systems such as
`/proc/` or `/sys/` here, or hierarchies that have them as submounts. In
- particular, do not specify the root directory `/` here.
+ particular, do not specify the root directory `/` here. Similarly,
+ `$SYSTEMD_CONFEXT_HIERARCHIES` works for confext images and supports the
+ systemd-confext multi-call functionality of sysext.
`systemd-tmpfiles`:
journal. Note that journal files in compact mode are limited to 4G to allow use of
32-bit offsets. Enabled by default.
+* `$SYSTEMD_JOURNAL_COMPRESS` – Takes a boolean, or one of the compression
+ algorithms "XZ", "LZ4", and "ZSTD". If enabled, the default compression
+ algorithm set at compile time will be used when opening a new journal file.
+ If disabled, the journal file compression will be disabled. Note that the
+ compression mode of existing journal files are not changed. To make the
+ specified algorithm takes an effect immediately, you need to explicitly run
+ `journalctl --rotate`.
+
`systemd-pcrphase`, `systemd-cryptsetup`:
* `$SYSTEMD_FORCE_MEASURE=1` — If set, force measuring of resources (which are
If you're hacking on the kernel in tandem with systemd, you can clone a kernel repository in mkosi.kernel/ in
the systemd repository, and mkosi will automatically build that kernel and install it into the final image.
To prevent the distribution's kernel from being installed (which isn't necessary since we're building our
-own kernel), you can add the following snippets to mkosi.default.d/20-local.conf:
+own kernel), you can add the following snippets to mkosi.conf.d/20-local.conf:
(This snippet is for Fedora, the list of packages will need to be changed for other distributions)
towards unexpected program termination as there are never files on disk that
need to be explicitly deleted.
-3. 🥇 Operate below a sub-directory of `/tmp/` and `/var/tmp/` you created, and
- take a BSD file lock ([`flock(dir_fd,
- LOCK_SH)`](https://man7.org/linux/man-pages/man2/flock.2.html)) on that
- sub-directory. This is particularly interesting when operating on more than
- a single file, or on file nodes that are not plain regular files, for
- example when extracting a tarball to a temporary directory. The ageing
- algorithm will skip all directories (and everything below them) that are
- locked through a BSD file lock. As BSD file locks are automatically released
+3. 🥇 Take an exclusive or shared BSD file lock ([`flock()`](
+ https://man7.org/linux/man-pages/man2/flock.2.html)) on files and directories
+ you don't want to be removed. This is particularly interesting when operating
+ on more than a single file, or on file nodes that are not plain regular files,
+ for example when extracting a tarball to a temporary directory. The ageing
+ algorithm will skip all directories (and everything below them) and files that
+ are locked through a BSD file lock. As BSD file locks are automatically released
when the file descriptor they are taken on is closed, and all file
descriptors opened by a process are implicitly closed when it exits, this is
a robust mechanism that ensures all temporary files are subject to ageing
modification/access times, as extracted files are otherwise immediately
candidates for deletion by the ageing algorithm. The
[`flock`](https://man7.org/linux/man-pages/man1/flock.1.html) tool of the
- `util-linux` packages makes this concept available to shell scripts. Note
- that `systemd-tmpfiles` only checks for BSD file locks on directories, locks
- on other types of file nodes (including regular files) are not considered.
+ `util-linux` packages makes this concept available to shell scripts.
4. Keep the access time of all temporary files created current. In regular
intervals, use `utimensat()` or a related call to update the access time
## mkosi
-To build with sanitizers in mkosi, create a file 20-local.conf in mkosi.default.d/ and add the following
+To build with sanitizers in mkosi, create a file 20-local.conf in mkosi.conf.d/ and add the following
contents:
```
sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-103:*
ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+# Toshiba Encore WT10A-102 tablet
+sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-102:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
#########################################
# Trekstor
#########################################
ninja -C "@BUILD_ROOT@" "$target"
fullname="@BUILD_ROOT@/$target"
-redirect="$(test -f "$fullname" && readlink "$fullname" || :)"
+if [ -f "$fullname" ]; then
+ redirect="$(readlink "$fullname" || :)"
+else
+ redirect=""
+fi
if [ -n "$redirect" ]; then
ninja -C "@BUILD_ROOT@" "man/$redirect"
</varlistentry>
</variablelist>
- <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> and <varname>$KERNEL_INSTALL_UKI_GENERATOR</varname>
- are set for plugins to select the initrd and/or UKI generator. This may be configured as
+ <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> and <varname>$KERNEL_INSTALL_UKI_GENERATOR</varname>
+ are set for plugins to select the initrd and/or UKI generator. This may be configured as
<varname>initrd_generator=</varname> and <varname>uki_generator=</varname> in <filename>install.conf</filename>, see below.</para>
<para><varname>$KERNEL_INSTALL_STAGING_AREA</varname> is set for plugins to a path to a directory.
<varname>MACHINE_ID=</varname>,
<varname>BOOT_ROOT=</varname>,
<varname>layout=</varname>,
- <varname>initrd_generator=</varname>.
+ <varname>initrd_generator=</varname>,
+ <varname>uki_generator=</varname>.
See the Environment variables section above for details.</para>
</listitem>
</varlistentry>
LookupDynamicUserByUID(in u uid,
out s name);
GetDynamicUsers(out a(us) users);
+ DumpUnitFileDescriptorStore(in s name,
+ out a(suuutuusu) entries);
signals:
UnitNew(s id,
o unit);
<variablelist class="dbus-method" generated="True" extra-ref="GetDynamicUsers()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="DumpUnitFileDescriptorStore()"/>
+
<variablelist class="dbus-signal" generated="True" extra-ref="UnitNew"/>
<variablelist class="dbus-signal" generated="True" extra-ref="UnitRemoved"/>
<ulink url="https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface">New Control Group
Interface</ulink> for more information how to make use of this functionality for resource control
purposes.</para>
+
+ <para><function>DumpUnitFileDescriptorStore()</function> returns an array with information about the
+ file descriptors currently in the file descriptor store of the specified unit. This call is equivalent
+ to <function>DumpFileDescriptorStore()</function> on the
+ <interfacename>org.freedesktop.systemd1.Service</interfacename>. For further details, see below.</para>
</refsect2>
<refsect2>
in b read_only,
in b mkdir,
in a(ss) options);
+ DumpFileDescriptorStore(out a(suuutuusu) entries);
GetProcesses(out a(sus) processes);
AttachProcesses(in s subcgroup,
in au pids);
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t RestartUSecMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
- readonly t RestartUSecCurrent = ...;
+ readonly t RestartUSecNext = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t TimeoutStartUSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
<!--property RestartUSecMax is not documented!-->
- <!--property RestartUSecCurrent is not documented!-->
+ <!--property RestartUSecNext is not documented!-->
<!--property TimeoutStartFailureMode is not documented!-->
<variablelist class="dbus-method" generated="True" extra-ref="MountImage()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="DumpFileDescriptorStore()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="GetProcesses()"/>
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
<variablelist class="dbus-property" generated="True" extra-ref="RestartUSecMax"/>
- <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecCurrent"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecNext"/>
<variablelist class="dbus-property" generated="True" extra-ref="TimeoutStartUSec"/>
directly on the Manager object has the advantage of not requiring a <function>GetUnit()</function> call
to get the unit object for a specific unit name. Calling the methods on the Manager object is hence a round
trip optimization.</para>
+
+ <para><function>DumpFileDescriptorStore()</function> returns an array with information about the file
+ descriptors currently in the file descriptor store of the service. Each entry consists of a file
+ descriptor name (i.e. the <varname>FDNAME=</varname> field), the file descriptor inode type and access
+ mode as integer (i.e. a <type>mode_t</type> value, flags such as <constant>S_IFREG</constant>,
+ <constant>S_IRUSR</constant>, …), the major and minor numbers of the device number of the file system
+ backing the inode of the file descriptor, the inode number, the major and minor numbers of the device
+ number if this refers to a character or block device node, a file system path pointing to the inode,
+ and the file descriptor flags (i.e. <constant>O_RDWR</constant>, <constant>O_RDONLY</constant>,
+ …).</para>
</refsect2>
<refsect2>
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>CONFEXT_LEVEL=</varname></term>
+
+ <listitem><para>Semantically the same as <varname>SYSEXT_LEVEL=</varname> but for confext images.
+ See <filename>/etc/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename>
+ for more information.</para>
+
+ <para>Examples: <literal>CONFEXT_LEVEL=2</literal>, <literal>CONFEXT_LEVEL=15.14</literal>.
+ </para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>SYSEXT_SCOPE=</varname></term>
<listitem><para>Takes a space-separated list of one or more of the strings
but not to initrd environments.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>CONFEXT_SCOPE=</varname></term>
+
+ <listitem><para>Semantically the same as <varname>SYSEXT_SCOPE=</varname> but for confext images.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>PORTABLE_PREFIXES=</varname></term>
<listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
--- /dev/null
+/* SPDX-License-Identifier: MIT-0 */
+
+/* This is equivalent to:
+ * busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ * org.freedesktop.systemd1.Manager GetUnitByPID $$
+ *
+ * Compile with 'cc print-unit-path-call-method.c -lsystemd'
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
+#define DESTINATION "org.freedesktop.systemd1"
+#define PATH "/org/freedesktop/systemd1"
+#define INTERFACE "org.freedesktop.systemd1.Manager"
+#define MEMBER "GetUnitByPID"
+
+static int log_error(int error, const char *message) {
+ errno = -error;
+ fprintf(stderr, "%s: %m\n", message);
+ return error;
+}
+
+int main(int argc, char **argv) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_error(r, "Failed to acquire bus");
+
+ r = sd_bus_call_method(bus, DESTINATION, PATH, INTERFACE, MEMBER, &error, &reply, "u", (unsigned) getpid());
+ if (r < 0)
+ return log_error(r, MEMBER " call failed");
+
+ const char *ans;
+ r = sd_bus_message_read(reply, "o", &ans);
+ if (r < 0)
+ return log_error(r, "Failed to read reply");
+
+ printf("Unit path is \"%s\".\n", ans);
+
+ return 0;
+}
/* SPDX-License-Identifier: MIT-0 */
-#include <errno.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-
-#include <systemd/sd-bus.h>
-#define _cleanup_(f) __attribute__((cleanup(f)))
-
/* This is equivalent to:
* busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
* org.freedesktop.systemd1.Manager GetUnitByPID $$
*
- * Compile with 'cc -lsystemd print-unit-path.c'
+ * Compile with 'cc print-unit-path.c -lsystemd'
*/
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
#define DESTINATION "org.freedesktop.systemd1"
#define PATH "/org/freedesktop/systemd1"
#define INTERFACE "org.freedesktop.systemd1.Manager"
return error;
}
-static int print_unit_path(sd_bus *bus) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+int main(int argc, char **argv) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_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_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
int r;
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_error(r, "Failed to acquire bus");
+
r = sd_bus_message_new_method_call(bus, &m,
DESTINATION, PATH, INTERFACE, MEMBER);
if (r < 0)
r = sd_bus_call(bus, m, -1, &error, &reply);
if (r < 0)
- return log_error(r, "Call failed");
+ return log_error(r, MEMBER " call failed");
const char *ans;
r = sd_bus_message_read(reply, "o", &ans);
return 0;
}
-
-int main(int argc, char **argv) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- int r;
-
- r = sd_bus_open_system(&bus);
- if (r < 0)
- return log_error(r, "Failed to acquire bus");
-
- print_unit_path(bus);
-}
'SD_BUS_ERROR_INCONSISTENT_MESSAGE',
'SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED',
'SD_BUS_ERROR_INVALID_ARGS',
+ 'SD_BUS_ERROR_INVALID_FILE_CONTENT',
'SD_BUS_ERROR_INVALID_SIGNATURE',
'SD_BUS_ERROR_IO_ERROR',
'SD_BUS_ERROR_LIMITS_EXCEEDED',
'SD_BUS_ERROR_NO_NETWORK',
'SD_BUS_ERROR_NO_REPLY',
'SD_BUS_ERROR_NO_SERVER',
+ 'SD_BUS_ERROR_OBJECT_PATH_IN_USE',
'SD_BUS_ERROR_PROPERTY_READ_ONLY',
+ 'SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN',
'SD_BUS_ERROR_SERVICE_UNKNOWN',
+ 'SD_BUS_ERROR_TIMED_OUT',
'SD_BUS_ERROR_TIMEOUT',
'SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN',
'SD_BUS_ERROR_UNKNOWN_INTERFACE',
'systemd-suspend-then-hibernate.service'],
''],
['systemd-sysctl.service', '8', ['systemd-sysctl'], ''],
- ['systemd-sysext', '8', ['systemd-sysext.service'], ''],
+ ['systemd-sysext',
+ '8',
+ ['systemd-confext', 'systemd-confext.service', 'systemd-sysext.service'],
+ ''],
['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf',
'5',
<refname>SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN</refname>
<refname>SD_BUS_ERROR_INVALID_SIGNATURE</refname>
<refname>SD_BUS_ERROR_INCONSISTENT_MESSAGE</refname>
+ <refname>SD_BUS_ERROR_TIMED_OUT</refname>
<refname>SD_BUS_ERROR_MATCH_RULE_NOT_FOUND</refname>
<refname>SD_BUS_ERROR_MATCH_RULE_INVALID</refname>
<refname>SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED</refname>
+ <refname>SD_BUS_ERROR_INVALID_FILE_CONTENT</refname>
+ <refname>SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN</refname>
+ <refname>SD_BUS_ERROR_OBJECT_PATH_IN_USE</refname>
<refpurpose>Standard D-Bus error names</refpurpose>
</refnamediv>
<funcsynopsis>
<funcsynopsisinfo>#include <systemd/sd-bus.h></funcsynopsisinfo>
-<funcsynopsisinfo>#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed"
-#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory"
-#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown"
-#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner"
-#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply"
-#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError"
-#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress"
-#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported"
-#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded"
-#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied"
-#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed"
-#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer"
-#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout"
-#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork"
-#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse"
-#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected"
-#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs"
-#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound"
-#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists"
-#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod"
-#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
-#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface"
-#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
-#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly"
-#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
-#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature"
-#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage"
-#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound"
-#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid"
+ <funcsynopsisinfo>
+#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed"
+#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory"
+#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown"
+#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner"
+#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply"
+#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError"
+#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress"
+#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported"
+#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded"
+#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied"
+#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed"
+#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer"
+#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout"
+#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork"
+#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse"
+#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected"
+#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs"
+#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound"
+#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists"
+#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod"
+#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
+#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface"
+#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
+#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly"
+#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
+#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature"
+#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage"
+#define SD_BUS_ERROR_TIMED_OUT "org.freedesktop.DBus.Error.TimedOut"
+#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound"
+#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid"
#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \
- "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"</funcsynopsisinfo>
-
+ "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"
+#define SD_BUS_ERROR_INVALID_FILE_CONTENT "org.freedesktop.DBus.Error.InvalidFileContent"
+#define SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN \
+ "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"
+#define SD_BUS_ERROR_OBJECT_PATH_IN_USE "org.freedesktop.DBus.Error.ObjectPathInUse"
+ </funcsynopsisinfo>
</funcsynopsis>
</refsynopsisdiv>
<xi:include href="libsystemd-pkgconfig.xml" />
+ <refsect1>
+ <title>Examples</title>
+
+ <example>
+ <title>Make a call to a D-Bus method that takes a single parameter</title>
+
+ <programlisting><xi:include href="print-unit-path-call-method.c" parse="text" /></programlisting>
+ <para>This defines a minimally useful program that will open a connection to the bus, call a method,
+ wait for the reply, and finally extract and print the answer. It does error handling and proper
+ memory management.</para>
+ </example>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
with <function>sd_bus_message_enter_container()</function>. It behaves mostly the same as
<function>sd_bus_message_close_container()</function>. Note that
<function>sd_bus_message_exit_container()</function> may only be called after iterating through all
- members of the container, i.e. reading or skipping them. Use
+ members of the container, i.e. reading or skipping over them. Use
<citerefentry><refentrytitle>sd_bus_message_skip</refentrytitle><manvolnum>3</manvolnum></citerefentry>
to skip over fields of a container in order to be able to exit the container with
<function>sd_bus_message_exit_container()</function> without reading all members.</para>
<constant>NULL</constant> or <parameter>type</parameter> is invalid.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><constant>-EBADMSG</constant></term>
+
+ <listitem><para>Message <parameter>m</parameter> has invalid structure.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>-ENXIO</constant></term>
+
+ <listitem><para>Message <parameter>m</parameter> does not have a container of type
+ <parameter>type</parameter> at the current position, or the contents do not match
+ <parameter>contents</parameter>.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><constant>-EPERM</constant></term>
<arg choice="plain">malloc</arg>
<arg choice="opt" rep="repeat"><replaceable>D-BUS SERVICE</replaceable></arg>
</cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">fdstore</arg>
+ <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
+ </cmdsynopsis>
</refsynopsisdiv>
<refsect1>
}
</programlisting>
</example>
+ </refsect2>
+
+ <refsect2>
+ <title><command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command></title>
+
+ <para>Lists the current contents of the specified service unit's file descriptor store. This shows
+ names, inode types, device numbers, inode numbers, paths and open modes of the open file
+ descriptors. The specified units must have <varname>FileDescriptorStoreMax=</varname> enabled, see
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para>
+
+ <example>
+ <title>Table output</title>
+ <programlisting>$ systemd-analyze fdstore systemd-journald.service
+FDNAME TYPE DEVNO INODE RDEVNO PATH FLAGS
+stored sock 0:8 4218620 - socket:[4218620] ro
+stored sock 0:8 4213198 - socket:[4213198] ro
+stored sock 0:8 4213190 - socket:[4213190] ro
+…</programlisting>
+ </example>
+ <para>Note: the "DEVNO" column refers to the major/minor numbers of the device node backing the file
+ system the file descriptor's inode is on. The "RDEVNO" column refers to the major/minor numbers of the
+ device node itself if the file descriptor refers to one. Compare with corresponding
+ <varname>.st_dev</varname> and <varname>.st_rdev</varname> fields in <type>struct stat</type> (see
+ <citerefentry
+ project='man-pages'><refentrytitle>stat</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+ details). The listed inode numbers in the "INODE" column are on the file system indicated by
+ "DEVNO".</para>
</refsect2>
+
</refsect1>
<refsect1>
files. Some metadata is attached to core files in the form of extended attributes, so the core files are
useful for some purposes even without the full metadata available in the journal entry.</para>
+ <para>For further details see <ulink url="https://systemd.io/COREDUMP">systemd Coredump
+ Handling</ulink>.</para>
+
<refsect2>
<title>Invocation of <command>systemd-coredump</command></title>
<citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>core</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sysctl.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
- <citerefentry><refentrytitle>systemd-sysctl.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ <citerefentry><refentrytitle>systemd-sysctl.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <ulink url="https://systemd.io/COREDUMP">systemd Coredump Handling</ulink>
</para>
</refsect1>
</refentry>
<term><option>--discover</option></term>
<listitem><para>Show a list of DDIs in well-known directories. This will show machine, portable
- service and system extension disk images in the usual directories
+ service and system/configuration extension disk images in the usual directories
<filename>/usr/lib/machines/</filename>, <filename>/usr/lib/portables/</filename>,
- <filename>/usr/lib/extensions/</filename>, <filename>/var/lib/machines/</filename>,
+ <filename>/usr/lib/confexts/</filename>, <filename>/var/lib/machines/</filename>,
<filename>/var/lib/portables/</filename>, <filename>/var/lib/extensions/</filename> and so
on.</para></listitem>
</varlistentry>
<literal>root</literal> user instead of overwriting the entire file.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--reset</option></term>
+
+ <listitem><para>If specified, all existing files that are configured by
+ <command>systemd-firstboot</command> are removed. Note that the files are removed regardless of
+ whether they'll be configured with a new value or not. This operation ensures that the next boot of
+ the image will be considered a first boot, and <command>systemd-firstboot</command> will prompt again
+ to configure each of the removed files.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--delete-root-password</option></term>
<variablelist>
<varlistentry>
+ <term><option>-o <replaceable>FILE</replaceable></option></term>
<term><option>--output=<replaceable>FILE</replaceable></option></term>
<listitem><para>Will write to this journal file. The filename
</varlistentry>
<varlistentry>
+ <term><option>-o <replaceable>DIR</replaceable></option></term>
<term><option>--output=<replaceable>DIR</replaceable></option></term>
<listitem><para>Will create journal files underneath directory
escaped as <literal>\;</literal>.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--fd=</option></term>
+
+ <listitem><para>Send a file descriptor along with the notification message. This is useful when
+ invoked in services that have the <varname>FileDescriptorStoreMax=</varname> setting enabled, see
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details. The specified file descriptor must be passed to <command>systemd-notify</command> when
+ invoked. This option may be used multiple times to pass multiple file descriptors in a single
+ notification message.</para>
+
+ <para>To use this functionality from a <command>bash</command> shell, use an expression like the following:</para>
+ <programlisting>systemd-notify --fd=4 --fd=5 4</some/file 5</some/other/file</programlisting></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--fdname=</option></term>
+
+ <listitem><para>Set a name to assign to the file descriptors passed via <option>--fd=</option> (see
+ above). This controls the <literal>FDNAME=</literal> field. This setting may only be specified once,
+ and applies to all file descriptors passed. Invoke this tool multiple times in case multiple file
+ descriptors with different file descriptor names shall be submitted.</para></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
<refnamediv>
<refname>systemd-sysext</refname>
<refname>systemd-sysext.service</refname>
+ <refname>systemd-confext</refname>
+ <refname>systemd-confext.service</refname>
<refpurpose>Activates System Extension Images</refpurpose>
</refnamediv>
<para><literallayout><filename>systemd-sysext.service</filename></literallayout></para>
+ <cmdsynopsis>
+ <command>systemd-confext</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">COMMAND</arg>
+ </cmdsynopsis>
+
+ <para><literallayout><filename>systemd-confext.service</filename></literallayout></para>
+
</refsynopsisdiv>
<refsect1>
service manager supports via <option>RootDirectory=</option>/<option>RootImage=</option>. Similar to
them they may optionally carry Verity authentication information.</para>
- <para>System extensions are automatically looked for in the directories
- <filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename>,
- <filename>/var/lib/extensions/</filename>, <filename>/usr/lib/extensions/</filename> and
- <filename>/usr/local/lib/extensions/</filename>. The first two listed directories are not suitable for
+ <para>System extensions are searched for in the directories
+ <filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename> and
+ <filename>/var/lib/extensions/</filename>. The first two listed directories are not suitable for
carrying large binary images, however are still useful for carrying symlinks to them. The primary place
for installing system extensions is <filename>/var/lib/extensions/</filename>. Any directories found in
- these search directories are considered directory based extension images, any files with the
+ these search directories are considered directory based extension images; any files with the
<filename>.raw</filename> suffix are considered disk image based extension images.</para>
<para>During boot OS extension images are activated automatically, if the
The <filename>extension-release</filename> file follows the same format and semantics, and carries the same
content, as the <filename>os-release</filename> file of the OS, but it describes the resources carried
in the extension image.</para>
+
+ <para>The <command>systemd-confext</command> concept follows the same principle as the
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ functionality but instead of working on <filename>/usr</filename> and <filename>/opt</filename>,
+ <command>confext</command> will extend only <filename>/etc</filename>. Files and directories contained
+ in the confext images outside of the <filename>/etc/</filename> hierarchy are <emphasis>not</emphasis>
+ merged, and hence have no effect when included in the image. Formats for these images are of the
+ same as sysext images.</para>
+
+ <para>Confexts are looked for in the directories <filename>/run/confexts/</filename>,
+ <filename>/var/lib/confexts/</filename>, <filename>/usr/lib/confexts/</filename> and
+ <filename>/usr/local/lib/confexts/</filename>. The first two listed directories are not suitable for
+ carrying large binary images, however are still useful for carrying symlinks to them. The primary place
+ for installing system extensions is <filename>/var/lib/confexts/</filename>. Any directories found in
+ these search directories are considered directory based confext images, any files with the
+ <filename>.raw</filename> suffix are considered disk image based confext images.</para>
+
+ <para>Again, just like sysext images, the confext images will contain a
+ <filename>/etc/extension-release.d/extension-release.<replaceable>$name</replaceable></filename>
+ file, which must match the image name (with the usual escape hatch of xattr), and again with content
+ being one or more of <varname>ID=</varname>, <varname>VERSION_ID=</varname>, and
+ <varname>CONFEXT_LEVEL</varname>. Confext images will then be checked and matched against the
+ base OS layer.</para>
</refsect1>
<refsect1>
<filename>/usr/</filename> as if it was installed in the OS image itself.) This case works regardless if
the underlying host <filename>/usr/</filename> is managed as immutable disk image or is a traditional
package manager controlled (i.e. writable) tree.</para>
- </refsect1>
+
+ <para>For the confext case, the OSConfig project aims to perform runtime reconfiguration of OS services.
+ Sometimes, there is a need to swap certain configuration parameter values or restart only a specific
+ service without deployment of new code or a complete OS deployment. In other words, we want to be able
+ to tie the most frequently configured options to runtime updateable flags that can be changed without a
+ system reboot. This will help reduce servicing times when there is a need for changing the OS configuration.</para></refsect1>
<refsect1>
<title>Commands</title>
- <para>The following commands are understood:</para>
+ <para>The following commands are understood by both the sysext and confext concepts:</para>
<variablelist>
<varlistentry>
<term><option>status</option></term>
<listitem><para>When invoked without any command verb, or when <option>status</option> is specified
- the current merge status is shown, separately for both <filename>/usr/</filename> and
- <filename>/opt/</filename>.</para></listitem>
+ the current merge status is shown, separately (for both <filename>/usr/</filename> and
+ <filename>/opt/</filename> of sysext and for <filename>/etc/</filename> of confext).</para></listitem>
</varlistentry>
<varlistentry>
<listitem><para>Merges all currently installed system extension images into
<filename>/usr/</filename> and <filename>/opt/</filename>, by overmounting these hierarchies with an
<literal>overlayfs</literal> file system combining the underlying hierarchies with those included in
- the extension images. This command will fail if the hierarchies are already merged.</para></listitem>
+ the extension images. This command will fail if the hierarchies are already merged. For confext, the merge
+ happens into the <filename>/etc/</filename> directory instead.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>unmerge</option></term>
<listitem><para>Unmerges all currently installed system extension images from
- <filename>/usr/</filename> and <filename>/opt/</filename>, by unmounting the
- <literal>overlayfs</literal> file systems created by <option>merge</option>
+ <filename>/usr/</filename> and <filename>/opt/</filename> for sysext and <filename>/etc/</filename>,
+ for confext, by unmounting the <literal>overlayfs</literal> file systems created by <option>merge</option>
prior.</para></listitem>
</varlistentry>
mounted the existing <literal>overlayfs</literal> instance is unmounted temporarily, and then
replaced by a new version. This command is useful after installing/removing system extension images,
in order to update the <literal>overlayfs</literal> file system accordingly. If no system extensions
- are installed when this command is executed, the equivalent of <option>unmerge</option> is
- executed, without establishing any new <literal>overlayfs</literal> instance. Note that currently
- there's a brief moment where neither the old nor the new <literal>overlayfs</literal> file system is
- mounted. This implies that all resources supplied by a system extension will briefly disappear — even
- if it exists continuously during the refresh operation.</para></listitem>
+ are installed when this command is executed, the equivalent of <option>unmerge</option> is executed,
+ without establishing any new <literal>overlayfs</literal> instance.
+ Note that currently there's a brief moment where neither the old nor the new <literal>overlayfs</literal>
+ file system is mounted. This implies that all resources supplied by a system extension will briefly
+ disappear — even if it exists continuously during the refresh operation.</para></listitem>
</varlistentry>
<varlistentry>
<listitem><para>Operate relative to the specified root directory, i.e. establish the
<literal>overlayfs</literal> mount not on the top-level host <filename>/usr/</filename> and
- <filename>/opt/</filename> hierarchies, but below some specified root directory.</para></listitem>
+ <filename>/opt/</filename> hierarchies for sysext or <filename>/etc/</filename> for confext,
+ but below some specified root directory.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--force</option></term>
<listitem><para>When merging system extensions into <filename>/usr/</filename> and
- <filename>/opt/</filename>, ignore version incompatibilities, i.e. force merging regardless of
- whether the version information included in the extension images matches the host or
- not.</para></listitem>
+ <filename>/opt/</filename> for sysext and <filename>/etc/</filename> for confext,
+ ignore version incompatibilities, i.e. force merging regardless of
+ whether the version information included in the images matches the host or not.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
directories marked with <varname>D</varname> or
<varname>R</varname>, and files or directories themselves
marked with <varname>r</varname> or <varname>R</varname> are
- removed.</para></listitem>
+ removed unless an exclusive or shared BSD lock is taken on them (see <citerefentry
+ project='man-pages'><refentrytitle>flock</refentrytitle><manvolnum>2</manvolnum></citerefentry>).
+ </para></listitem>
</varlistentry>
<varlistentry>
<term><varname>Endpoint=</varname></term>
<listitem>
<para>Sets an endpoint IP address or hostname, followed by a colon, and then
- a port number. This endpoint will be updated automatically once to
+ a port number. IPv6 address must be in the square brackets. For example,
+ <literal>111.222.333.444:51820</literal> for IPv4 and <literal>[1111:2222::3333]:51820</literal>
+ for IPv6 address. This endpoint will be updated automatically once to
the most recent source IP address and port of correctly
authenticated packets from the peer at configuration time.</para>
</listitem>
<varlistentry>
<term><varname>ExecStart=</varname></term>
- <listitem><para>Commands with their arguments that are
- executed when this service is started. The value is split into
- zero or more command lines according to the rules described
- below (see section "Command Lines" below).
- </para>
+ <listitem><para>Commands that are executed when this service is started. The value is split into zero
+ or more command lines according to the rules described in the section "Command Lines" below.</para>
<para>Unless <varname>Type=</varname> is <option>oneshot</option>, exactly one command must be given. When
<varname>Type=oneshot</varname> is used, zero or more commands may be specified. Commands may be specified by
<varname>ExecStop=</varname> line set. (Services lacking both <varname>ExecStart=</varname> and
<varname>ExecStop=</varname> are not valid.)</para>
- <para>For each of the specified commands, the first argument must be either an absolute path to an executable
- or a simple file name without any slashes. Optionally, this filename may be prefixed with a number of special
- characters:</para>
-
- <table>
- <title>Special executable prefixes</title>
-
- <tgroup cols='2'>
- <colspec colname='prefix'/>
- <colspec colname='meaning'/>
-
- <thead>
- <row>
- <entry>Prefix</entry>
- <entry>Effect</entry>
- </row>
- </thead>
- <tbody>
- <row>
- <entry><literal>@</literal></entry>
- <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <literal>argv[0]</literal> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
- </row>
-
- <row>
- <entry><literal>-</literal></entry>
- <entry>If the executable path is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success.</entry>
- </row>
-
- <row>
- <entry><literal>:</literal></entry>
- <entry>If the executable path is prefixed with <literal>:</literal>, environment variable substitution (as described by the "Command Lines" section below) is not applied.</entry>
- </row>
-
- <row>
- <entry><literal>+</literal></entry>
- <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
- </row>
-
- <row>
- <entry><literal>!</literal></entry>
-
- <entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
- </row>
-
- <row>
- <entry><literal>!!</literal></entry>
-
- <entry>This prefix is very similar to <literal>!</literal>, however it only has an effect on systems lacking support for ambient process capabilities, i.e. without support for <varname>AmbientCapabilities=</varname>. It's intended to be used for unit files that take benefit of ambient capabilities to run processes with minimal privileges wherever possible while remaining compatible with systems that lack ambient capabilities support. Note that when <literal>!!</literal> is used, and a system lacking ambient capability support is detected any configured <varname>SystemCallFilter=</varname> and <varname>CapabilityBoundingSet=</varname> stanzas are implicitly modified, in order to permit spawned processes to drop credentials and capabilities themselves, even if this is configured to not be allowed. Moreover, if this prefix is used and a system lacking ambient capability support is detected <varname>AmbientCapabilities=</varname> will be skipped and not be applied. On systems supporting ambient capabilities, <literal>!!</literal> has no effect and is redundant.</entry>
- </row>
- </tbody>
- </tgroup>
- </table>
-
- <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
- <literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
- order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
- time. Note that these prefixes are also supported for the other command line settings,
- i.e. <varname>ExecStartPre=</varname>, <varname>ExecStartPost=</varname>, <varname>ExecReload=</varname>,
- <varname>ExecStop=</varname> and <varname>ExecStopPost=</varname>.</para>
-
<para>If more than one command is specified, the commands are
invoked sequentially in the order they appear in the unit
file. If one of the commands fails (and is not prefixed with
fully stopped and no job is queued or being executed for it. If this option is used,
<varname>NotifyAccess=</varname> (see above) should be set to open access to the notification socket
provided by systemd. If <varname>NotifyAccess=</varname> is not set, it will be implicitly set to
- <option>main</option>.</para></listitem>
+ <option>main</option>.</para>
+
+ <para>The <command>fdstore</command> command of
+ <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ may be used to list the current contents of a service's file descriptor store.</para>
+
+ <para>Note that the service manager will only pass file descriptors contained in the file descriptor
+ store to the service's own processes, never to other clients via IPC or similar. However, it does
+ allow unprivileged clients to query the list of currently open file descriptors of a
+ service. Sensitive data may hence be safely placed inside the referenced files, but should not be
+ attached to the metadata (e.g. included in filenames) of the stored file
+ descriptors.</para></listitem>
</varlistentry>
<varlistentry>
<para>The command to execute may contain spaces, but control characters are not allowed.</para>
+ <para>Each command may be prefixed with a number of special characters:</para>
+
+ <table>
+ <title>Special executable prefixes</title>
+
+ <tgroup cols='2'>
+ <colspec colname='prefix'/>
+ <colspec colname='meaning'/>
+
+ <thead>
+ <row>
+ <entry>Prefix</entry>
+ <entry>Effect</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <constant>argv[0]</constant> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
+ </row>
+
+ <row>
+ <entry><literal>-</literal></entry>
+ <entry>If the executable path is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success.</entry>
+ </row>
+
+ <row>
+ <entry><literal>:</literal></entry>
+ <entry>If the executable path is prefixed with <literal>:</literal>, environment variable substitution (as described by the "Command Lines" section below) is not applied.</entry>
+ </row>
+
+ <row>
+ <entry><literal>+</literal></entry>
+ <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
+ </row>
+
+ <row>
+ <entry><literal>!</literal></entry>
+
+ <entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
+ </row>
+
+ <row>
+ <entry><literal>!!</literal></entry>
+
+ <entry>This prefix is very similar to <literal>!</literal>, however it only has an effect on systems lacking support for ambient process capabilities, i.e. without support for <varname>AmbientCapabilities=</varname>. It's intended to be used for unit files that take benefit of ambient capabilities to run processes with minimal privileges wherever possible while remaining compatible with systems that lack ambient capabilities support. Note that when <literal>!!</literal> is used, and a system lacking ambient capability support is detected any configured <varname>SystemCallFilter=</varname> and <varname>CapabilityBoundingSet=</varname> stanzas are implicitly modified, in order to permit spawned processes to drop credentials and capabilities themselves, even if this is configured to not be allowed. Moreover, if this prefix is used and a system lacking ambient capability support is detected <varname>AmbientCapabilities=</varname> will be skipped and not be applied. On systems supporting ambient capabilities, <literal>!!</literal> has no effect and is redundant.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
+ <literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
+ order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
+ time.</para>
+
+ <para>For each command, the first argument must be either an absolute path to an executable or a simple
+ file name without any slashes. If the command is not a full (absolute) path, it will be resolved to a
+ full path using a fixed search path determined at compilation time. Searched directories include
+ <filename>/usr/local/bin/</filename>, <filename>/usr/bin/</filename>, <filename>/bin/</filename> on
+ systems using split <filename>/usr/bin/</filename> and <filename>/bin/</filename> directories, and their
+ <filename>sbin/</filename> counterparts on systems using split <filename>bin/</filename> and
+ <filename>sbin/</filename>. It is thus safe to use just the executable name in case of executables
+ located in any of the "standard" directories, and an absolute path must be used in other cases. Using an
+ absolute path is recommended to avoid ambiguity. Hint: this search path may be queried using
+ <command>systemd-path search-binaries-default</command>.</para>
+
<para>The command line accepts <literal>%</literal> specifiers as described in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
For this type of expansion, quotes are respected when splitting
into words, and afterwards removed.</para>
- <para>If the command is not a full (absolute) path, it will be resolved to a full path using a
- fixed search path determined at compilation time. Searched directories include
- <filename>/usr/local/bin/</filename>, <filename>/usr/bin/</filename>, <filename>/bin/</filename>
- on systems using split <filename>/usr/bin/</filename> and <filename>/bin/</filename>
- directories, and their <filename>sbin/</filename> counterparts on systems using split
- <filename>bin/</filename> and <filename>sbin/</filename>. It is thus safe to use just the
- executable name in case of executables located in any of the "standard" directories, and an
- absolute path must be used in other cases. Using an absolute path is recommended to avoid
- ambiguity. Hint: this search path may be queried using
- <command>systemd-path search-binaries-default</command>.</para>
-
<para>Example:</para>
<programlisting>Environment="ONE=one" 'TWO=two two'
<para>Example:</para>
+ <programlisting>Type=oneshot
+ExecStart=:echo $USER ; -false ; +:@true $TEST</programlisting>
+
+ <para>This will execute <command>/usr/bin/echo</command> with the literal argument
+ <literal>$USER</literal> (<literal>:</literal> suppresses variable expansion), and then
+ <command>/usr/bin/false</command> (the return value will be ignored because <literal>-</literal>
+ suppresses checking of the return value), and <command>/usr/bin/true</command> (with elevated privileges,
+ with <literal>$TEST</literal> as <constant>argv[0]</constant>).</para>
+
+ <para>Example:</para>
+
<programlisting>ExecStart=echo / >/dev/null & \; \
ls</programlisting>
<term><varname>ConditionControlGroupController=</varname></term>
<listitem><para>Check whether given cgroup controllers (e.g. <literal>cpu</literal>) are available
- for use on the system.</para>
+ for use on the system or whether the legacy v1 cgroup or the modern v2 cgroup hierarchy is used.
+ </para>
<para>Multiple controllers may be passed with a space separating them; in this case the condition
will only pass if all listed controllers are available for use. Controllers unknown to systemd are
- ignored. Valid controllers are <literal>cpu</literal>, <literal>cpuset</literal>,
- <literal>io</literal>, <literal>memory</literal>, and <literal>pids</literal>. Even if available in
- the kernel, a particular controller may not be available if it was disabled on the kernel command
- line with <varname>cgroup_disable=controller</varname>.</para></listitem>
+ ignored. Valid controllers are <literal>cpu</literal>, <literal>io</literal>,
+ <literal>memory</literal>, and <literal>pids</literal>. Even if available in the kernel, a
+ particular controller may not be available if it was disabled on the kernel command line with
+ <varname>cgroup_disable=controller</varname>.</para>
+
+ <para>Alternatively, two special strings <literal>v1</literal> and <literal>v2</literal> may be
+ specified (without any controller names). <literal>v2</literal> will pass if the unified v2 cgroup
+ hierarchy is used, and <literal>v1</literal> will pass if the legacy v1 hierarchy or the hybrid
+ hierarchy are used. Note that legacy or hybrid hierarchies have been deprecated. See
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ more information.</para>
+ </listitem>
</varlistentry>
<varlistentry>
# an hour ago in "/tmp/foo/bar", are subject to time-based cleanup.
d /tmp/foo/bar - - - - bmA:1h -</programlisting></para>
- <para>Note that while the aging algorithm is run a 'shared' BSD file lock (see <citerefentry
+ <para>Note that while the aging algorithm is run an exclusive BSD file lock (see <citerefentry
project='man-pages'><refentrytitle>flock</refentrytitle><manvolnum>2</manvolnum></citerefentry>) is
- taken on each directory the algorithm descends into (and each directory below that, and so on). If the
- aging algorithm finds a lock is already taken on some directory, it (and everything below it) is
- skipped. Applications may use this to temporarily exclude certain directory subtrees from the aging
- algorithm: the applications can take a BSD file lock themselves, and as long as they keep it aging of
- the directory and everything below it is disabled.</para>
+ taken on each directory/file the algorithm decides to remove. If the aging algorithm finds a lock (
+ shared or exclusive) is already taken on some directory/file, it (and everything below it) is skipped.
+ Applications may use this to temporarily exclude certain directory subtrees from the aging algorithm:
+ the applications can take a BSD file lock themselves, and as long as they keep it aging of the
+ directory/file and everything below it is disabled.</para>
</refsect2>
<refsect2>
tools/testing/selftests/bpf/config.x86_64 \
tools/testing/selftests/bpf/config
- make O="$BUILDDIR" -j "$(nproc)"
+ # Make sure systemd-boot boots this kernel and not the distro provided one by overriding the version.
+ make O="$BUILDDIR" VERSION=99 -j "$(nproc)"
- KERNEL_RELEASE=$(make O="$BUILDDIR" -s kernelrelease)
+ KERNEL_RELEASE=$(make O="$BUILDDIR" VERSION=99 -s kernelrelease)
mkdir -p "$DESTDIR/usr/lib/modules/$KERNEL_RELEASE"
- make O="$BUILDDIR" INSTALL_MOD_PATH="$DESTDIR/usr" modules_install
- make O="$BUILDDIR" INSTALL_PATH="$DESTDIR/usr/lib/modules/$KERNEL_RELEASE" install
+ make O="$BUILDDIR" VERSION=99 INSTALL_MOD_PATH="$DESTDIR/usr" modules_install
+ make O="$BUILDDIR" VERSION=99 INSTALL_PATH="$DESTDIR/usr/lib/modules/$KERNEL_RELEASE" install
mkdir -p "$DESTDIR/usr/lib/kernel/selftests"
- make -C tools/testing/selftests -j "$(nproc)" O="$BUILDDIR" KSFT_INSTALL_PATH="$DESTDIR/usr/lib/kernel/selftests" SKIP_TARGETS="" install
+ make -C tools/testing/selftests -j "$(nproc)" O="$BUILDDIR" VERSION=99 KSFT_INSTALL_PATH="$DESTDIR/usr/lib/kernel/selftests" SKIP_TARGETS="" install
ln -sf /usr/lib/kernel/selftests/bpf/bpftool "$DESTDIR/usr/bin/bpftool"
fi
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-
[Output]
Bootable=yes
# Prevent ASAN warnings when building the image and ship the real ASAN options prefixed with MKOSI_.
[Content]
BuildDirectory=mkosi.builddir
-Cache=mkosi.cache
+CacheDirectory=mkosi.cache
+ExtraTrees=src:/root/src
Packages=
acl
bash-completion
zstd
[Host]
-QemuHeadless=yes
-Netdev=yes
+Acl=yes
QemuMem=2G
ExtraSearchPaths=build/
KernelCommandLineExtra=systemd.crash_shell
systemd.journald.forward_to_console
systemd.journald.max_level_console=warning
systemd.mask=auditd
+ # Tell the kernel to only log warning and up to the console.
+ loglevel=4
[Validation]
Password=
#
# Copyright © 2016 Zeal Jagannatha
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
+[Match]
Distribution=arch
[Content]
polkit
popt
python-pefile
+ python-psutil
+ python-pytest
quota-tools
shadow
tpm2-tss
python-jinja
python-lxml
python-pyelftools
- python-pytest
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-# We use python3*dist() throughout this file because we need to make sure the python3.9dis() packages are
-# installed on CentOS Stream 8. mkosi doesn't support release specific configuration yet so we use the globs
-# to get the necessary packages on both CentOS Stream 8 and CentOS Stream 9.
+[Match]
+Distribution=centos
[Distribution]
-Distribution=centos
Repositories=epel
-RepositoryDirectory=mkosi.conf.d/centos/mkosi.reposdir
[Content]
Packages=
polkit
popt
procps-ng
- python3[.][9]dist(pefile)
- python3[.][9]dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
- python3[.][9]dist(pytest)
- python39
+ python3-docutils
quota
tpm2-tss
vim-common
glibc-devel.i686
glibc-static
glibc-static.i686
- libgcrypt-devel # CentOS Stream 8 libgcrypt-devel doesn't ship a pkg-config file.
libxslt
pam-devel
perl-interpreter
pkgconfig(tss2-rc)
pkgconfig(valgrind)
pkgconfig(xkbcommon)
- python3*dist(docutils)
- python3*dist(jinja2)
- python3*dist(lxml)
- python3*dist(pyelftools)
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=debian
[Distribution]
-Distribution=debian
Release=testing
[Content]
policykit-1
procps
python3-pefile
+ python3-psutil
+ python3-pytest
quota
xxd
python3-jinja2
python3-lxml
python3-pyelftools
- python3-pytest
xsltproc
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=fedora
[Distribution]
-Distribution=fedora
Release=37
[Content]
popt
procps-ng
python3dist(pefile)
+ python3dist(psutil)
+ python3dist(pytest)
quota
tpm2-tss
vim-common
pkgconfig(valgrind)
pkgconfig(xencontrol)
pkgconfig(xkbcommon)
- python3dist(docutils)
+ python3-docutils
python3dist(jinja2)
python3dist(lxml)
python3dist(pyelftools)
- python3dist(pytest)
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=opensuse
[Distribution]
-Distribution=opensuse
Release=tumbleweed
[Content]
libxkbcommon0
pam
python3-pefile
+ python3-psutil
+ python3-pytest
shadow
tpm2-0-tss
vim
python3-Jinja2
python3-lxml
python3-pyelftools
- python3-pytest
qrencode-devel
shadow
system-group-obsolete
# SPDX-License-Identifier: LGPL-2.1-or-later
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=ubuntu
[Distribution]
-Distribution=ubuntu
Release=jammy
Repositories=main,universe
policykit-1
procps
python3-pefile
+ python3-psutil
+ python3-pytest
quota
xxd
python3-jinja2
python3-lxml
python3-pyelftools
- python3-pytest
xsltproc
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=centos
+Release=8
+
+[Content]
+Packages=
+ python39
+ python3.9dist(pefile)
+ python3.9dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
+ python3.9dist(psutil)
+ python3.9dist(pytest)
+
+BuildPackages=
+ libgcrypt-devel # CentOS Stream 8 libgcrypt-devel doesn't ship a pkg-config file.
+ python3.9dist(jinja2)
+ python3.9dist(lxml)
+ python3.9dist(pyelftools)
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
[powertools-hotfixes]
name=powertools-hotfixes
mirrorlist=http://mirrorlist.centos.org/?release=$stream&arch=$basearch&repo=PowerTools
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=centos
+Release=9
+
+[Content]
+Packages=
+ python3dist(pefile)
+ python3dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
+ python3dist(psutil)
+ python3dist(pytest)
+
+BuildPackages=
+ pkgconfig(libgcrypt)
+ python3dist(jinja2)
+ python3dist(lxml)
+ python3dist(pyelftools)
set debuginfod enabled off
set build-id-verbose 0
+set substitute-path ../src /root/src
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
CONFIG_DM_VERITY=y
+CONFIG_DMI_SYSFS=y
+CONFIG_DMI=y
CONFIG_EFI_MIXED=y
CONFIG_EFI_STUB=y
CONFIG_EFI_ZBOOT=y
CONFIG_HOTPLUG_PCI=y
CONFIG_HPET=y
CONFIG_HUGETLBFS=y
+CONFIG_HW_RANDOM_VIRTIO=y
CONFIG_HW_RANDOM=y
CONFIG_HYPERVISOR_GUEST=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_IKCONFIG=y
CONFIG_IMA_APPRAISE=y
CONFIG_IMA_ARCH_POLICY=y
CONFIG_IMA=y
CONFIG_INTEGRITY_MACHINE_KEYRING=y
CONFIG_INTEGRITY_PLATFORM_KEYRING=y
CONFIG_INTEGRITY_SIGNATURE=y
+CONFIG_IOSCHED_BFQ=y
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_MULTIPLE_TABLES=y
CONFIG_SCSI=y
CONFIG_SECONDARY_TRUSTED_KEYRING=y
CONFIG_SECURITY_NETWORK=y
+CONFIG_SECURITY_YAMA=y
CONFIG_SECURITY=y
CONFIG_SERIAL_8250_CONSOLE=y
-CONFIG_SERIAL_8250_DETECT_IRQ=y
-CONFIG_SERIAL_8250_EXTENDED=y
-CONFIG_SERIAL_8250_MANY_PORTS=y
-CONFIG_SERIAL_8250_NR_UARTS=32
-CONFIG_SERIAL_8250_RSA=y
-CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_PCI=y
CONFIG_SERIAL_8250=y
-CONFIG_SERIAL_NONSTANDARD=y
CONFIG_SMP=y
CONFIG_SWAP=y
CONFIG_SYSTEM_BLACKLIST_KEYRING=y
CONFIG_VIRTIO_INPUT=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_VSOCKETS=y
+CONFIG_VSOCKETS=y
CONFIG_WATCHDOG=y
CONFIG_X86_ACPI_CPUFREQ=y
CONFIG_X86_CPUID=y
# Vladimir Yerilov <openmindead@gmail.com>, 2020.
# Alexey Rubtsov <rushills@gmail.com>, 2021.
# Olga Smirnova <mistresssilvara@hotmail.com>, 2022.
+# Andrei Stepanov <adem4ik@gmail.com>, 2023.
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-20 10:35+0200\n"
-"PO-Revision-Date: 2022-10-27 23:19+0000\n"
-"Last-Translator: Olga Smirnova <mistresssilvara@hotmail.com>\n"
+"PO-Revision-Date: 2023-04-02 02:20+0000\n"
+"Last-Translator: Andrei Stepanov <adem4ik@gmail.com>\n"
"Language-Team: Russian <https://translate.fedoraproject.org/projects/systemd/"
"master/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-"X-Generator: Weblate 4.14.1\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 4.15.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/timedate/org.freedesktop.timedate1.policy:53
msgid "Turn network time synchronization on or off"
-msgstr "Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c или вÑ\8bключить синхронизацию времени по сети"
+msgstr "Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c или оÑ\82ключить синхронизацию времени по сети"
#: src/timedate/org.freedesktop.timedate1.policy:54
msgid ""
"Authentication is required to control whether network time synchronization "
"shall be enabled."
msgstr ""
-"ЧÑ\82обÑ\8b вклÑ\8eÑ\87иÑ\82Ñ\8c или вÑ\8bключить синхронизацию времени по сети, необходимо "
+"ЧÑ\82обÑ\8b вклÑ\8eÑ\87иÑ\82Ñ\8c или оÑ\82ключить синхронизацию времени по сети, необходимо "
"пройти аутентификацию."
#: src/core/dbus-unit.c:359
fi
done
- n=$(($COMP_CWORD - $i))
+ n=$((COMP_CWORD - i))
if [[ -z ${verb-} ]]; then
comps=${VERBS[*]}
fi
done
- n=$(($COMP_CWORD - $i))
+ n=$((COMP_CWORD - i))
if [[ -z ${verb-} ]]; then
comps=${VERBS[*]}
--- /dev/null
+# systemd-confext(8) completion -*- shell-script -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <https://www.gnu.org/licenses/>.
+
+__contains_word() {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+_systemd-confext() {
+ local i verb comps
+ local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
+ local -A OPTS=(
+ [STANDALONE]='-h --help --version
+ --no-pager
+ --no-legend
+ --force'
+ [ARG]='--root
+ --json'
+ )
+
+ local -A VERBS=(
+ [STANDALONE]='status
+ merge
+ unmerge
+ refresh
+ list'
+ )
+
+ _init_completion || return
+
+ if __contains_word "$prev" ${OPTS[ARG]}; then
+ case $prev in
+ --root)
+ comps=$(compgen -A directory -- "$cur" )
+ compopt -o dirnames
+ ;;
+ --json)
+ comps='pretty short off'
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
+ if [[ "$cur" = -* ]]; then
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+ return 0
+ fi
+
+ for ((i=0; i < COMP_CWORD; i++)); do
+ if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
+ ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
+ verb=${COMP_WORDS[i]}
+ break
+ fi
+ done
+
+ if [[ -z ${verb-} ]]; then
+ comps=${VERBS[*]}
+ elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
+ comps=''
+ fi
+
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+}
+
+complete -F _systemd-confext systemd-confext
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "analyze-fdstore.h"
+#include "analyze.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "fd-util.h"
+#include "format-table.h"
+
+static int dump_fdstore(sd_bus *bus, const char *arg) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
+ _cleanup_free_ char *unit = NULL;
+ int r;
+
+ assert(bus);
+ assert(arg);
+
+ r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_GLOB, ".service", &unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mangle name '%s': %m", arg);
+
+ r = bus_call_method(
+ bus,
+ bus_systemd_mgr,
+ "DumpUnitFileDescriptorStore",
+ &error,
+ &reply,
+ "s", unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to call DumpUnitFileDescriptorStore: %s",
+ bus_error_message(&error, r));
+
+ r = sd_bus_message_enter_container(reply, 'a', "(suuutuusu)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ table = table_new("fdname", "type", "devno", "inode", "rdevno", "path", "flags");
+ if (!table)
+ return log_oom();
+
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
+
+ for (;;) {
+ uint32_t mode, major, minor, rmajor, rminor, flags;
+ const char *fdname, *path;
+ uint64_t inode;
+
+ r = sd_bus_message_read(
+ reply,
+ "(suuutuusu)",
+ &fdname,
+ &mode,
+ &major, &minor,
+ &inode,
+ &rmajor, &rminor,
+ &path,
+ &flags);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ r = table_add_many(
+ table,
+ TABLE_STRING, fdname,
+ TABLE_MODE_INODE_TYPE, mode,
+ TABLE_DEVNUM, makedev(major, minor),
+ TABLE_UINT64, inode,
+ TABLE_DEVNUM, makedev(rmajor, rminor),
+ TABLE_PATH, path,
+ TABLE_STRING, accmode_to_string(flags));
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_get_rows(table) <= 0)
+ log_info("No file descriptors in fdstore of '%s'.", unit);
+ else {
+ r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to output table: %m");
+ }
+
+ return EXIT_SUCCESS;
+}
+
+int verb_fdstore(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ r = acquire_bus(&bus, NULL);
+ if (r < 0)
+ return bus_log_connect_error(r, arg_transport);
+
+ STRV_FOREACH(arg, strv_skip(argv, 1)) {
+ r = dump_fdstore(bus, *arg);
+ if (r < 0)
+ return r;
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+int verb_fdstore(int argc, char *argv[], void *userdata);
#include "analyze-calendar.h"
#include "analyze-capability.h"
#include "analyze-cat-config.h"
+#include "analyze-compare-versions.h"
#include "analyze-condition.h"
#include "analyze-critical-chain.h"
#include "analyze-dot.h"
#include "analyze-dump.h"
#include "analyze-exit-status.h"
+#include "analyze-fdstore.h"
#include "analyze-filesystems.h"
#include "analyze-inspect-elf.h"
#include "analyze-log-control.h"
#include "analyze-timestamp.h"
#include "analyze-unit-files.h"
#include "analyze-unit-paths.h"
-#include "analyze-compare-versions.h"
#include "analyze-verify.h"
#include "build.h"
#include "bus-error.h"
" security [UNIT...] Analyze security of unit\n"
" inspect-elf FILE... Parse and print ELF package metadata\n"
" malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n"
+ " fdstore SERVICE... Show file descriptor store contents of service\n"
"\nOptions:\n"
" --recursive-errors=MODE Control which units are verified\n"
" --offline=BOOL Perform a security review on unit file(s)\n"
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= is only supported for security right now.");
- if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot"))
+ if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Option --json= is only supported for security, inspect-elf, and plot right now.");
+ "Option --json= is only supported for security, inspect-elf, plot, and fdstore right now.");
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
{ "security", VERB_ANY, VERB_ANY, 0, verb_security },
{ "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection },
{ "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc },
+ { "fdstore", 2, VERB_ANY, 0, verb_fdstore },
{}
};
'analyze-dot.c',
'analyze-dump.c',
'analyze-exit-status.c',
+ 'analyze-fdstore.c',
'analyze-filesystems.c',
'analyze-inspect-elf.c',
'analyze-log-control.c',
/* Empty argument or explicit string "masked" for default behaviour. */
arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT);
else {
- bool b;
-
- r = parse_boolean_argument("--echo=", optarg, &b);
+ r = parse_boolean_argument("--echo=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, b);
- SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !b);
+ SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, r);
+ SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !r);
}
break;
if (isempty(emoji) || streq(emoji, "auto"))
SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO));
else {
- bool b;
-
- r = parse_boolean_argument("--emoji=", emoji, &b);
+ r = parse_boolean_argument("--emoji=", emoji, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !b);
+ SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r);
}
if (argc > optind) {
# elif defined(__SH4A__)
# define LIB_ARCH_TUPLE "sh4a-linux-gnu"
# endif
-#elif defined(__loongarch64)
+#elif defined(__loongarch_lp64)
# define native_architecture() ARCHITECTURE_LOONGARCH64
# if defined(__loongarch_double_float)
-# define LIB_ARCH_TUPLE "loongarch64-linux-gnuf64"
+# define LIB_ARCH_TUPLE "loongarch64-linux-gnu"
# elif defined(__loongarch_single_float)
# define LIB_ARCH_TUPLE "loongarch64-linux-gnuf32"
# elif defined(__loongarch_soft_float)
strna(n1));
}
-int chaseat(
- int dir_fd,
- const char *path,
- ChaseFlags flags,
- char **ret_path,
- int *ret_fd) {
-
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
_cleanup_free_ char *buffer = NULL, *done = NULL;
_cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
* the mount point is emitted. CHASE_WARN cannot be used in PID 1.
*/
+ if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
+ /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
+ * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
+
+ r = dir_fd_is_root_or_cwd(dir_fd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
+ }
+
if (!(flags &
(CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) &&
}
/* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
- * directory file descriptor is provided will we look at CHASE_AT_RESOLVE_IN_ROOT to determine
+ * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
* whether to resolve symlinks in it or not. */
if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
_cleanup_free_ char *f = NULL;
r = path_extract_filename(done, &f);
- if (r < 0 && r != -EDESTADDRREQ)
+ if (r < 0 && r != -EADDRNOTAVAIL)
return r;
- /* If we get EDESTADDRREQ we clear done and it will get reinitialized by the next block. */
+ /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
free_and_replace(done, f);
}
return 0;
}
-int chase(
- const char *path,
- const char *original_root,
- ChaseFlags flags,
- char **ret_path,
- int *ret_fd) {
-
+int chase(const char *path, const char *original_root, ChaseFlags flags, char **ret_path, int *ret_fd) {
_cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
_cleanup_close_ int fd = -EBADF, pfd = -EBADF;
int r;
path = path_startswith(absolute, empty_to_root(root));
if (!path)
return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
- SYNTHETIC_ERRNO(ECHRNG),
- "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
- absolute, empty_to_root(root));
+ SYNTHETIC_ERRNO(ECHRNG),
+ "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+ absolute, empty_to_root(root));
fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
- flags |= CHASE_AT_RESOLVE_IN_ROOT;
- flags &= ~CHASE_PREFIX_ROOT;
+ if (!empty_or_root(root))
+ flags |= CHASE_AT_RESOLVE_IN_ROOT;
- r = chaseat(fd, path, flags, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
+ r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
if (r < 0)
return r;
return r;
}
-int chase_and_open(
- const char *path,
- const char *root,
- ChaseFlags chase_flags,
- int open_flags,
- char **ret_path) {
-
+int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL, *fname = NULL;
mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
if (isempty(q))
q = ".";
- r = path_extract_filename(q, &fname);
- if (r < 0 && r != -EADDRNOTAVAIL)
- return r;
+ if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
+ r = path_extract_filename(q, &fname);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+ }
- if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EADDRNOTAVAIL)
- r = fd_reopen(path_fd, open_flags);
- else
- r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
+ r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
if (r < 0)
return r;
return r;
}
-int chase_and_opendir(
- const char *path,
- const char *root,
- ChaseFlags chase_flags,
- char **ret_path,
- DIR **ret_dir) {
-
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
DIR *d;
return 0;
}
-int chase_and_stat(
- const char *path,
- const char *root,
- ChaseFlags chase_flags,
- char **ret_path,
- struct stat *ret_stat) {
-
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
return 0;
}
-int chase_and_access(
- const char *path,
- const char *root,
- ChaseFlags chase_flags,
- int access_mode,
- char **ret_path) {
-
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
return 0;
}
-int chase_and_unlink(
- const char *path,
- const char *root,
- ChaseFlags chase_flags,
- int unlink_flags,
- char **ret_path) {
-
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
_cleanup_free_ char *p = NULL, *fname = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
return pfd;
}
-int chase_and_openat(
- int dir_fd,
- const char *path,
- ChaseFlags chase_flags,
- int open_flags,
- char **ret_path) {
-
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL, *fname = NULL;
mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
if (r < 0)
return r;
- r = path_extract_filename(p, &fname);
- if (r < 0 && r != -EDESTADDRREQ)
- return r;
+ if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
+ r = path_extract_filename(p, &fname);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+ }
- if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EDESTADDRREQ)
- r = fd_reopen(path_fd, open_flags);
- else
- r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
+ r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
if (r < 0)
return r;
return r;
}
-int chase_and_opendirat(
- int dir_fd,
- const char *path,
- ChaseFlags chase_flags,
- char **ret_path,
- DIR **ret_dir) {
-
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
DIR *d;
return 0;
}
-int chase_and_statat(
- int dir_fd,
- const char *path,
- ChaseFlags chase_flags,
- char **ret_path,
- struct stat *ret_stat) {
-
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
return 0;
}
-int chase_and_accessat(
- int dir_fd,
- const char *path,
- ChaseFlags chase_flags,
- int access_mode,
- char **ret_path) {
-
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
return 0;
}
-int chase_and_unlinkat(
- int dir_fd,
- const char *path,
- ChaseFlags chase_flags,
- int unlink_flags,
- char **ret_path) {
-
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
_cleanup_free_ char *p = NULL, *fname = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
return 0;
}
-int chase_and_open_parent_at(
- int dir_fd,
- const char *path,
- ChaseFlags chase_flags,
- char **ret_filename) {
-
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
int pfd, r;
assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
DEFINE_STRING_TABLE_LOOKUP(compression, Compression);
+bool compression_supported(Compression c) {
+ static const unsigned supported =
+ (1U << COMPRESSION_NONE) |
+ (1U << COMPRESSION_XZ) * HAVE_XZ |
+ (1U << COMPRESSION_LZ4) * HAVE_LZ4 |
+ (1U << COMPRESSION_ZSTD) * HAVE_ZSTD;
+
+ return c >= 0 && c < _COMPRESSION_MAX && FLAGS_SET(supported, 1U << c);
+}
+
int compress_blob_xz(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size) {
#if HAVE_XZ
#pragma once
#include <errno.h>
+#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
const char* compression_to_string(Compression compression);
Compression compression_from_string(const char *compression);
+bool compression_supported(Compression c);
+
int compress_blob_xz(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size);
int compress_blob_lz4(const void *src, uint64_t src_size,
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+extern void __gcov_dump(void);
+extern void __gcov_reset(void);
+
/* When built with --coverage (gcov) we need to explicitly call __gcov_dump()
* in places where we use _exit(), since _exit() skips at-exit hooks resulting
* in lost coverage.
* To make sure we don't miss any _exit() calls, this header file is included
* explicitly on the compiler command line via the -include directive (only
* when built with -Db_coverage=true)
- * */
+ */
extern void _exit(int);
-extern void __gcov_dump(void);
static inline _Noreturn void _coverage__exit(int status) {
__gcov_dump();
_exit(status);
}
#define _exit(x) _coverage__exit(x)
+
+/* gcov provides wrappers for the exec*() calls but there's none for execveat(),
+ * which means we lose all coverage prior to the call. To mitigate this, let's
+ * add a simple execveat() wrapper in gcov's style[0], which dumps and resets
+ * the coverage data when needed.
+ *
+ * This applies only when we're built with -Dfexecve=true.
+ *
+ * [0] https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/libgcov-interface.c;h=b2ee930864183b78c8826255183ca86e15e21ded;hb=HEAD
+ */
+
+extern int execveat(int, const char *, char * const [], char * const [], int);
+
+static inline int _coverage_execveat(
+ int dirfd,
+ const char *pathname,
+ char * const argv[],
+ char * const envp[],
+ int flags) {
+ __gcov_dump();
+ int r = execveat(dirfd, pathname, argv, envp, flags);
+ __gcov_reset();
+
+ return r;
+}
+#define execveat(d,p,a,e,f) _coverage_execveat(d, p, a, e, f)
assert(ret);
- if (major(devnum) == 0 && minor(devnum) == 0)
+ if (devnum_is_zero(devnum))
/* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
* /dev/block/ and /dev/char/, hence we handle them specially here. */
return device_path_make_inaccessible(mode, ret);
}
#define FORMAT_DEVNUM(d) format_devnum((d), (char[DEVNUM_STR_MAX]) {})
+
+static inline bool devnum_is_zero(dev_t d) {
+ return major(d) == 0 && minor(d) == 0;
+}
int fd_get_path(int fd, char **ret) {
int r;
+ assert(fd >= 0 || fd == AT_FDCWD);
+
+ if (fd == AT_FDCWD)
+ return safe_getcwd(ret);
+
r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret);
if (r == -ENOENT) {
/* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
*/
return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
}
+
+const char *accmode_to_string(int flags) {
+ switch (flags & O_ACCMODE) {
+ case O_RDONLY:
+ return "ro";
+ case O_WRONLY:
+ return "wo";
+ case O_RDWR:
+ return "rw";
+ default:
+ return NULL;
+ }
+}
#pragma once
#include <dirent.h>
+#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>
int fd_get_diskseq(int fd, uint64_t *ret);
int dir_fd_is_root(int dir_fd);
+static inline int dir_fd_is_root_or_cwd(int dir_fd) {
+ return dir_fd == AT_FDCWD ? true : dir_fd_is_root(dir_fd);
+}
/* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
#define PROC_FD_PATH_MAX \
#define FORMAT_PROC_FD_PATH(fd) \
format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
+
+const char *accmode_to_string(int flags);
* have the exact same contents and basic file attributes already. In that case remove the new file
* instead. This call is useful for reducing inotify wakeups on files that are updated but don't
* actually change. This function is written in a style that we rather rename too often than suppress
- * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+ * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
* events is an optimization only, not more. */
old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
if (isempty(path)) {
assert(!FLAGS_SET(flags, O_CREAT|O_EXCL));
- return fd_reopen(dir_fd, flags);
+ return fd_reopen(dir_fd, flags & ~O_NOFOLLOW);
}
if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) {
static thread_local LIST_HEAD(LogContext, _log_context) = NULL;
static thread_local size_t _log_context_num_fields = 0;
+static thread_local const char *log_prefix = NULL;
+
#if LOG_MESSAGE_VERIFICATION || defined(__COVERITY__)
bool _log_message_dummy = false; /* Always false */
#endif
header_time[FORMAT_TIMESTAMP_MAX],
prefix[1 + DECIMAL_STR_MAX(int) + 2],
tid_string[3 + DECIMAL_STR_MAX(pid_t) + 1];
- struct iovec iovec[9];
+ struct iovec iovec[11];
const char *on = NULL, *off = NULL;
size_t n = 0;
if (on)
iovec[n++] = IOVEC_MAKE_STRING(on);
+ if (log_prefix) {
+ iovec[n++] = IOVEC_MAKE_STRING(log_prefix);
+ iovec[n++] = IOVEC_MAKE_STRING(": ");
+ }
iovec[n++] = IOVEC_MAKE_STRING(buffer);
if (off)
iovec[n++] = IOVEC_MAKE_STRING(off);
IOVEC_MAKE_STRING(header_time),
IOVEC_MAKE_STRING(program_invocation_short_name),
IOVEC_MAKE_STRING(header_pid),
+ IOVEC_MAKE_STRING(strempty(log_prefix)),
+ IOVEC_MAKE_STRING(log_prefix ? ": " : ""),
IOVEC_MAKE_STRING(buffer),
};
const struct msghdr msghdr = {
IOVEC_MAKE_STRING(header_priority),
IOVEC_MAKE_STRING(program_invocation_short_name),
IOVEC_MAKE_STRING(header_pid),
+ IOVEC_MAKE_STRING(strempty(log_prefix)),
+ IOVEC_MAKE_STRING(log_prefix ? ": " : ""),
IOVEC_MAKE_STRING(buffer),
IOVEC_MAKE_STRING("\n"),
};
if (journal_fd < 0)
return 0;
- iovec_len = MIN(4 + _log_context_num_fields * 2, IOVEC_MAX);
+ iovec_len = MIN(6 + _log_context_num_fields * 2, IOVEC_MAX);
iovec = newa(struct iovec, iovec_len);
log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object, extra_field, extra);
iovec[n++] = IOVEC_MAKE_STRING(header);
iovec[n++] = IOVEC_MAKE_STRING("MESSAGE=");
+ if (log_prefix) {
+ iovec[n++] = IOVEC_MAKE_STRING(log_prefix);
+ iovec[n++] = IOVEC_MAKE_STRING(": ");
+ }
iovec[n++] = IOVEC_MAKE_STRING(buffer);
iovec[n++] = IOVEC_MAKE_STRING("\n");
/* Make sure that %m maps to the specified error (or "Success"). */
LOCAL_ERRNO(ERRNO_VALUE(error));
- /* Prepend the object name before the message */
- if (object) {
- size_t n;
-
- n = strlen(object);
- buffer = newa(char, n + 2 + LINE_MAX);
- b = stpcpy(stpcpy(buffer, object), ": ");
- } else
- b = buffer = newa(char, LINE_MAX);
+ LOG_SET_PREFIX(object);
+ b = buffer = newa(char, LINE_MAX);
(void) vsnprintf(b, LINE_MAX, format, ap);
return log_dispatch_internal(level, error, file, line, func,
log_show_color(true);
}
+const char *_log_set_prefix(const char *prefix, bool force) {
+ const char *old = log_prefix;
+
+ if (prefix || force)
+ log_prefix = prefix;
+
+ return old;
+}
+
static int saved_log_context_enabled = -1;
bool log_context_enabled(void) {
#define log_ratelimit_error_errno(error, ...) log_ratelimit_full_errno(LOG_ERR, error, __VA_ARGS__)
#define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__)
+const char *_log_set_prefix(const char *prefix, bool force);
+static inline const char *_log_unset_prefixp(const char **p) {
+ assert(p);
+ _log_set_prefix(*p, true);
+ return NULL;
+}
+
+#define LOG_SET_PREFIX(prefix) \
+ _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false);
+
/*
* The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep
* track of a thread local log context onto which we can push extra metadata fields that should be logged.
#ifndef O_TMPFILE
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
#endif
+
+/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
+ * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
+ * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */
+#if O_LARGEFILE != 0
+#define RAW_O_LARGEFILE O_LARGEFILE
+#else
+#define RAW_O_LARGEFILE 0100000
+#endif
assert(_mkdirat && _mkdirat != mkdirat);
r = _mkdirat(dir_fd, path, mode);
- if (r >= 0) {
- r = chmod_and_chown_at(dir_fd, path, mode, uid, gid);
- if (r < 0)
- return r;
- }
+ if (r >= 0)
+ return chmod_and_chown_at(dir_fd, path, mode, uid, gid);
if (r != -EEXIST)
return r;
#include "utf8.h"
#include "xattr-util.h"
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+ const char *release_file_directory;
+ const char *release_file_path_prefix;
+} image_class_release_info[_IMAGE_CLASS_MAX] = {
+ [IMAGE_SYSEXT] = {
+ .release_file_directory = "/usr/lib/extension-release.d/",
+ .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
+ },
+ [IMAGE_CONFEXT] = {
+ .release_file_directory = "/etc/extension-release.d/",
+ .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
+ }
+};
+
bool image_name_is_valid(const char *s) {
if (!filename_is_valid(s))
return false;
return true;
}
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check) {
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
int r;
assert(path);
+ assert(image_class >= 0);
+ assert(image_class < _IMAGE_CLASS_MAX);
/* 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 distinguish the case where the whole root doesn't exist from
return -errno;
/* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
+ * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
* and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
- r = open_extension_release(path, extension, relax_extension_release_check, NULL, NULL);
+ r = open_extension_release(path, image_class, extension, relax_extension_release_check, NULL, NULL);
if (r == -ENOENT) /* We got nothing */
return 0;
if (r < 0)
return false;
}
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
_cleanup_free_ char *q = NULL;
int r, fd;
if (extension) {
+ assert(image_class >= 0);
+ assert(image_class < _IMAGE_CLASS_MAX);
+
const char *extension_full_path;
if (!image_name_is_valid(extension))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"The extension name %s is invalid.", extension);
- extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
+ extension_full_path = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
r = chase(extension_full_path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
_cleanup_free_ char *extension_release_dir_path = NULL;
_cleanup_closedir_ DIR *extension_release_dir = NULL;
- r = chase_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
+ r = chase_and_opendir(image_class_release_info[image_class].release_file_directory, root, CHASE_PREFIX_ROOT,
&extension_release_dir_path, &extension_release_dir);
if (r < 0)
- return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
+ return log_debug_errno(r, "Cannot open %s%s, ignoring: %m", root, image_class_release_info[image_class].release_file_directory);
r = -ENOENT;
FOREACH_DIRENT(de, extension_release_dir, return -errno) {
continue;
if (!image_name_is_valid(image_name)) {
- log_debug("%s/%s is not a valid extension-release file name, ignoring.",
+ log_debug("%s/%s is not a valid release file name, ignoring.",
extension_release_dir_path, de->d_name);
continue;
}
O_PATH|O_CLOEXEC|O_NOFOLLOW);
if (extension_release_fd < 0)
return log_debug_errno(errno,
- "Failed to open extension-release file %s/%s: %m",
+ "Failed to open release file %s/%s: %m",
extension_release_dir_path,
de->d_name);
return 0;
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+int fopen_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
_cleanup_free_ char *p = NULL;
_cleanup_close_ int fd = -EBADF;
FILE *f;
int r;
if (!ret_file)
- return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
+ return open_extension_release(root, image_class, extension, relax_extension_release_check, ret_path, NULL);
- r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+ r = open_extension_release(root, image_class, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
if (r < 0)
return r;
return 0;
}
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
+static int parse_release_internal(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, va_list ap) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ r = fopen_extension_release(root, image_class, extension, relax_extension_release_check, &p, &f);
if (r < 0)
return r;
return parse_env_filev(f, p, ap);
}
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int _parse_extension_release(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) {
va_list ap;
int r;
+ assert(image_class >= 0);
+ assert(image_class < _IMAGE_CLASS_MAX);
+
va_start(ap, extension);
- r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+ r = parse_release_internal(root, image_class, relax_extension_release_check, extension, ap);
va_end(ap);
return r;
int r;
va_start(ap, root);
- r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
+ r = parse_release_internal(root, -1, /* relax_extension_release_check= */ false, NULL, ap);
va_end(ap);
return r;
return 0;
}
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret) {
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ assert(image_class >= 0);
+ assert(image_class < _IMAGE_CLASS_MAX);
+
+ r = fopen_extension_release(root, image_class, extension, relax_extension_release_check, &p, &f);
if (r < 0)
return r;
#include <stdio.h>
#include "time-util.h"
+typedef enum ImageClass {
+ IMAGE_MACHINE,
+ IMAGE_PORTABLE,
+ IMAGE_SYSEXT,
+ IMAGE_CONFEXT,
+ _IMAGE_CLASS_MAX,
+ _IMAGE_CLASS_INVALID = -EINVAL,
+} ImageClass;
+
+const char* image_class_to_string(ImageClass cl) _const_;
+ImageClass image_class_from_string(const char *s) _pure_;
/* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
bool image_name_is_valid(const char *s) _pure_;
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check);
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
static inline int path_is_os_tree(const char *path) {
- return path_is_extension_tree(path, NULL, false);
+ return path_is_extension_tree(IMAGE_SYSEXT, path, NULL, false);
}
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
- return open_extension_release(root, NULL, false, ret_path, ret_fd);
+ return open_extension_release(root, IMAGE_SYSEXT, NULL, false, ret_path, ret_fd);
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
+int fopen_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
- return fopen_extension_release(root, NULL, false, ret_path, ret_file);
+ return fopen_extension_release(root, IMAGE_SYSEXT, NULL, false, ret_path, ret_file);
}
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+int _parse_extension_release(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
int _parse_os_release(const char *root, ...) _sentinel_;
-#define parse_extension_release(root, relax_extension_release_check, extension, ...) _parse_extension_release(root, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_extension_release(root, image_class, relax_extension_release_check, extension, ...) _parse_extension_release(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
#define parse_os_release(root, ...) _parse_os_release(root, __VA_ARGS__, NULL)
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret);
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
int load_os_release_pairs(const char *root, char ***ret);
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
return null_or_empty(&st);
}
-int null_or_empty_fd(int fd) {
- struct stat st;
-
- assert(fd >= 0);
-
- if (fstat(fd, &st) < 0)
- return -errno;
-
- return null_or_empty(&st);
-}
-
static int fd_is_read_only_fs(int fd) {
struct statvfs st;
return 0;
}
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, 0);
+ if (fd < 0)
+ return fd;
+
+ return RET_NERRNO(fstatfs(fd, ret));
+}
+
void inode_hash_func(const struct stat *q, struct siphash *state) {
siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
}
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
+
+const char* inode_type_to_string(mode_t m) {
+
+ /* Returns a short string for the inode type. We use the same name as the underlying macros for each
+ * inode type. */
+
+ switch (m & S_IFMT) {
+ case S_IFREG:
+ return "reg";
+ case S_IFDIR:
+ return "dir";
+ case S_IFCHR:
+ return "chr";
+ case S_IFBLK:
+ return "blk";
+ case S_IFIFO:
+ return "fifo";
+ case S_IFSOCK:
+ return "sock";
+ }
+
+ return NULL;
+}
bool null_or_empty(struct stat *st) _pure_;
int null_or_empty_path_with_root(const char *fn, const char *root);
-int null_or_empty_fd(int fd);
static inline int null_or_empty_path(const char *fn) {
return null_or_empty_path_with_root(fn, NULL);
int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx);
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
+
#if HAS_FEATURE_MEMORY_SANITIZER
# warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
# define STRUCT_STATX_DEFINE(var) \
void inode_hash_func(const struct stat *q, struct siphash *state);
int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
+
+const char* inode_type_to_string(mode_t m);
&ttynr) != 1)
return -EIO;
- if (major(ttynr) == 0 && minor(ttynr) == 0)
+ if (devnum_is_zero(ttynr))
return -ENXIO;
if (d)
DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
static const char* const service_state_table[_SERVICE_STATE_MAX] = {
- [SERVICE_DEAD] = "dead",
- [SERVICE_CONDITION] = "condition",
- [SERVICE_START_PRE] = "start-pre",
- [SERVICE_START] = "start",
- [SERVICE_START_POST] = "start-post",
- [SERVICE_RUNNING] = "running",
- [SERVICE_EXITED] = "exited",
- [SERVICE_RELOAD] = "reload",
- [SERVICE_RELOAD_SIGNAL] = "reload-signal",
- [SERVICE_RELOAD_NOTIFY] = "reload-notify",
- [SERVICE_STOP] = "stop",
- [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
- [SERVICE_STOP_SIGTERM] = "stop-sigterm",
- [SERVICE_STOP_SIGKILL] = "stop-sigkill",
- [SERVICE_STOP_POST] = "stop-post",
- [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
- [SERVICE_FINAL_SIGTERM] = "final-sigterm",
- [SERVICE_FINAL_SIGKILL] = "final-sigkill",
- [SERVICE_FAILED] = "failed",
- [SERVICE_AUTO_RESTART] = "auto-restart",
- [SERVICE_CLEANING] = "cleaning",
+ [SERVICE_DEAD] = "dead",
+ [SERVICE_CONDITION] = "condition",
+ [SERVICE_START_PRE] = "start-pre",
+ [SERVICE_START] = "start",
+ [SERVICE_START_POST] = "start-post",
+ [SERVICE_RUNNING] = "running",
+ [SERVICE_EXITED] = "exited",
+ [SERVICE_RELOAD] = "reload",
+ [SERVICE_RELOAD_SIGNAL] = "reload-signal",
+ [SERVICE_RELOAD_NOTIFY] = "reload-notify",
+ [SERVICE_STOP] = "stop",
+ [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
+ [SERVICE_STOP_SIGTERM] = "stop-sigterm",
+ [SERVICE_STOP_SIGKILL] = "stop-sigkill",
+ [SERVICE_STOP_POST] = "stop-post",
+ [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
+ [SERVICE_FINAL_SIGTERM] = "final-sigterm",
+ [SERVICE_FINAL_SIGKILL] = "final-sigkill",
+ [SERVICE_FAILED] = "failed",
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart",
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
+ [SERVICE_AUTO_RESTART] = "auto-restart",
+ [SERVICE_CLEANING] = "cleaning",
};
DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
SERVICE_FINAL_SIGKILL,
SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART,
+ SERVICE_FAILED_BEFORE_AUTO_RESTART,
SERVICE_AUTO_RESTART,
SERVICE_CLEANING,
_SERVICE_STATE_MAX,
}
int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, const char *target) {
- const char *src, *dst;
+ _cleanup_free_ char *src = NULL, *dst = NULL;
_cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
UnitType src_unit_type, dst_unit_type;
UnitNameFlags src_name_type, dst_name_type;
+ int r;
/* Check if the *alias* symlink is valid. This applies to symlinks like
* /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
* -ELOOP for an alias to self.
*/
- src = basename(filename);
- dst = basename(target);
+ r = path_extract_filename(filename, &src);
+ if (r < 0)
+ return r;
+
+ r = path_extract_filename(target, &dst);
+ if (r < 0)
+ return r;
/* src checks */
}
if (fragment && ret_names) {
- const char *fragment_basename = basename(fragment);
+ _cleanup_free_ char *fragment_basename = NULL;
+ r = path_extract_filename(fragment, &fragment_basename);
+ if (r < 0)
+ return r;
if (!streq(fragment_basename, unit_name)) {
/* Add names based on the fragment name to the set of names */
#define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1))
#define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1))
-#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
+#define ETC_PASSWD_LOCK_FILENAME ".pwd.lock"
+#define ETC_PASSWD_LOCK_PATH "/etc/" ETC_PASSWD_LOCK_FILENAME
/* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer
* NULL is special */
assert(to);
r = get_file_version(fd_from, &a);
+ if (r == -ESRCH)
+ return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from);
if (r < 0)
return r;
- if (r == 0)
- return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE),
- "Source file \"%s\" does not carry version information!",
- from);
r = get_file_version(fd_to, &b);
+ if (r == -ESRCH)
+ return log_notice_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).",
+ to);
if (r < 0)
return r;
- if (r == 0 || compare_product(a, b) != 0)
- return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE),
- "Skipping \"%s\", since it's owned by another boot loader.",
- to);
+ if (compare_product(a, b) != 0)
+ return log_notice_errno(SYNTHETIC_ERRNO(ESRCH),
+ "Skipping \"%s\", it's owned by another boot loader.", to);
r = compare_version(a, b);
log_debug("Comparing versions: \"%s\" %s \"%s", a, comparison_operator(r), b);
if (r < 0)
return log_warning_errno(SYNTHETIC_ERRNO(ESTALE),
- "Skipping \"%s\", since newer boot loader version in place already.", to);
+ "Skipping \"%s\", newer boot loader version in place already.", to);
if (r == 0)
return log_info_errno(SYNTHETIC_ERRNO(ESTALE),
- "Skipping \"%s\", since same boot loader version in place already.", to);
+ "Skipping \"%s\", same boot loader version in place already.", to);
return 0;
}
/* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
r = chase_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+ if (r == -ENOENT && arg_graceful) {
+ log_debug("Source directory does not exist, ignoring.");
+ return 0;
+ }
if (r < 0)
return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
k = copy_one_file(esp_path, de->d_name, force);
/* Don't propagate an error code if no update necessary, installed version already equal or
* newer version, or other boot loader in place. */
- if (arg_graceful && IN_SET(k, -ESTALE, -EREMOTE))
+ if (arg_graceful && IN_SET(k, -ESTALE, -ESRCH))
continue;
if (k < 0 && r == 0)
r = k;
return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
r = get_file_version(fd, &v);
+ if (r == -ESRCH)
+ continue; /* No version information */
if (r < 0)
return r;
- if (r > 0 && startswith(v, "systemd-boot ")) {
+ if (startswith(v, "systemd-boot ")) {
r = unlinkat(dirfd(d), de->d_name, 0);
if (r < 0)
return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name);
return log_error_errno(r, "Failed to read \"%s/%s\": %m", esp_path, path);
FOREACH_DIRENT(de, d, break) {
- _cleanup_free_ char *v = NULL;
+ _cleanup_free_ char *v = NULL, *filename = NULL;
_cleanup_close_ int fd = -EBADF;
if (!endswith_no_case(de->d_name, ".efi"))
if (prefix && !startswith_no_case(de->d_name, prefix))
continue;
+ filename = path_join(p, de->d_name);
+ if (!filename)
+ return log_oom();
+ LOG_SET_PREFIX(filename);
+
fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
if (fd < 0)
- return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
+ return log_error_errno(errno, "Failed to open file for reading: %m");
r = get_file_version(fd, &v);
+ if (r == -ESRCH) /* Not the file we are looking for. */
+ continue;
if (r < 0)
return r;
- if (*previous) { /* let's output the previous entry now, since now we know that there will be one more, and can draw the tree glyph properly */
+ if (*previous) { /* Let's output the previous entry now, since now we know that there will be
+ * one more, and can draw the tree glyph properly. */
printf(" %s %s%s\n",
*is_first ? "File:" : " ",
special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous);
struct stat st;
char *buf;
const char *s, *e;
- char *x = NULL;
+ char *marker = NULL;
int r;
assert(fd >= 0);
return log_error_errno(errno, "Failed to stat EFI binary: %m");
r = stat_verify_regular(&st);
- if (r < 0)
- return log_error_errno(r, "EFI binary is not a regular file: %m");
-
- if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size)) {
- *ret = NULL;
- return 0;
+ if (r < 0) {
+ log_debug_errno(r, "EFI binary is not a regular file, assuming no version information: %m");
+ return -ESRCH;
}
+ if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size))
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
+ "EFI binary size too %s: %"PRIi64,
+ st.st_size < 27 ? "small" : "large", st.st_size);
+
buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED)
- return log_error_errno(errno, "Failed to memory map EFI binary: %m");
+ return log_error_errno(errno, "Failed to mmap EFI binary: %m");
s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
if (!s) {
- r = -ESRCH;
+ r = log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no LoaderInfo marker.");
goto finish;
}
e = memmem_safe(s, st.st_size - (s - buf), " ####", 5);
if (!e || e - s < 3) {
- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed version string.");
+ r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "EFI binary has malformed LoaderInfo marker.");
goto finish;
}
- x = strndup(s, e - s);
- if (!x) {
+ marker = strndup(s, e - s);
+ if (!marker) {
r = log_oom();
goto finish;
}
- r = 1;
+ log_debug("EFI binary LoaderInfo marker: \"%s\"", marker);
+ r = 0;
+ *ret = marker;
finish:
(void) munmap(buf, st.st_size);
- if (r >= 0)
- *ret = x;
-
return r;
}
};
int c, r;
- bool b;
assert(argc >= 0);
assert(argv);
if (streq(optarg, "auto")) /* retained for backwards compatibility */
arg_make_entry_directory = -1; /* yes if machine-id is permanent */
else {
- r = parse_boolean_argument("--make-entry-directory=", optarg, &b);
+ r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
if (r < 0)
return r;
- arg_make_entry_directory = b;
+ arg_make_entry_directory = r;
}
break;
}
if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "--efi-boot-option-description= too long: %zu > %zu", strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
+ "--efi-boot-option-description= too long: %zu > %zu",
+ strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
+static int method_dump_unit_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return method_generic_unit_operation(message, userdata, error, bus_service_method_dump_file_descriptor_store, 0);
+}
+
const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_RESULT("a(us)", users),
method_get_dynamic_users,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("DumpUnitFileDescriptorStore",
+ SD_BUS_ARGS("s", name),
+ SD_BUS_RESULT("a(suuutuusu)", entries),
+ method_dump_unit_descriptor_store,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL_WITH_ARGS("UnitNew",
SD_BUS_ARGS("s", id, "o", unit),
#include "alloc-util.h"
#include "async.h"
+#include "bus-common-errors.h"
#include "bus-get-properties.h"
#include "dbus-cgroup.h"
#include "dbus-execute.h"
#include "fd-util.h"
#include "fileio.h"
#include "locale-util.h"
+#include "missing_fcntl.h"
#include "mount-util.h"
#include "open-file.h"
#include "parse-util.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
static BUS_DEFINE_PROPERTY_GET2(property_get_notify_access, "s", Service, service_get_notify_access, notify_access_to_string);
-static BUS_DEFINE_PROPERTY_GET(property_get_restart_usec_current, "t", Service, service_restart_usec);
+static BUS_DEFINE_PROPERTY_GET(property_get_restart_usec_next, "t", Service, service_restart_usec_next);
static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, service_timeout_abort_usec);
static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode);
return bus_service_method_mount(message, userdata, error, true);
}
+int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ Service *s = ASSERT_PTR(userdata);
+ int r;
+
+ assert(message);
+
+ r = mac_selinux_unit_access_check(UNIT(s), message, "status", error);
+ if (r < 0)
+ return r;
+
+ if (s->n_fd_store_max == 0 && s->n_fd_store == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED, "File descriptor store not enabled for %s.", UNIT(s)->id);
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(suuutuusu)");
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH(fd_store, i, s->fd_store) {
+ _cleanup_free_ char *path = NULL;
+ struct stat st;
+ int flags;
+
+ if (fstat(i->fd, &st) < 0) {
+ log_debug_errno(errno, "Failed to stat() file descriptor entry '%s', skipping.", strna(i->fdname));
+ continue;
+ }
+
+ flags = fcntl(i->fd, F_GETFL);
+ if (flags < 0) {
+ log_debug_errno(errno, "Failed to issue F_GETFL on file descriptor entry '%s', skipping.", strna(i->fdname));
+ continue;
+ }
+
+ /* glibc implies O_LARGEFILE everywhere on 64bit off_t builds, but forgets to hide it away on
+ * F_GETFL, but provides no definition to check for that. Let's mask the flag away manually,
+ * to not confuse clients. */
+ flags &= ~RAW_O_LARGEFILE;
+
+ (void) fd_get_path(i->fd, &path);
+
+ r = sd_bus_message_append(
+ reply,
+ "(suuutuusu)",
+ i->fdname,
+ (uint32_t) st.st_mode,
+ (uint32_t) major(st.st_dev), (uint32_t) minor(st.st_dev),
+ (uint64_t) st.st_ino,
+ (uint32_t) major(st.st_rdev), (uint32_t) minor(st.st_rdev),
+ path,
+ (uint32_t) flags);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestartSteps", "u", bus_property_get_unsigned, offsetof(Service, restart_steps), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestartUSecMax", "t", bus_property_get_usec, offsetof(Service, restart_usec_max), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("RestartUSecCurrent", "t", property_get_restart_usec_current, 0, 0),
+ SD_BUS_PROPERTY("RestartUSecNext", "t", property_get_restart_usec_next, 0, 0),
SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutAbortUSec", "t", property_get_timeout_abort_usec, 0, 0),
bus_service_method_mount_image,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("DumpFileDescriptorStore",
+ SD_BUS_NO_ARGS,
+ SD_BUS_ARGS("a(suuutuusu)", entries),
+ bus_service_method_dump_file_descriptor_store,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
/* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
return r;
if (u->transient && u->load_state == UNIT_STUB) {
- /* This is a transient unit, let's load a little more */
+ /* This is a transient unit, let's allow a little more */
r = bus_service_set_transient_property(s, name, message, flags, error);
if (r != 0)
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_commit_properties(Unit *u);
+int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error);
assert(u);
assert(message);
- /* We iterate through the array twice. First run we just check
- * if all passed data is valid, second run actually applies
- * it. This is to implement transaction-like behaviour without
- * actually providing full transactions. */
+ /* We iterate through the array twice. First run just checks if all passed data is valid, second run
+ * actually applies it. This implements transaction-like behaviour without actually providing full
+ * transactions. */
r = sd_bus_message_enter_container(message, 'a', "(sv)");
if (r < 0)
- return r;
+ goto error;
for (;;) {
const char *name;
r = sd_bus_message_enter_container(message, 'r', "sv");
if (r < 0)
- return r;
+ goto error;
if (r == 0) {
if (for_real || UNIT_WRITE_FLAGS_NOOP(flags))
break;
/* Reached EOF. Let's try again, and this time for realz... */
r = sd_bus_message_rewind(message, false);
if (r < 0)
- return r;
+ goto error;
for_real = true;
continue;
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
- return r;
+ goto error;
r = sd_bus_message_enter_container(message, 'v', NULL);
if (r < 0)
- return r;
+ goto error;
/* If not for real, then mask out the two target flags */
f = for_real ? flags : (flags & ~(UNIT_RUNTIME|UNIT_PERSISTENT));
if (r == 0)
r = bus_unit_set_live_property(u, name, message, f, error);
if (r < 0)
- return r;
+ goto error;
if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY,
r = sd_bus_message_exit_container(message);
if (r < 0)
- return r;
+ goto error;
r = sd_bus_message_exit_container(message);
if (r < 0)
- return r;
+ goto error;
n += for_real;
}
r = sd_bus_message_exit_container(message);
if (r < 0)
- return r;
+ goto error;
if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties)
UNIT_VTABLE(u)->bus_commit_properties(u);
return n;
+
+ error:
+ /* Pretty much any of the calls above can fail if the message is not formed properly
+ * or if it has unexpected contents. Fill in a more informative error message here. */
+ if (sd_bus_error_is_set(error))
+ return r;
+ return sd_bus_error_set_errnof(error, r,
+ r == -ENXIO ? "Failed to set unit properties: Unexpected message contents"
+ : "Failed to set unit properties: %m");
}
int bus_unit_validate_load_state(Unit *u, sd_bus_error *error) {
return r;
}
-int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group) {
+int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicCreds **ret) {
+ _cleanup_(dynamic_creds_unrefp) DynamicCreds *creds = NULL;
bool acquired = false;
int r;
- assert(creds);
assert(m);
+ assert(ret);
+
+ if (!user && !group) {
+ *ret = NULL;
+ return 0;
+ }
+
+ creds = new0(DynamicCreds, 1);
+ if (!creds)
+ return -ENOMEM;
/* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some
* services use different user and groups. For cases like that there's DynamicCreds containing a pair of user
* and group. This call allocates a pair. */
- if (!creds->user && user) {
+ if (user) {
r = dynamic_user_acquire(m, user, &creds->user);
if (r < 0)
return r;
acquired = true;
}
- if (!creds->group) {
-
- if (creds->user && (!group || streq_ptr(user, group)))
- creds->group = dynamic_user_ref(creds->user);
- else if (group) {
- r = dynamic_user_acquire(m, group, &creds->group);
- if (r < 0) {
- if (acquired)
- creds->user = dynamic_user_unref(creds->user);
- return r;
- }
+ if (creds->user && (!group || streq_ptr(user, group)))
+ creds->group = dynamic_user_ref(creds->user);
+ else if (group) {
+ r = dynamic_user_acquire(m, group, &creds->group);
+ if (r < 0) {
+ if (acquired)
+ creds->user = dynamic_user_unref(creds->user);
+ return r;
}
}
+ *ret = TAKE_PTR(creds);
+
return 0;
}
return 0;
}
-void dynamic_creds_unref(DynamicCreds *creds) {
- assert(creds);
+DynamicCreds* dynamic_creds_unref(DynamicCreds *creds) {
+ if (!creds)
+ return NULL;
creds->user = dynamic_user_unref(creds->user);
creds->group = dynamic_user_unref(creds->group);
+
+ return mfree(creds);
}
-void dynamic_creds_destroy(DynamicCreds *creds) {
- assert(creds);
+DynamicCreds* dynamic_creds_destroy(DynamicCreds *creds) {
+ if (!creds)
+ return NULL;
creds->user = dynamic_user_destroy(creds->user);
creds->group = dynamic_user_destroy(creds->group);
+
+ return mfree(creds);
}
int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret);
int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret);
-int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group);
+int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicCreds **ret);
int dynamic_creds_realize(DynamicCreds *creds, char **suggested_paths, uid_t *uid, gid_t *gid);
-void dynamic_creds_unref(DynamicCreds *creds);
-void dynamic_creds_destroy(DynamicCreds *creds);
+DynamicCreds *dynamic_creds_unref(DynamicCreds *creds);
+DynamicCreds *dynamic_creds_destroy(DynamicCreds *creds);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DynamicCreds*, dynamic_creds_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DynamicCreds*, dynamic_creds_destroy);
if (!IN_SET(context->mount_propagation_flag, 0, MS_SHARED))
return true;
- if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir))
+ if (context->private_tmp && runtime && runtime->shared && (runtime->shared->tmp_dir || runtime->shared->var_tmp_dir))
return true;
if (context->private_devices ||
}
static bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type) {
+ assert(context);
+
if (!context->dynamic_user)
return false;
* that is sticky, and that's the one we want to use here.
* This does not apply when we are using /run/systemd/empty as fallback. */
- if (context->private_tmp && runtime) {
- if (streq_ptr(runtime->tmp_dir, RUN_SYSTEMD_EMPTY))
- tmp_dir = runtime->tmp_dir;
- else if (runtime->tmp_dir)
- tmp_dir = strjoina(runtime->tmp_dir, "/tmp");
+ if (context->private_tmp && runtime && runtime->shared) {
+ if (streq_ptr(runtime->shared->tmp_dir, RUN_SYSTEMD_EMPTY))
+ tmp_dir = runtime->shared->tmp_dir;
+ else if (runtime->shared->tmp_dir)
+ tmp_dir = strjoina(runtime->shared->tmp_dir, "/tmp");
- if (streq_ptr(runtime->var_tmp_dir, RUN_SYSTEMD_EMPTY))
- var_tmp_dir = runtime->var_tmp_dir;
- else if (runtime->var_tmp_dir)
- var_tmp_dir = strjoina(runtime->var_tmp_dir, "/tmp");
+ if (streq_ptr(runtime->shared->var_tmp_dir, RUN_SYSTEMD_EMPTY))
+ var_tmp_dir = runtime->shared->var_tmp_dir;
+ else if (runtime->shared->var_tmp_dir)
+ var_tmp_dir = strjoina(runtime->shared->var_tmp_dir, "/tmp");
}
ns_info = (NamespaceInfo) {
static int close_remaining_fds(
const ExecParameters *params,
const ExecRuntime *runtime,
- const DynamicCreds *dcreds,
int user_lookup_fd,
int socket_fd,
const int *fds, size_t n_fds) {
n_dont_close += n_fds;
}
- if (runtime) {
- append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket);
- append_socket_pair(dont_close, &n_dont_close, runtime->ipcns_storage_socket);
+ if (runtime && runtime->shared) {
+ append_socket_pair(dont_close, &n_dont_close, runtime->shared->netns_storage_socket);
+ append_socket_pair(dont_close, &n_dont_close, runtime->shared->ipcns_storage_socket);
}
- if (dcreds) {
- if (dcreds->user)
- append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket);
- if (dcreds->group)
- append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket);
+ if (runtime && runtime->dynamic_creds) {
+ if (runtime->dynamic_creds->user)
+ append_socket_pair(dont_close, &n_dont_close, runtime->dynamic_creds->user->storage_socket);
+ if (runtime->dynamic_creds->group)
+ append_socket_pair(dont_close, &n_dont_close, runtime->dynamic_creds->group->storage_socket);
}
if (user_lookup_fd >= 0)
return 0;
}
+static void log_command_line(Unit *unit, const char *msg, const char *executable, char **argv) {
+ assert(unit);
+ assert(msg);
+ assert(executable);
+
+ if (!DEBUG_LOGGING)
+ return;
+
+ _cleanup_free_ char *cmdline = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
+
+ log_unit_struct(unit, LOG_DEBUG,
+ "EXECUTABLE=%s", executable,
+ LOG_UNIT_MESSAGE(unit, "%s: %s", msg, strnull(cmdline)),
+ LOG_UNIT_INVOCATION_ID(unit));
+}
+
static int exec_child(
Unit *unit,
const ExecCommand *command,
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
- DynamicCreds *dcreds,
const CGroupContext *cgroup_context,
int socket_fd,
const int named_iofds[static 3],
}
#endif
- r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, keep_fds, n_keep_fds);
+ r = close_remaining_fds(params, runtime, user_lookup_fd, socket_fd, keep_fds, n_keep_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
return log_unit_error_errno(unit, r, "Failed to close unwanted file descriptors: %m");
return log_unit_error_errno(unit, errno, "Failed to update environment: %m");
}
- if (context->dynamic_user && dcreds) {
+ if (context->dynamic_user && runtime && runtime->dynamic_creds) {
_cleanup_strv_free_ char **suggested_paths = NULL;
/* On top of that, make sure we bypass our own NSS module nss-systemd comprehensively for any NSS
return log_oom();
}
- r = dynamic_creds_realize(dcreds, suggested_paths, &uid, &gid);
+ r = dynamic_creds_realize(runtime->dynamic_creds, suggested_paths, &uid, &gid);
if (r < 0) {
*exit_status = EXIT_USER;
if (r == -EILSEQ)
return log_unit_error_errno(unit, SYNTHETIC_ERRNO(ESRCH), "GID validation failed for \""GID_FMT"\"", gid);
}
- if (dcreds->user)
- username = dcreds->user->name;
+ if (runtime->dynamic_creds->user)
+ username = runtime->dynamic_creds->user->name;
} else {
r = get_fixed_user(context, &username, &uid, &gid, &home, &shell);
return log_unit_error_errno(unit, r, "Failed to determine $HOME for user: %m");
}
- /* If a socket is connected to STDIN/STDOUT/STDERR, we
- * must sure to drop O_NONBLOCK */
+ /* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */
if (socket_fd >= 0)
(void) fd_nonblock(socket_fd, false);
}
}
- if (context->network_namespace_path && runtime && runtime->netns_storage_socket[0] >= 0) {
- r = open_shareable_ns_path(runtime->netns_storage_socket, context->network_namespace_path, CLONE_NEWNET);
+ if (context->network_namespace_path && runtime && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
+ r = open_shareable_ns_path(runtime->shared->netns_storage_socket, context->network_namespace_path, CLONE_NEWNET);
if (r < 0) {
*exit_status = EXIT_NETWORK;
return log_unit_error_errno(unit, r, "Failed to open network namespace path %s: %m", context->network_namespace_path);
}
}
- if (context->ipc_namespace_path && runtime && runtime->ipcns_storage_socket[0] >= 0) {
- r = open_shareable_ns_path(runtime->ipcns_storage_socket, context->ipc_namespace_path, CLONE_NEWIPC);
+ if (context->ipc_namespace_path && runtime && runtime->shared && runtime->shared->ipcns_storage_socket[0] >= 0) {
+ r = open_shareable_ns_path(runtime->shared->ipcns_storage_socket, context->ipc_namespace_path, CLONE_NEWIPC);
if (r < 0) {
*exit_status = EXIT_NAMESPACE;
return log_unit_error_errno(unit, r, "Failed to open IPC namespace path %s: %m", context->ipc_namespace_path);
}
}
- if (exec_needs_network_namespace(context) && runtime && runtime->netns_storage_socket[0] >= 0) {
+ if (exec_needs_network_namespace(context) && runtime && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
if (ns_type_supported(NAMESPACE_NET)) {
- r = setup_shareable_ns(runtime->netns_storage_socket, CLONE_NEWNET);
+ r = setup_shareable_ns(runtime->shared->netns_storage_socket, CLONE_NEWNET);
if (r < 0) {
if (ERRNO_IS_PRIVILEGE(r))
log_unit_warning_errno(unit, r,
log_unit_warning(unit, "PrivateNetwork=yes is configured, but the kernel does not support network namespaces, ignoring.");
}
- if (exec_needs_ipc_namespace(context) && runtime && runtime->ipcns_storage_socket[0] >= 0) {
+ if (exec_needs_ipc_namespace(context) && runtime && runtime->shared && runtime->shared->ipcns_storage_socket[0] >= 0) {
if (ns_type_supported(NAMESPACE_IPC)) {
- r = setup_shareable_ns(runtime->ipcns_storage_socket, CLONE_NEWIPC);
+ r = setup_shareable_ns(runtime->shared->ipcns_storage_socket, CLONE_NEWIPC);
if (r == -EPERM)
log_unit_warning_errno(unit, r,
"PrivateIPC=yes is configured, but IPC namespace setup failed, ignoring: %m");
}
#endif
- /* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that we are
- * more aggressive this time since socket_fd and the netns and ipcns fds we don't need anymore. We do keep the exec_fd
- * however if we have it as we want to keep it open until the final execve(). */
+ /* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that
+ * we are more aggressive this time, since we don't need socket_fd and the netns and ipcns fds any
+ * more. We do keep exec_fd however, if we have it, since we need to keep it open until the final
+ * execve(). */
r = close_all_fds(keep_fds, n_keep_fds);
if (r >= 0)
if (needs_sandboxing) {
uint64_t bset;
- /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly
- * requested. (Note this is placed after the general resource limit initialization, see
- * above, in order to take precedence.) */
+ /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly requested.
+ * (Note this is placed after the general resource limit initialization, see above, in order
+ * to take precedence.) */
if (context->restrict_realtime && !context->rlimit[RLIMIT_RTPRIO]) {
if (setrlimit(RLIMIT_RTPRIO, &RLIMIT_MAKE_CONST(0)) < 0) {
*exit_status = EXIT_LIMITS;
} else
final_argv = command->argv;
- if (DEBUG_LOGGING) {
- _cleanup_free_ char *line = NULL;
-
- line = quote_command_line(final_argv, SHELL_ESCAPE_EMPTY);
- if (!line) {
- *exit_status = EXIT_MEMORY;
- return log_oom();
- }
-
- log_unit_struct(unit, LOG_DEBUG,
- "EXECUTABLE=%s", executable,
- LOG_UNIT_MESSAGE(unit, "Executing: %s", line));
- }
+ log_command_line(unit, "Executing", executable, final_argv);
if (exec_fd >= 0) {
uint8_t hot = 1;
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
- DynamicCreds *dcreds,
const CGroupContext *cgroup_context,
pid_t *ret) {
_cleanup_free_ char *subcgroup_path = NULL;
_cleanup_strv_free_ char **files_env = NULL;
size_t n_storage_fds = 0, n_socket_fds = 0;
- _cleanup_free_ char *line = NULL;
pid_t pid;
assert(unit);
if (r < 0)
return log_unit_error_errno(unit, r, "Failed to load environment files: %m");
- line = quote_command_line(command->argv, SHELL_ESCAPE_EMPTY);
- if (!line)
- return log_oom();
-
/* Fork with up-to-date SELinux label database, so the child inherits the up-to-date db
and, until the next SELinux policy changes, we save further reloads in future children. */
mac_selinux_maybe_reload();
- log_unit_struct(unit, LOG_DEBUG,
- LOG_UNIT_MESSAGE(unit, "About to execute %s", line),
- "EXECUTABLE=%s", command->path, /* We won't know the real executable path until we create
- the mount namespace in the child, but we want to log
- from the parent, so we need to use the (possibly
- inaccurate) path here. */
- LOG_UNIT_INVOCATION_ID(unit));
+ /* We won't know the real executable path until we create the mount namespace in the child, but we
+ want to log from the parent, so we use the possibly inaccurate path here. */
+ log_command_line(unit, "About to execute", command->path, command->argv);
if (params->cgroup_path) {
r = exec_parameters_get_cgroup_path(params, &subcgroup_path);
context,
params,
runtime,
- dcreds,
cgroup_context,
socket_fd,
named_iofds,
end = LIST_FIND_TAIL(command, *l);
LIST_INSERT_AFTER(command, *l, end, e);
} else
- *l = e;
+ *l = e;
}
int exec_command_set(ExecCommand *c, const char *path, ...) {
return NULL;
}
-static ExecRuntime* exec_runtime_free(ExecRuntime *rt, bool destroy) {
+static ExecSharedRuntime* exec_shared_runtime_free(ExecSharedRuntime *rt) {
+ if (!rt)
+ return NULL;
+
+ if (rt->manager)
+ (void) hashmap_remove(rt->manager->exec_shared_runtime_by_id, rt->id);
+
+ rt->id = mfree(rt->id);
+ rt->tmp_dir = mfree(rt->tmp_dir);
+ rt->var_tmp_dir = mfree(rt->var_tmp_dir);
+ safe_close_pair(rt->netns_storage_socket);
+ safe_close_pair(rt->ipcns_storage_socket);
+ return mfree(rt);
+}
+
+DEFINE_TRIVIAL_UNREF_FUNC(ExecSharedRuntime, exec_shared_runtime, exec_shared_runtime_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSharedRuntime*, exec_shared_runtime_free);
+
+ExecSharedRuntime* exec_shared_runtime_destroy(ExecSharedRuntime *rt) {
int r;
if (!rt)
return NULL;
- if (rt->manager)
- (void) hashmap_remove(rt->manager->exec_runtime_by_id, rt->id);
+ assert(rt->n_ref > 0);
+ rt->n_ref--;
- /* When destroy is true, then rm_rf tmp_dir and var_tmp_dir. */
+ if (rt->n_ref > 0)
+ return NULL;
- if (destroy && rt->tmp_dir && !streq(rt->tmp_dir, RUN_SYSTEMD_EMPTY)) {
+ if (rt->tmp_dir && !streq(rt->tmp_dir, RUN_SYSTEMD_EMPTY)) {
log_debug("Spawning thread to nuke %s", rt->tmp_dir);
r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir);
rt->tmp_dir = NULL;
}
- if (destroy && rt->var_tmp_dir && !streq(rt->var_tmp_dir, RUN_SYSTEMD_EMPTY)) {
+ if (rt->var_tmp_dir && !streq(rt->var_tmp_dir, RUN_SYSTEMD_EMPTY)) {
log_debug("Spawning thread to nuke %s", rt->var_tmp_dir);
r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir);
rt->var_tmp_dir = NULL;
}
- rt->id = mfree(rt->id);
- rt->tmp_dir = mfree(rt->tmp_dir);
- rt->var_tmp_dir = mfree(rt->var_tmp_dir);
- safe_close_pair(rt->netns_storage_socket);
- safe_close_pair(rt->ipcns_storage_socket);
- return mfree(rt);
-}
-
-static void exec_runtime_freep(ExecRuntime **rt) {
- (void) exec_runtime_free(*rt, false);
+ return exec_shared_runtime_free(rt);
}
-static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
+static int exec_shared_runtime_allocate(ExecSharedRuntime **ret, const char *id) {
_cleanup_free_ char *id_copy = NULL;
- ExecRuntime *n;
+ ExecSharedRuntime *n;
assert(ret);
if (!id_copy)
return -ENOMEM;
- n = new(ExecRuntime, 1);
+ n = new(ExecSharedRuntime, 1);
if (!n)
return -ENOMEM;
- *n = (ExecRuntime) {
+ *n = (ExecSharedRuntime) {
.id = TAKE_PTR(id_copy),
.netns_storage_socket = PIPE_EBADF,
.ipcns_storage_socket = PIPE_EBADF,
return 0;
}
-static int exec_runtime_add(
+static int exec_shared_runtime_add(
Manager *m,
const char *id,
char **tmp_dir,
char **var_tmp_dir,
int netns_storage_socket[2],
int ipcns_storage_socket[2],
- ExecRuntime **ret) {
+ ExecSharedRuntime **ret) {
- _cleanup_(exec_runtime_freep) ExecRuntime *rt = NULL;
+ _cleanup_(exec_shared_runtime_freep) ExecSharedRuntime *rt = NULL;
int r;
assert(m);
/* tmp_dir, var_tmp_dir, {net,ipc}ns_storage_socket fds are donated on success */
- r = exec_runtime_allocate(&rt, id);
+ r = exec_shared_runtime_allocate(&rt, id);
if (r < 0)
return r;
- r = hashmap_ensure_put(&m->exec_runtime_by_id, &string_hash_ops, rt->id, rt);
+ r = hashmap_ensure_put(&m->exec_shared_runtime_by_id, &string_hash_ops, rt->id, rt);
if (r < 0)
return r;
if (ret)
*ret = rt;
- /* do not remove created ExecRuntime object when the operation succeeds. */
+ /* do not remove created ExecSharedRuntime object when the operation succeeds. */
TAKE_PTR(rt);
return 0;
}
-static int exec_runtime_make(
+static int exec_shared_runtime_make(
Manager *m,
const ExecContext *c,
const char *id,
- ExecRuntime **ret) {
+ ExecSharedRuntime **ret) {
_cleanup_(namespace_cleanup_tmpdirp) char *tmp_dir = NULL, *var_tmp_dir = NULL;
_cleanup_close_pair_ int netns_storage_socket[2] = PIPE_EBADF, ipcns_storage_socket[2] = PIPE_EBADF;
assert(c);
assert(id);
- /* It is not necessary to create ExecRuntime object. */
+ /* It is not necessary to create ExecSharedRuntime object. */
if (!exec_needs_network_namespace(c) && !exec_needs_ipc_namespace(c) && !c->private_tmp) {
*ret = NULL;
return 0;
return -errno;
}
- r = exec_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret);
+ r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret);
if (r < 0)
return r;
return 1;
}
-int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool create, ExecRuntime **ret) {
- ExecRuntime *rt;
+int exec_shared_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool create, ExecSharedRuntime **ret) {
+ ExecSharedRuntime *rt;
int r;
assert(m);
assert(id);
assert(ret);
- rt = hashmap_get(m->exec_runtime_by_id, id);
+ rt = hashmap_get(m->exec_shared_runtime_by_id, id);
if (rt)
- /* We already have an ExecRuntime object, let's increase the ref count and reuse it */
+ /* We already have an ExecSharedRuntime object, let's increase the ref count and reuse it */
goto ref;
if (!create) {
}
/* If not found, then create a new object. */
- r = exec_runtime_make(m, c, id, &rt);
+ r = exec_shared_runtime_make(m, c, id, &rt);
if (r < 0)
return r;
if (r == 0) {
- /* When r == 0, it is not necessary to create ExecRuntime object. */
+ /* When r == 0, it is not necessary to create ExecSharedRuntime object. */
*ret = NULL;
return 0;
}
return 1;
}
-ExecRuntime *exec_runtime_unref(ExecRuntime *rt, bool destroy) {
- if (!rt)
- return NULL;
-
- assert(rt->n_ref > 0);
-
- rt->n_ref--;
- if (rt->n_ref > 0)
- return NULL;
-
- return exec_runtime_free(rt, destroy);
-}
-
-int exec_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
- ExecRuntime *rt;
+int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
+ ExecSharedRuntime *rt;
assert(m);
assert(f);
assert(fds);
- HASHMAP_FOREACH(rt, m->exec_runtime_by_id) {
+ HASHMAP_FOREACH(rt, m->exec_shared_runtime_by_id) {
fprintf(f, "exec-runtime=%s", rt->id);
if (rt->tmp_dir)
return 0;
}
-int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds) {
- _cleanup_(exec_runtime_freep) ExecRuntime *rt_create = NULL;
- ExecRuntime *rt;
+int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds) {
+ _cleanup_(exec_shared_runtime_freep) ExecSharedRuntime *rt_create = NULL;
+ ExecSharedRuntime *rt;
int r;
/* This is for the migration from old (v237 or earlier) deserialization text.
* Due to the bug #7790, this may not work with the units that use JoinsNamespaceOf=.
- * Even if the ExecRuntime object originally created by the other unit, we cannot judge
+ * Even if the ExecSharedRuntime object originally created by the other unit, we cannot judge
* so or not from the serialized text, then we always creates a new object owned by this. */
assert(u);
assert(key);
assert(value);
- /* Manager manages ExecRuntime objects by the unit id.
+ /* Manager manages ExecSharedRuntime objects by the unit id.
* So, we omit the serialized text when the unit does not have id (yet?)... */
if (isempty(u->id)) {
log_unit_debug(u, "Invocation ID not found. Dropping runtime parameter.");
return 0;
}
- if (hashmap_ensure_allocated(&u->manager->exec_runtime_by_id, &string_hash_ops) < 0)
+ if (hashmap_ensure_allocated(&u->manager->exec_shared_runtime_by_id, &string_hash_ops) < 0)
return log_oom();
- rt = hashmap_get(u->manager->exec_runtime_by_id, u->id);
+ rt = hashmap_get(u->manager->exec_shared_runtime_by_id, u->id);
if (!rt) {
- if (exec_runtime_allocate(&rt_create, u->id) < 0)
+ if (exec_shared_runtime_allocate(&rt_create, u->id) < 0)
return log_oom();
rt = rt_create;
} else
return 0;
- /* If the object is newly created, then put it to the hashmap which manages ExecRuntime objects. */
+ /* If the object is newly created, then put it to the hashmap which manages ExecSharedRuntime objects. */
if (rt_create) {
- r = hashmap_put(u->manager->exec_runtime_by_id, rt_create->id, rt_create);
+ r = hashmap_put(u->manager->exec_shared_runtime_by_id, rt_create->id, rt_create);
if (r < 0) {
log_unit_debug_errno(u, r, "Failed to put runtime parameter to manager's storage: %m");
return 0;
return 1;
}
-int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
+int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
_cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL;
char *id = NULL;
int r, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1};
}
finalize:
- r = exec_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL);
+ r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to add exec-runtime: %m");
return 0;
}
-void exec_runtime_vacuum(Manager *m) {
- ExecRuntime *rt;
+void exec_shared_runtime_vacuum(Manager *m) {
+ ExecSharedRuntime *rt;
assert(m);
- /* Free unreferenced ExecRuntime objects. This is used after manager deserialization process. */
+ /* Free unreferenced ExecSharedRuntime objects. This is used after manager deserialization process. */
- HASHMAP_FOREACH(rt, m->exec_runtime_by_id) {
+ HASHMAP_FOREACH(rt, m->exec_shared_runtime_by_id) {
if (rt->n_ref > 0)
continue;
- (void) exec_runtime_free(rt, false);
+ (void) exec_shared_runtime_free(rt);
+ }
+}
+
+int exec_runtime_make(ExecSharedRuntime *shared, DynamicCreds *creds, ExecRuntime **ret) {
+ _cleanup_(exec_runtime_freep) ExecRuntime *rt = NULL;
+
+ assert(ret);
+
+ if (!shared && !creds) {
+ *ret = NULL;
+ return 0;
}
+
+ rt = new(ExecRuntime, 1);
+ if (!rt)
+ return -ENOMEM;
+
+ *rt = (ExecRuntime) {
+ .shared = shared,
+ .dynamic_creds = creds,
+ };
+
+ *ret = TAKE_PTR(rt);
+ return 1;
+}
+
+ExecRuntime* exec_runtime_free(ExecRuntime *rt) {
+ if (!rt)
+ return NULL;
+
+ exec_shared_runtime_unref(rt->shared);
+ dynamic_creds_unref(rt->dynamic_creds);
+ return mfree(rt);
+}
+
+ExecRuntime* exec_runtime_destroy(ExecRuntime *rt) {
+ if (!rt)
+ return NULL;
+
+ rt->shared = exec_shared_runtime_destroy(rt->shared);
+ rt->dynamic_creds = dynamic_creds_destroy(rt->dynamic_creds);
+ return exec_runtime_free(rt);
}
void exec_params_clear(ExecParameters *p) {
typedef struct ExecStatus ExecStatus;
typedef struct ExecCommand ExecCommand;
typedef struct ExecContext ExecContext;
+typedef struct ExecSharedRuntime ExecSharedRuntime;
+typedef struct DynamicCreds DynamicCreds;
typedef struct ExecRuntime ExecRuntime;
typedef struct ExecParameters ExecParameters;
typedef struct Manager Manager;
* invocations of commands. Specifically, this allows sharing of /tmp and /var/tmp data as well as network namespaces
* between invocations of commands. This is a reference counted object, with one reference taken by each currently
* active command invocation that wants to share this runtime. */
-struct ExecRuntime {
+struct ExecSharedRuntime {
unsigned n_ref;
Manager *manager;
int ipcns_storage_socket[2];
};
+struct ExecRuntime {
+ ExecSharedRuntime *shared;
+ DynamicCreds *dynamic_creds;
+};
+
typedef enum ExecDirectoryType {
EXEC_DIRECTORY_RUNTIME = 0,
EXEC_DIRECTORY_STATE,
const ExecContext *context,
const ExecParameters *exec_params,
ExecRuntime *runtime,
- DynamicCreds *dynamic_creds,
const CGroupContext *cgroup_context,
pid_t *ret);
void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix);
void exec_status_reset(ExecStatus *s);
-int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *name, bool create, ExecRuntime **ret);
-ExecRuntime *exec_runtime_unref(ExecRuntime *r, bool destroy);
+int exec_shared_runtime_acquire(Manager *m, const ExecContext *c, const char *name, bool create, ExecSharedRuntime **ret);
+ExecSharedRuntime *exec_shared_runtime_destroy(ExecSharedRuntime *r);
+ExecSharedRuntime *exec_shared_runtime_unref(ExecSharedRuntime *r);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSharedRuntime*, exec_shared_runtime_unref);
+
+int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds);
+int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds);
+int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds);
+void exec_shared_runtime_vacuum(Manager *m);
-int exec_runtime_serialize(const Manager *m, FILE *f, FDSet *fds);
-int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds);
-int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds);
-void exec_runtime_vacuum(Manager *m);
+int exec_runtime_make(ExecSharedRuntime *shared, DynamicCreds *creds, ExecRuntime **ret);
+ExecRuntime* exec_runtime_free(ExecRuntime *rt);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecRuntime*, exec_runtime_free);
+ExecRuntime* exec_runtime_destroy(ExecRuntime *rt);
void exec_params_clear(ExecParameters *p);
}
static void initialize_coredump(bool skip_setup) {
-#if ENABLE_COREDUMP
if (getpid_cached() != 1)
return;
* command line later so core dumps can still be generated during early startup and in initrd. */
if (!skip_setup)
disable_coredumps();
-#endif
}
static void initialize_core_pattern(bool skip_setup) {
for (int i = 1; i < argc; i++)
if (streq(argv[i], "--switched-root"))
return false; /* If we switched root, don't skip the setup. */
- else if (streq(argv[i], "--deserialize"))
+ else if (startswith(argv[i], "--deserialize=") || streq(argv[i], "--deserialize"))
found_deserialize = true;
return found_deserialize; /* When we are deserializing, then we are reexecuting, hence avoid the extensive setup */
manager_serialize_uid_refs(m, f);
manager_serialize_gid_refs(m, f);
- r = exec_runtime_serialize(m, f, fds);
+ r = exec_shared_runtime_serialize(m, f, fds);
if (r < 0)
return r;
else if ((val = startswith(l, "destroy-ipc-gid=")))
manager_deserialize_gid_refs_one(m, val);
else if ((val = startswith(l, "exec-runtime=")))
- (void) exec_runtime_deserialize_one(m, val, fds);
+ (void) exec_shared_runtime_deserialize_one(m, val, fds);
else if ((val = startswith(l, "subscribed="))) {
if (strv_extend(&m->deserialized_subscribed, val) < 0)
bus_done(m);
manager_varlink_done(m);
- exec_runtime_vacuum(m);
- hashmap_free(m->exec_runtime_by_id);
+ exec_shared_runtime_vacuum(m);
+ hashmap_free(m->exec_shared_runtime_by_id);
dynamic_user_vacuum(m, false);
hashmap_free(m->dynamic_users);
/* This block is (optionally) done with the reloading counter bumped */
_unused_ _cleanup_(manager_reloading_stopp) Manager *reloading = NULL;
+ /* Make sure we don't have a left-over from a previous run */
+ if (!serialization)
+ (void) rm_rf(m->lookup_paths.transient, 0);
+
/* If we will deserialize make sure that during enumeration this is already known, so we increase the
* counter here already */
if (serialization)
manager_clear_jobs_and_units(m);
lookup_paths_flush_generator(&m->lookup_paths);
lookup_paths_free(&m->lookup_paths);
- exec_runtime_vacuum(m);
+ exec_shared_runtime_vacuum(m);
dynamic_user_vacuum(m, false);
m->uid_refs = hashmap_free(m->uid_refs);
m->gid_refs = hashmap_free(m->gid_refs);
manager_vacuum_gid_refs(m);
/* Release any runtimes no longer referenced */
- exec_runtime_vacuum(m);
+ exec_shared_runtime_vacuum(m);
}
int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
Hashmap *uid_refs;
Hashmap *gid_refs;
- /* ExecRuntime, indexed by their owner unit id */
- Hashmap *exec_runtime_by_id;
+ /* ExecSharedRuntime, indexed by their owner unit id */
+ Hashmap *exec_shared_runtime_by_id;
/* When the user hits C-A-D more than 7 times per 2s, do something immediately... */
RateLimit ctrl_alt_del_ratelimit;
mount_parameters_done(&m->parameters_proc_self_mountinfo);
mount_parameters_done(&m->parameters_fragment);
- m->exec_runtime = exec_runtime_unref(m->exec_runtime, false);
+ m->exec_runtime = exec_runtime_free(m->exec_runtime);
exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX);
m->control_command = NULL;
- dynamic_creds_unref(&m->dynamic_creds);
-
mount_unwatch_control_pid(m);
m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source);
return r;
}
- if (!IN_SET(m->deserialized_state, MOUNT_DEAD, MOUNT_FAILED)) {
- (void) unit_setup_dynamic_creds(u);
+ if (!IN_SET(m->deserialized_state, MOUNT_DEAD, MOUNT_FAILED))
(void) unit_setup_exec_runtime(u);
- }
mount_set_state(m, m->deserialized_state);
return 0;
&m->exec_context,
&exec_params,
m->exec_runtime,
- &m->dynamic_creds,
&m->cgroup_context,
&pid);
if (r < 0)
mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
- m->exec_runtime = exec_runtime_unref(m->exec_runtime, true);
+ m->exec_runtime = exec_runtime_destroy(m->exec_runtime);
unit_destroy_runtime_data(UNIT(m), &m->exec_context);
unit_unref_uid_gid(UNIT(m), true);
- dynamic_creds_destroy(&m->dynamic_creds);
-
/* Any dependencies based on /proc/self/mountinfo are now stale. Let's re-generate dependencies from
* .mount unit. */
(void) mount_add_non_exec_dependencies(m);
.cgroup_context_offset = offsetof(Mount, cgroup_context),
.kill_context_offset = offsetof(Mount, kill_context),
.exec_runtime_offset = offsetof(Mount, exec_runtime),
- .dynamic_creds_offset = offsetof(Mount, dynamic_creds),
.sections =
"Unit\0"
CGroupContext cgroup_context;
ExecRuntime *exec_runtime;
- DynamicCreds dynamic_creds;
MountState state, deserialized_state;
#include "devnum-util.h"
#include "env-util.h"
#include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "glyph-util.h"
if (isempty(host_os_release_id))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
- r = load_extension_release_pairs(mount_entry_source(m), extension_name, /* relax_extension_release_check= */ false, &extension_release);
+ r = load_extension_release_pairs(mount_entry_source(m), IMAGE_SYSEXT, extension_name, /* relax_extension_release_check= */ false, &extension_release);
if (r == -ENOENT && m->ignore)
return 0;
if (r < 0)
host_os_release_version_id,
host_os_release_sysext_level,
/* host_sysext_scope */ NULL, /* Leave empty, we need to accept both system and portable */
- extension_release);
+ extension_release,
+ IMAGE_SYSEXT);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Directory %s extension-release metadata does not match the root's", extension_name);
if (r < 0)
}
if (n_extension_images > 0 || !strv_isempty(extension_directories)) {
- r = parse_env_extension_hierarchies(&hierarchies);
+ r = parse_env_extension_hierarchies(&hierarchies, "SYSTEMD_SYSEXT_HIERARCHIES");
if (r < 0)
return r;
}
#include "constants.h"
#include "dbus-service.h"
#include "dbus-unit.h"
+#include "devnum-util.h"
#include "env-util.h"
#include "escape.h"
#include "exit-status.h"
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m");
}
-usec_t service_restart_usec(Service *s) {
- unsigned n_restarts;
- long double unit;
+usec_t service_restart_usec_next(Service *s) {
+ unsigned n_restarts_next;
+ usec_t value;
assert(s);
- /* s->n_restarts is not yet updated when we're in these states, so let's add 1 to it manually.
- * Note that for SERVICE_AUTO_RESTART a restart job might have been enqueued,
- * i.e. s->n_restarts is already increased. But we assume it's not since the time
- * between job enqueuing and running is usually neglectable compared to the time
- * we'll be sleeping. */
- n_restarts = s->n_restarts +
- (IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART) ? 1 : 0);
+ /* When the service state is in SERVICE_*_BEFORE_AUTO_RESTART or SERVICE_AUTO_RESTART,
+ * we still need to add 1 to s->n_restarts manually because s->n_restarts is not updated
+ * until a restart job is enqueued. Note that for SERVICE_AUTO_RESTART, that might have been
+ * the case, i.e. s->n_restarts is already increased. But we assume it's not since the time
+ * between job enqueuing and running is usually neglectable compared to the time we'll be sleeping. */
+ n_restarts_next = s->n_restarts + 1;
- /* n_restarts can equal to 0 if no restart has happened nor planned */
- if (n_restarts <= 1 ||
+ if (n_restarts_next <= 1 ||
s->restart_steps == 0 ||
s->restart_usec_max == USEC_INFINITY ||
- s->restart_usec == s->restart_usec_max)
- return s->restart_usec;
-
- if (n_restarts > s->restart_steps)
- return s->restart_usec_max;
-
- /* Enforced in service_verify() and above */
- assert(s->restart_usec_max > s->restart_usec);
+ s->restart_usec >= s->restart_usec_max)
+ value = s->restart_usec;
+ else if (n_restarts_next > s->restart_steps)
+ value = s->restart_usec_max;
+ else {
+ /* Enforced in service_verify() and above */
+ assert(s->restart_usec_max > s->restart_usec);
- unit = powl(s->restart_usec_max - s->restart_usec, 1.0L / s->restart_steps);
+ /* ((restart_usec_max - restart_usec)^(1/restart_steps))^(n_restart_next - 1) */
+ value = usec_add(s->restart_usec,
+ (usec_t) powl(s->restart_usec_max - s->restart_usec,
+ (long double) (n_restarts_next - 1) / s->restart_steps));
+ }
- return usec_add(s->restart_usec, (usec_t) powl(unit, n_restarts - 1));
+ log_unit_debug(UNIT(s), "Next restart interval calculated as: %s", FORMAT_TIMESPAN(value, 0));
+ return value;
}
static void service_extend_event_source_timeout(Service *s, sd_event_source *source, usec_t extended) {
static void service_release_fd_store(Service *s) {
assert(s);
- if (s->n_keep_fd_store > 0)
- return;
-
log_unit_debug(UNIT(s), "Releasing all stored fds");
while (s->fd_store)
service_fd_store_unlink(s->fd_store);
assert(s);
+ /* Don't release resources if this is a transitionary failed/dead state */
+ if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
+ return;
+
if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
return;
s->pid_file = mfree(s->pid_file);
s->status_text = mfree(s->status_text);
- s->exec_runtime = exec_runtime_unref(s->exec_runtime, false);
+ s->exec_runtime = exec_runtime_free(s->exec_runtime);
exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
s->control_command = NULL;
s->main_command = NULL;
- dynamic_creds_unref(&s->dynamic_creds);
-
exit_status_set_free(&s->restart_prevent_status);
exit_status_set_free(&s->restart_force_status);
exit_status_set_free(&s->success_status);
service_close_socket_fd(s);
- unit_ref_unset(&s->accept_socket);
-
service_stop_watchdog(s);
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
return service_verify(s);
}
+static void service_dump_fdstore(Service *s, FILE *f, const char *prefix) {
+ assert(s);
+ assert(f);
+ assert(prefix);
+
+ LIST_FOREACH(fd_store, i, s->fd_store) {
+ _cleanup_free_ char *path = NULL;
+ struct stat st;
+ int flags;
+
+ if (fstat(i->fd, &st) < 0) {
+ log_debug_errno(errno, "Failed to stat fdstore entry: %m");
+ continue;
+ }
+
+ flags = fcntl(i->fd, F_GETFL);
+ if (flags < 0) {
+ log_debug_errno(errno, "Failed to get fdstore entry flags: %m");
+ continue;
+ }
+
+ (void) fd_get_path(i->fd, &path);
+
+ fprintf(f,
+ "%s%s '%s' (type=%s; dev=" DEVNUM_FORMAT_STR "; inode=%" PRIu64 "; rdev=" DEVNUM_FORMAT_STR "; path=%s; access=%s)\n",
+ prefix, i == s->fd_store ? "File Descriptor Store Entry:" : " ",
+ i->fdname,
+ inode_type_to_string(st.st_mode),
+ DEVNUM_FORMAT_VAL(st.st_dev),
+ (uint64_t) st.st_ino,
+ DEVNUM_FORMAT_VAL(st.st_rdev),
+ strna(path),
+ accmode_to_string(flags));
+ }
+}
+
static void service_dump(Unit *u, FILE *f, const char *prefix) {
- ServiceExecCommand c;
Service *s = SERVICE(u);
const char *prefix2;
kill_context_dump(&s->kill_context, f, prefix);
exec_context_dump(&s->exec_context, f, prefix);
- for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) {
-
+ for (ServiceExecCommand c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) {
if (!s->exec_command[c])
continue;
prefix, s->n_fd_store_max,
prefix, s->n_fd_store);
+ service_dump_fdstore(s, f, prefix);
+
if (s->open_files)
LIST_FOREACH(open_files, of, s->open_files) {
_cleanup_free_ char *ofs = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
}
- if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
+ if (IN_SET(state,
+ SERVICE_DEAD, SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART)) {
unit_unwatch_all_pids(UNIT(s));
unit_dequeue_rewatch_pids(UNIT(s));
}
return usec_add(UNIT(s)->state_change_timestamp.monotonic, service_timeout_abort_usec(s));
case SERVICE_AUTO_RESTART:
- return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, service_restart_usec(s));
+ return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, service_restart_usec_next(s));
case SERVICE_CLEANING:
return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->exec_context.timeout_clean_usec);
return r;
}
- if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) {
+ if (!IN_SET(s->deserialized_state,
+ SERVICE_DEAD, SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
+ SERVICE_CLEANING)) {
(void) unit_enqueue_rewatch_pids(u);
- (void) unit_setup_dynamic_creds(u);
(void) unit_setup_exec_runtime(u);
}
/* Pass the per-connection socket */
- rfds = new(int, 1);
+ rfds = newdup(int, &s->socket_fd, 1);
if (!rfds)
return -ENOMEM;
- rfds[0] = s->socket_fd;
rfd_names = strv_new("connection");
if (!rfd_names)
&s->exec_context,
&exec_params,
s->exec_runtime,
- &s->dynamic_creds,
&s->cgroup_context,
&pid);
if (r < 0)
if (s->will_auto_restart)
return true;
- if (s->state == SERVICE_AUTO_RESTART)
+ if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
return true;
return unit_will_restart_default(u);
}
static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
- ServiceState end_state;
+ ServiceState end_state, restart_state;
int r;
assert(s);
if (s->result == SERVICE_SUCCESS) {
unit_log_success(UNIT(s));
end_state = SERVICE_DEAD;
+ restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
} else if (s->result == SERVICE_SKIP_CONDITION) {
unit_log_skip(UNIT(s), service_result_to_string(s->result));
end_state = SERVICE_DEAD;
+ restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
} else {
unit_log_failure(UNIT(s), service_result_to_string(s->result));
end_state = SERVICE_FAILED;
+ restart_state = SERVICE_FAILED_BEFORE_AUTO_RESTART;
}
unit_warn_leftover_processes(UNIT(s), unit_log_leftover_process_stop);
s->will_auto_restart = true;
}
- /* Make sure service_release_resources() doesn't destroy our FD store, while we are changing through
- * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
- s->n_keep_fd_store ++;
-
- service_set_state(s, end_state);
-
if (s->will_auto_restart) {
s->will_auto_restart = false;
- r = service_arm_timer(s, /* relative= */ true, service_restart_usec(s));
- if (r < 0) {
- s->n_keep_fd_store--;
+ /* We make two state changes here: one that maps to the high-level UNIT_INACTIVE/UNIT_FAILED
+ * state (i.e. a state indicating deactivation), and then one that that maps to the
+ * high-level UNIT_STARTING state (i.e. a state indicating activation). We do this so that
+ * external software can watch the state changes and see all service failures, even if they
+ * are only transitionary and followed by an automatic restart. We have fine-grained
+ * low-level states for this though so that software can distinguish the permanent UNIT_INACTIVE
+ * state from this transitionary UNIT_INACTIVE state by looking at the low-level states. */
+ service_set_state(s, restart_state);
+
+ r = service_arm_timer(s, /* relative= */ true, service_restart_usec_next(s));
+ if (r < 0)
goto fail;
- }
service_set_state(s, SERVICE_AUTO_RESTART);
- } else
+ } else {
+ service_set_state(s, end_state);
+
/* If we shan't restart, then flush out the restart counter. But don't do that immediately, so that the
* user can still introspect the counter. Do so on the next start. */
s->flush_n_restarts = true;
+ }
/* The new state is in effect, let's decrease the fd store ref counter again. Let's also re-add us to the GC
* queue, so that the fd store is possibly gc'ed again */
- s->n_keep_fd_store--;
unit_add_to_gc_queue(UNIT(s));
/* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
s->notify_access_override = _NOTIFY_ACCESS_INVALID;
/* We want fresh tmpdirs in case service is started again immediately */
- s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
+ s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
/* Also, remove the runtime directory */
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
/* Get rid of the IPC bits of the user */
unit_unref_uid_gid(UNIT(s), true);
- /* Release the user, and destroy it if we are the only remaining owner */
- dynamic_creds_destroy(&s->dynamic_creds);
-
/* Try to delete the pid file. At this point it will be
* out-of-date, and some software might be confused by it, so
* let's remove it. */
if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST))
return 0;
- /* A service that will be restarted must be stopped first to
- * trigger BindsTo and/or OnFailure dependencies. If a user
- * does not want to wait for the holdoff time to elapse, the
- * service should be manually restarted, not started. We
- * simply return EAGAIN here, so that any start jobs stay
- * queued, and assume that the auto restart timer will
- * eventually trigger the restart. */
- if (s->state == SERVICE_AUTO_RESTART)
+ /* A service that will be restarted must be stopped first to trigger BindsTo and/or OnFailure
+ * dependencies. If a user does not want to wait for the holdoff time to elapse, the service should
+ * be manually restarted, not started. We simply return EAGAIN here, so that any start jobs stay
+ * queued, and assume that the auto restart timer will eventually trigger the restart. */
+ if (IN_SET(s->state, SERVICE_AUTO_RESTART, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
return -EAGAIN;
assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED));
/* Don't create restart jobs from manual stops. */
s->forbid_restart = true;
- /* Already on it */
- if (IN_SET(s->state,
- SERVICE_STOP, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
- SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
+ switch (s->state) {
+
+ case SERVICE_STOP:
+ case SERVICE_STOP_SIGTERM:
+ case SERVICE_STOP_SIGKILL:
+ case SERVICE_STOP_POST:
+ case SERVICE_FINAL_WATCHDOG:
+ case SERVICE_FINAL_SIGTERM:
+ case SERVICE_FINAL_SIGKILL:
+ /* Already on it */
return 0;
- /* A restart will be scheduled or is in progress. */
- if (s->state == SERVICE_AUTO_RESTART) {
+ case SERVICE_AUTO_RESTART:
+ /* A restart will be scheduled or is in progress. */
service_set_state(s, SERVICE_DEAD);
return 0;
- }
- /* If there's already something running we go directly into kill mode. */
- if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP_WATCHDOG)) {
+ case SERVICE_CONDITION:
+ case SERVICE_START_PRE:
+ case SERVICE_START:
+ case SERVICE_START_POST:
+ case SERVICE_RELOAD:
+ case SERVICE_RELOAD_SIGNAL:
+ case SERVICE_RELOAD_NOTIFY:
+ case SERVICE_STOP_WATCHDOG:
+ /* If there's already something running we go directly into kill mode. */
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
return 0;
- }
- /* If we are currently cleaning, then abort it, brutally. */
- if (s->state == SERVICE_CLEANING) {
+ case SERVICE_CLEANING:
+ /* If we are currently cleaning, then abort it, brutally. */
service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS);
return 0;
- }
- assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
+ case SERVICE_RUNNING:
+ case SERVICE_EXITED:
+ service_enter_stop(s, SERVICE_SUCCESS);
+ return 1;
- service_enter_stop(s, SERVICE_SUCCESS);
- return 1;
+ case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+ case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+ case SERVICE_DEAD:
+ case SERVICE_FAILED:
+ default:
+ /* Unknown state, or unit_stop() should already have handled these */
+ assert_not_reached();
+ }
}
static int service_reload(Unit *u) {
control_pid_good(s) > 0)
return false;
+ /* Only allow collection of actually dead services, i.e. not those that are in the transitionary
+ * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */
+ if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED))
+ return false;
+
return true;
}
}
static int service_demand_pid_file(Service *s) {
- PathSpec *ps;
+ _cleanup_free_ PathSpec *ps = NULL;
assert(s->pid_file);
assert(!s->pid_file_pathspec);
- ps = new0(PathSpec, 1);
+ ps = new(PathSpec, 1);
if (!ps)
return -ENOMEM;
- ps->unit = UNIT(s);
- ps->path = strdup(s->pid_file);
- if (!ps->path) {
- free(ps);
+ *ps = (PathSpec) {
+ .unit = UNIT(s),
+ .path = strdup(s->pid_file),
+ /* PATH_CHANGED would not be enough. There are daemons (sendmail) that keep their PID file
+ * open all the time. */
+ .type = PATH_MODIFIED,
+ .inotify_fd = -EBADF,
+ };
+
+ if (!ps->path)
return -ENOMEM;
- }
path_simplify(ps->path);
- /* PATH_CHANGED would not be enough. There are daemons (sendmail) that
- * keep their PID file open all the time. */
- ps->type = PATH_MODIFIED;
- ps->inotify_fd = -EBADF;
-
- s->pid_file_pathspec = ps;
+ s->pid_file_pathspec = TAKE_PTR(ps);
return service_watch_pid_file(s);
}
switch (s->state) {
- /* Waiting for SIGCHLD is usually more interesting,
- * because it includes return codes/signals. Which is
- * why we ignore the cgroup events for most cases,
- * except when we don't know pid which to expect the
- * SIGCHLD for. */
+ /* Waiting for SIGCHLD is usually more interesting, because it includes return
+ * codes/signals. Which is why we ignore the cgroup events for most cases, except when we
+ * don't know pid which to expect the SIGCHLD for. */
case SERVICE_START:
if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
* up the cgroup earlier and should do it now. */
case SERVICE_DEAD:
case SERVICE_FAILED:
+ case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+ case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+ case SERVICE_AUTO_RESTART:
unit_prune_cgroup(u);
break;
if (s->restart_usec > 0)
log_unit_debug(UNIT(s),
"Service restart interval %s expired, scheduling restart.",
- FORMAT_TIMESPAN(service_restart_usec(s), USEC_PER_SEC));
+ FORMAT_TIMESPAN(service_restart_usec_next(s), USEC_PER_SEC));
else
log_unit_debug(UNIT(s),
"Service has no hold-off time (RestartSec=0), scheduling restart.");
.cgroup_context_offset = offsetof(Service, cgroup_context),
.kill_context_offset = offsetof(Service, kill_context),
.exec_runtime_offset = offsetof(Service, exec_runtime),
- .dynamic_creds_offset = offsetof(Service, dynamic_creds),
.sections =
"Unit\0"
/* Runtime data of the execution context */
ExecRuntime *exec_runtime;
- DynamicCreds dynamic_creds;
pid_t main_pid, control_pid;
ServiceFDStore *fd_store;
size_t n_fd_store;
unsigned n_fd_store_max;
- unsigned n_keep_fd_store;
char *usb_function_descriptors;
char *usb_function_strings;
int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net);
void service_close_socket_fd(Service *s);
-usec_t service_restart_usec(Service *s);
+usec_t service_restart_usec_next(Service *s);
const char* service_restart_to_string(ServiceRestart i) _const_;
ServiceRestart service_restart_from_string(const char *s) _pure_;
s->peers_by_address = set_free(s->peers_by_address);
- s->exec_runtime = exec_runtime_unref(s->exec_runtime, false);
+ s->exec_runtime = exec_runtime_free(s->exec_runtime);
exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX);
s->control_command = NULL;
- dynamic_creds_unref(&s->dynamic_creds);
-
socket_unwatch_control_pid(s);
unit_ref_unset(&s->service);
if (s->exec_context.network_namespace_path &&
s->exec_runtime &&
- s->exec_runtime->netns_storage_socket[0] >= 0) {
- r = open_shareable_ns_path(s->exec_runtime->netns_storage_socket, s->exec_context.network_namespace_path, CLONE_NEWNET);
+ s->exec_runtime->shared &&
+ s->exec_runtime->shared->netns_storage_socket[0] >= 0) {
+ r = open_shareable_ns_path(s->exec_runtime->shared->netns_storage_socket, s->exec_context.network_namespace_path, CLONE_NEWNET);
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to open network namespace path %s: %m", s->exec_context.network_namespace_path);
}
if (s->exec_context.ipc_namespace_path &&
s->exec_runtime &&
- s->exec_runtime->ipcns_storage_socket[0] >= 0) {
- r = open_shareable_ns_path(s->exec_runtime->ipcns_storage_socket, s->exec_context.ipc_namespace_path, CLONE_NEWIPC);
+ s->exec_runtime->shared &&
+ s->exec_runtime->shared->ipcns_storage_socket[0] >= 0) {
+ r = open_shareable_ns_path(s->exec_runtime->shared->ipcns_storage_socket, s->exec_context.ipc_namespace_path, CLONE_NEWIPC);
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to open IPC namespace path %s: %m", s->exec_context.ipc_namespace_path);
}
if (exec_needs_network_namespace(&s->exec_context) &&
s->exec_runtime &&
- s->exec_runtime->netns_storage_socket[0] >= 0) {
+ s->exec_runtime->shared &&
+ s->exec_runtime->shared->netns_storage_socket[0] >= 0) {
if (ns_type_supported(NAMESPACE_NET)) {
- r = setup_shareable_ns(s->exec_runtime->netns_storage_socket, CLONE_NEWNET);
+ r = setup_shareable_ns(s->exec_runtime->shared->netns_storage_socket, CLONE_NEWNET);
if (r < 0) {
log_unit_error_errno(UNIT(s), r, "Failed to join network namespace: %m");
_exit(EXIT_NETWORK);
return r;
}
- if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED, SOCKET_CLEANING)) {
- (void) unit_setup_dynamic_creds(u);
+ if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED, SOCKET_CLEANING))
(void) unit_setup_exec_runtime(u);
- }
socket_set_state(s, s->deserialized_state);
return 0;
&s->exec_context,
&exec_params,
s->exec_runtime,
- &s->dynamic_creds,
&s->cgroup_context,
&pid);
if (r < 0)
socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
- s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
+ s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
unit_unref_uid_gid(UNIT(s), true);
-
- dynamic_creds_destroy(&s->dynamic_creds);
}
static void socket_enter_signal(Socket *s, SocketState state, SocketResult f);
/* If the service is already active we cannot start the
* socket */
- if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART))
+ if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
return log_unit_error_errno(u, SYNTHETIC_ERRNO(EBUSY), "Socket service %s already active, refusing.", UNIT(service)->id);
}
return;
if (IN_SET(SERVICE(other)->state,
- SERVICE_DEAD, SERVICE_FAILED,
+ SERVICE_DEAD, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED, SERVICE_FAILED_BEFORE_AUTO_RESTART,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
SERVICE_AUTO_RESTART))
socket_enter_listening(s);
.cgroup_context_offset = offsetof(Socket, cgroup_context),
.kill_context_offset = offsetof(Socket, kill_context),
.exec_runtime_offset = offsetof(Socket, exec_runtime),
- .dynamic_creds_offset = offsetof(Socket, dynamic_creds),
.sections =
"Unit\0"
CGroupContext cgroup_context;
ExecRuntime *exec_runtime;
- DynamicCreds dynamic_creds;
/* For Accept=no sockets refers to the one service we'll
* activate. For Accept=yes sockets is either NULL, or filled
s->parameters_fragment.what = mfree(s->parameters_fragment.what);
s->parameters_fragment.options = mfree(s->parameters_fragment.options);
- s->exec_runtime = exec_runtime_unref(s->exec_runtime, false);
+ s->exec_runtime = exec_runtime_free(s->exec_runtime);
exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX);
s->control_command = NULL;
- dynamic_creds_unref(&s->dynamic_creds);
-
swap_unwatch_control_pid(s);
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
return r;
}
- if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED)) {
- (void) unit_setup_dynamic_creds(u);
+ if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED))
(void) unit_setup_exec_runtime(u);
- }
swap_set_state(s, new_state);
return 0;
&s->exec_context,
&exec_params,
s->exec_runtime,
- &s->dynamic_creds,
&s->cgroup_context,
&pid);
if (r < 0)
unit_warn_leftover_processes(UNIT(s), unit_log_leftover_process_stop);
swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
- s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
+ s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
unit_unref_uid_gid(UNIT(s), true);
-
- dynamic_creds_destroy(&s->dynamic_creds);
}
static void swap_enter_active(Swap *s, SwapResult f) {
.cgroup_context_offset = offsetof(Swap, cgroup_context),
.kill_context_offset = offsetof(Swap, kill_context),
.exec_runtime_offset = offsetof(Swap, exec_runtime),
- .dynamic_creds_offset = offsetof(Swap, dynamic_creds),
.sections =
"Unit\0"
CGroupContext cgroup_context;
ExecRuntime *exec_runtime;
- DynamicCreds dynamic_creds;
SwapState state, deserialized_state;
continue;
}
- r = exec_runtime_deserialize_compat(u, l, v, fds);
+ r = exec_shared_runtime_deserialize_compat(u, l, v, fds);
if (r < 0) {
log_unit_warning(u, "Failed to deserialize runtime parameter '%s', ignoring.", l);
continue;
static int unit_add_oomd_dependencies(Unit *u) {
CGroupContext *c;
- bool wants_oomd;
+ CGroupMask mask;
+ int r;
assert(u);
if (!c)
return 0;
- wants_oomd = (c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL);
+ bool wants_oomd = c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL;
if (!wants_oomd)
return 0;
+ if (!cg_all_unified())
+ return 0;
+
+ r = cg_mask_supported(&mask);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to determine supported controllers: %m");
+
+ if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY))
+ return 0;
+
return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, "systemd-oomd.service", true, UNIT_DEPENDENCY_FILE);
}
e = sd_bus_message_get_error(message);
if (e) {
- if (!sd_bus_error_has_name(e, "org.freedesktop.DBus.Error.NameHasNoOwner")) {
+ if (!sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
r = sd_bus_error_get_errno(e);
log_unit_error_errno(u, r,
"Unexpected error response from GetNameOwner(): %s",
return NULL;
}
-char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
+const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
+ assert(s);
assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
+ assert(buf);
_cleanup_free_ char *t = NULL;
- if (!s)
- return NULL;
-
- /* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the
- * allocated return buffer pointer is also written to *buf, except if no escaping was necessary, in
- * which case *buf is set to NULL, and the input pointer is returned as-is. This means the return
- * value always contains a properly escaped version, but *buf when passed only contains a pointer if
- * an allocation was necessary. If *buf is not specified, then the return value always needs to be
- * freed. Callers can use this to optimize memory allocations. */
+ /* Returns a string with any escaping done. If no escaping was necessary, *buf is set to NULL, and
+ * the input pointer is returned as-is. If an allocation was needed, the return buffer pointer is
+ * written to *buf. This means the return value always contains a properly escaped version, but *buf
+ * only contains a pointer if an allocation was made. Callers can use this to optimize memory
+ * allocations. */
if (flags & UNIT_ESCAPE_SPECIFIERS) {
t = specifier_escape(s);
s = t;
}
- /* We either do c-escaping or shell-escaping, to additionally escape characters that we parse for
- * ExecStart= and friend, i.e. '$' and ';' and quotes. */
+ /* We either do C-escaping or shell-escaping, to additionally escape characters that we parse for
+ * ExecStart= and friends, i.e. '$' and ';' and quotes. */
if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
char *t2 = shell_escape(s, "$;'\"");
s = t;
}
- if (buf) {
- *buf = TAKE_PTR(t);
- return (char*) s;
- }
-
- return TAKE_PTR(t) ?: strdup(s);
+ *buf = TAKE_PTR(t);
+ return s;
}
char* unit_concat_strv(char **l, UnitWriteFlags flags) {
}
int unit_setup_exec_runtime(Unit *u) {
+ _cleanup_(exec_shared_runtime_unrefp) ExecSharedRuntime *esr = NULL;
+ _cleanup_(dynamic_creds_unrefp) DynamicCreds *dcreds = NULL;
ExecRuntime **rt;
+ ExecContext *ec;
size_t offset;
Unit *other;
int r;
if (*rt)
return 0;
+ ec = unit_get_exec_context(u);
+ assert(ec);
+
/* Try to get it from somebody else */
UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_JOINS_NAMESPACE_OF) {
- r = exec_runtime_acquire(u->manager, NULL, other->id, false, rt);
- if (r == 1)
- return 1;
+ r = exec_shared_runtime_acquire(u->manager, NULL, other->id, false, &esr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ break;
}
- return exec_runtime_acquire(u->manager, unit_get_exec_context(u), u->id, true, rt);
-}
-
-int unit_setup_dynamic_creds(Unit *u) {
- ExecContext *ec;
- DynamicCreds *dcreds;
- size_t offset;
-
- assert(u);
+ if (!esr) {
+ r = exec_shared_runtime_acquire(u->manager, ec, u->id, true, &esr);
+ if (r < 0)
+ return r;
+ }
- offset = UNIT_VTABLE(u)->dynamic_creds_offset;
- assert(offset > 0);
- dcreds = (DynamicCreds*) ((uint8_t*) u + offset);
+ if (ec->dynamic_user) {
+ r = dynamic_creds_make(u->manager, ec->user, ec->group, &dcreds);
+ if (r < 0)
+ return r;
+ }
- ec = unit_get_exec_context(u);
- assert(ec);
+ r = exec_runtime_make(esr, dcreds, rt);
+ if (r < 0)
+ return r;
- if (!ec->dynamic_user)
- return 0;
+ TAKE_PTR(esr);
+ TAKE_PTR(dcreds);
- return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group);
+ return r;
}
bool unit_type_supported(UnitType t) {
if (r < 0)
return r;
- r = unit_setup_dynamic_creds(u);
- if (r < 0)
- return r;
-
return 0;
}
size_t kill_context_offset;
/* If greater than 0, the offset into the object where the
- * pointer to ExecRuntime is found, if the unit type has
+ * pointer to ExecSharedRuntime is found, if the unit type has
* that */
size_t exec_runtime_offset;
- /* If greater than 0, the offset into the object where the pointer to DynamicCreds is found, if the unit type
- * has that. */
- size_t dynamic_creds_offset;
-
/* The name of the configuration file section with the private settings of this unit */
const char *private_section;
ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_;
int unit_setup_exec_runtime(Unit *u);
-int unit_setup_dynamic_creds(Unit *u);
-char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf);
+const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf);
char* unit_concat_strv(char **l, UnitWriteFlags flags);
int unit_write_setting(Unit *u, UnitWriteFlags flags, const char *name, const char *data);
if (isempty(optarg) || streq(optarg, "auto"))
arg_newline = -1;
else {
- bool b;
-
- r = parse_boolean_argument("--newline=", optarg, &b);
+ r = parse_boolean_argument("--newline=", optarg, NULL);
if (r < 0)
return r;
- arg_newline = b;
+ arg_newline = r;
}
break;
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
- size_t secret_size, blob_size, hash_size, pubkey_size = 0;
+ _cleanup_free_ void *srk_buf = NULL;
+ size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0;
_cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL;
uint16_t pcr_bank, primary_alg;
const char *node;
&blob, &blob_size,
&hash, &hash_size,
&pcr_bank,
- &primary_alg);
+ &primary_alg,
+ &srk_buf,
+ &srk_buf_size);
if (r < 0)
return r;
primary_alg,
blob, blob_size,
hash, hash_size,
+ srk_buf, srk_buf_size,
&secret2, &secret2_size);
if (r < 0)
return r;
hash, hash_size,
use_pin ? binary_salt : NULL,
use_pin ? sizeof(binary_salt) : 0,
+ srk_buf, srk_buf_size,
flags,
&v);
if (r < 0)
case ARG_VERSION:
return version();
- case ARG_FIDO2_WITH_PIN: {
- bool lock_with_pin;
-
- r = parse_boolean_argument("--fido2-with-client-pin=", optarg, &lock_with_pin);
+ case ARG_FIDO2_WITH_PIN:
+ r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, lock_with_pin);
+ SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r);
break;
- }
-
- case ARG_FIDO2_WITH_UP: {
- bool lock_with_up;
- r = parse_boolean_argument("--fido2-with-user-presence=", optarg, &lock_with_up);
+ case ARG_FIDO2_WITH_UP:
+ r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, lock_with_up);
+ SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r);
break;
- }
-
- case ARG_FIDO2_WITH_UV: {
- bool lock_with_uv;
- r = parse_boolean_argument("--fido2-with-user-verification=", optarg, &lock_with_uv);
+ case ARG_FIDO2_WITH_UV:
+ r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, lock_with_uv);
+ SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r);
break;
- }
case ARG_PASSWORD:
if (arg_enroll_type >= 0)
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
+ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
&policy_hash_size,
&salt,
&salt_size,
+ &srk_buf,
+ &srk_buf_size,
&flags);
if (r < 0)
return log_debug_open_error(cd, r);
policy_hash_size,
salt,
salt_size,
+ srk_buf,
+ srk_buf_size,
flags,
&decrypted_key,
&decrypted_key_size);
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags = 0;
&policy_hash_size,
&salt,
&salt_size,
+ &srk_buf,
+ &srk_buf_size,
&flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt));
+ crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf));
}
/*
size_t policy_hash_size,
const void *salt,
size_t salt_size,
+ const void *srk_buf,
+ size_t srk_buf_size,
TPM2Flags flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
primary_alg,
key_data, key_data_size,
policy_hash, policy_hash_size,
+ srk_buf, srk_buf_size,
ret_decrypted_key, ret_decrypted_key_size);
}
size_t policy_hash_size,
const void *salt,
size_t salt_size,
+ const void *srk_buf,
+ size_t srk_buf_size,
TPM2Flags flags,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
size_t policy_hash_size,
const void *salt,
size_t salt_size,
+ const void *srk_buf,
+ size_t srk_buf_size,
TPM2Flags flags,
usec_t until,
bool headless,
blob_size,
policy_hash,
policy_hash_size,
+ srk_buf,
+ srk_buf_size,
ret_decrypted_key,
ret_decrypted_key_size);
blob_size,
policy_hash,
policy_hash_size,
+ srk_buf,
+ srk_buf_size,
ret_decrypted_key,
ret_decrypted_key_size);
/* We get this error in case there is an authentication policy mismatch. This should
size_t *ret_policy_hash_size,
void **ret_salt,
size_t *ret_salt_size,
+ void **ret_srk_buf,
+ size_t *ret_srk_buf_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags;
&blob, &blob_size,
&policy_hash, &policy_hash_size,
&salt, &salt_size,
+ &srk_buf, &srk_buf_size,
&flags);
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
*ret_salt_size = salt_size;
*ret_keyslot = keyslot;
*ret_token = token;
+ *ret_srk_buf = TAKE_PTR(srk_buf);
+ *ret_srk_buf_size = srk_buf_size;
*ret_flags = flags;
return 0;
}
size_t policy_hash_size,
const void *salt,
size_t salt_size,
+ const void *srk_buf,
+ size_t salt_srk_buf_size,
TPM2Flags flags,
usec_t until,
bool headless,
size_t *ret_policy_hash_size,
void **ret_salt,
size_t *ret_salt_size,
+ void **ret_srk_buf,
+ size_t *ret_srk_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token);
size_t policy_hash_size,
const void *salt,
size_t salt_size,
+ const void *srk_buf,
+ size_t salt_srk_buf_size,
TPM2Flags flags,
usec_t until,
bool headless,
size_t *ret_policy_hash_size,
void **ret_salt,
size_t *ret_salt_size,
+ void **ret_srk_buf,
+ size_t *ret_srk_size,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
key_data, key_data_size,
/* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
/* salt= */ NULL, /* salt_size= */ 0,
+ /* srk_buf= */ NULL, /* srk_buf_size= */ 0,
arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
until,
arg_headless,
* works. */
for (;;) {
- _cleanup_free_ void *pubkey = NULL, *salt = NULL;
- size_t pubkey_size = 0, salt_size = 0;
+ _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
+ size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
&blob, &blob_size,
&policy_hash, &policy_hash_size,
&salt, &salt_size,
+ &srk_buf, &srk_buf_size,
&tpm2_flags,
&keyslot,
&token);
blob, blob_size,
policy_hash, policy_hash_size,
salt, salt_size,
+ srk_buf, srk_buf_size,
tpm2_flags,
until,
arg_headless,
#include "kbd-util.h"
#include "libcrypt-util.h"
#include "locale-util.h"
+#include "lock-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "mkdir.h"
static bool arg_delete_root_password = false;
static bool arg_root_password_is_hashed = false;
static bool arg_welcome = true;
+static bool arg_reset = false;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
}
}
+static int should_configure(int dir_fd, const char *filename) {
+ assert(dir_fd >= 0);
+ assert(filename);
+
+ if (faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to access %s: %m", filename);
+
+ return true; /* missing */
+ }
+
+ return arg_force; /* exists, but if --force was given we should still configure the file. */
+}
+
static bool locale_is_ok(const char *name) {
if (arg_root)
return 0;
}
-static int process_locale(void) {
- const char *etc_localeconf;
+static int process_locale(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
char* locales[3];
unsigned i = 0;
int r;
- etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
- if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s, assuming locale information has been configured.",
- etc_localeconf);
- return 0;
- }
+ assert(rfd >= 0);
+
+ pfd = chase_and_open_parent_at(rfd, "/etc/locale.conf",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+ &f);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m");
+
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/locale.conf, assuming locale information has been configured.");
+ if (r <= 0)
+ return r;
- if (arg_copy_locale && arg_root) {
+ r = dir_fd_is_root(rfd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
- (void) mkdir_parents(etc_localeconf, 0755);
- r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, COPY_REFLINK);
+ if (arg_copy_locale && r == 0) {
+ r = copy_file_atomic_at(AT_FDCWD, "/etc/locale.conf", pfd, f, 0644, COPY_REFLINK);
if (r != -ENOENT) {
if (r < 0)
- return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
+ return log_error_errno(r, "Failed to copy host's /etc/locale.conf: %m");
- log_info("%s copied.", etc_localeconf);
+ log_info("Copied host's /etc/locale.conf.");
return 0;
}
}
locales[i] = NULL;
- (void) mkdir_parents(etc_localeconf, 0755);
- r = write_env_file(etc_localeconf, locales);
+ r = write_env_file_at(pfd, f, locales);
if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
+ return log_error_errno(r, "Failed to write /etc/locale.conf: %m");
- log_info("%s written.", etc_localeconf);
+ log_info("/etc/locale.conf written.");
return 0;
}
kmaps, 60, keymap_is_valid, &arg_keymap);
}
-static int process_keymap(void) {
- const char *etc_vconsoleconf;
+static int process_keymap(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
char **keymap;
int r;
- etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
- if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s, assuming console has been configured.",
- etc_vconsoleconf);
- return 0;
- }
+ assert(rfd >= 0);
- if (arg_copy_keymap && arg_root) {
+ pfd = chase_and_open_parent_at(rfd, "/etc/vconsole.conf",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+ &f);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m");
- (void) mkdir_parents(etc_vconsoleconf, 0755);
- r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, COPY_REFLINK);
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/vconsole.conf, assuming console has been configured.");
+ if (r <= 0)
+ return r;
+
+ r = dir_fd_is_root(rfd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+ if (arg_copy_keymap && r == 0) {
+ r = copy_file_atomic_at(AT_FDCWD, "/etc/vconsole.conf", pfd, f, 0644, COPY_REFLINK);
if (r != -ENOENT) {
if (r < 0)
- return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
+ return log_error_errno(r, "Failed to copy host's /etc/vconsole.conf: %m");
- log_info("%s copied.", etc_vconsoleconf);
+ log_info("Copied host's /etc/vconsole.conf.");
return 0;
}
}
keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
- r = mkdir_parents(etc_vconsoleconf, 0755);
- if (r < 0)
- return log_error_errno(r, "Failed to create the parent directory of %s: %m", etc_vconsoleconf);
-
- r = write_env_file(etc_vconsoleconf, keymap);
+ r = write_env_file_at(pfd, f, keymap);
if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
+ return log_error_errno(r, "Failed to write /etc/vconsole.conf: %m");
- log_info("%s written.", etc_vconsoleconf);
+ log_info("/etc/vconsole.conf written.");
return 0;
}
return 0;
}
-static int process_timezone(void) {
- const char *etc_localtime, *e;
+static int process_timezone(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
+ const char *e;
int r;
- etc_localtime = prefix_roota(arg_root, "/etc/localtime");
- if (laccess(etc_localtime, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s, assuming timezone has been configured.",
- etc_localtime);
- return 0;
- }
+ assert(rfd >= 0);
- if (arg_copy_timezone && arg_root) {
- _cleanup_free_ char *p = NULL;
+ pfd = chase_and_open_parent_at(rfd, "/etc/localtime",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+ &f);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/localtime: %m");
- r = readlink_malloc("/etc/localtime", &p);
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/localtime, assuming timezone has been configured.");
+ if (r <= 0)
+ return r;
+
+ r = dir_fd_is_root(rfd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+ if (arg_copy_timezone && r == 0) {
+ _cleanup_free_ char *s = NULL;
+
+ r = readlink_malloc("/etc/localtime", &s);
if (r != -ENOENT) {
if (r < 0)
- return log_error_errno(r, "Failed to read host timezone: %m");
+ return log_error_errno(r, "Failed to read host's /etc/localtime: %m");
- (void) mkdir_parents(etc_localtime, 0755);
- r = symlink_atomic(p, etc_localtime);
+ r = symlinkat_atomic_full(s, pfd, f, /* make_relative= */ false);
if (r < 0)
- return log_error_errno(r, "Failed to create %s symlink: %m", etc_localtime);
+ return log_error_errno(r, "Failed to create /etc/localtime symlink: %m");
- log_info("%s copied.", etc_localtime);
+ log_info("Copied host's /etc/localtime.");
return 0;
}
}
e = strjoina("../usr/share/zoneinfo/", arg_timezone);
- (void) mkdir_parents(etc_localtime, 0755);
- r = symlink_atomic(e, etc_localtime);
+ r = symlinkat_atomic_full(e, pfd, f, /* make_relative= */ false);
if (r < 0)
- return log_error_errno(r, "Failed to create %s symlink: %m", etc_localtime);
+ return log_error_errno(r, "Failed to create /etc/localtime symlink: %m");
- log_info("%s written", etc_localtime);
+ log_info("/etc/localtime written");
return 0;
}
return 0;
}
-static int process_hostname(void) {
- const char *etc_hostname;
+static int process_hostname(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
int r;
- etc_hostname = prefix_roota(arg_root, "/etc/hostname");
- if (laccess(etc_hostname, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s, assuming hostname has been configured.",
- etc_hostname);
- return 0;
- }
+ assert(rfd >= 0);
+
+ pfd = chase_and_open_parent_at(rfd, "/etc/hostname",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN,
+ &f);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/hostname: %m");
+
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/hostname, assuming hostname has been configured.");
+ if (r <= 0)
+ return r;
r = prompt_hostname();
if (r < 0)
if (isempty(arg_hostname))
return 0;
- r = write_string_file(etc_hostname, arg_hostname,
- WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
- (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+ r = write_string_file_at(pfd, f, arg_hostname,
+ WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
+ return log_error_errno(r, "Failed to write /etc/hostname: %m");
- log_info("%s written.", etc_hostname);
+ log_info("/etc/hostname written.");
return 0;
}
-static int process_machine_id(void) {
- const char *etc_machine_id;
+static int process_machine_id(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
int r;
- etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
- if (laccess(etc_machine_id, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s, assuming machine-id has been configured.",
- etc_machine_id);
- return 0;
- }
+ assert(rfd >= 0);
+
+ pfd = chase_and_open_parent_at(rfd, "/etc/machine-id",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+ &f);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/machine-id: %m");
+
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/machine-id, assuming machine-id has been configured.");
+ if (r <= 0)
+ return r;
if (sd_id128_is_null(arg_machine_id)) {
log_debug("Initialization of machine-id was not requested, skipping.");
return 0;
}
- r = write_string_file(etc_machine_id, SD_ID128_TO_STRING(arg_machine_id),
- WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
- (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+ r = write_string_file_at(pfd, "machine-id", SD_ID128_TO_STRING(arg_machine_id),
+ WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
if (r < 0)
- return log_error_errno(r, "Failed to write machine id: %m");
+ return log_error_errno(r, "Failed to write /etc/machine id: %m");
- log_info("%s written.", etc_machine_id);
+ log_info("/etc/machine-id written.");
return 0;
}
return 0;
}
-static int find_shell(const char *path, const char *root) {
+static int find_shell(int rfd, const char *path) {
int r;
assert(path);
if (!valid_shell(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path);
- r = chase(path, root, CHASE_PREFIX_ROOT, NULL, NULL);
- if (r < 0) {
- const char *p;
- p = prefix_roota(root, path);
- return log_error_errno(r, "Failed to resolve shell %s: %m", p);
- }
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve shell %s: %m", path);
return 0;
}
-static int prompt_root_shell(void) {
+static int prompt_root_shell(int rfd) {
int r;
if (arg_root_shell)
break;
}
- r = find_shell(s, arg_root);
+ r = find_shell(rfd, s);
if (r < 0)
continue;
return 0;
}
-static int write_root_passwd(const char *passwd_path, const char *password, const char *shell) {
+static int write_root_passwd(int etc_fd, const char *password, const char *shell) {
_cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
int r;
assert(password);
- r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
+ r = fopen_temporary_at_label(etc_fd, "passwd", "passwd", &passwd, &passwd_tmp);
if (r < 0)
return r;
- original = fopen(passwd_path, "re");
+ r = xfopenat(etc_fd, "passwd", "re", O_NOFOLLOW, &original);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
if (original) {
struct passwd *i;
if (r < 0)
return r;
- r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
+ r = renameat_and_apply_smack_floor_label(etc_fd, passwd_tmp, etc_fd, "passwd");
if (r < 0)
return r;
return 0;
}
-static int write_root_shadow(const char *shadow_path, const char *hashed_password) {
+static int write_root_shadow(int etc_fd, const char *hashed_password) {
_cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
_cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
int r;
assert(hashed_password);
- r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
+ r = fopen_temporary_at_label(etc_fd, "shadow", "shadow", &shadow, &shadow_tmp);
if (r < 0)
return r;
- original = fopen(shadow_path, "re");
+ r = xfopenat(etc_fd, "shadow", "re", O_NOFOLLOW, &original);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
if (original) {
struct spwd *i;
if (r < 0)
return r;
- r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
+ r = renameat_and_apply_smack_floor_label(etc_fd, shadow_tmp, etc_fd, "shadow");
if (r < 0)
return r;
return 0;
}
-static int process_root_account(void) {
- _cleanup_close_ int lock = -EBADF;
+static int process_root_account(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_(release_lock_file) LockFile lock = LOCK_FILE_INIT;
_cleanup_(erase_and_freep) char *_hashed_password = NULL;
const char *password, *hashed_password;
- const char *etc_passwd, *etc_shadow;
- int r;
+ int k = 0, r;
- etc_passwd = prefix_roota(arg_root, "/etc/passwd");
- etc_shadow = prefix_roota(arg_root, "/etc/shadow");
+ assert(rfd >= 0);
- if (laccess(etc_passwd, F_OK) >= 0 && laccess(etc_shadow, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s and %s, assuming root account has been initialized.",
- etc_passwd, etc_shadow);
+ pfd = chase_and_open_parent_at(rfd, "/etc/passwd",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+ NULL);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/passwd: %m");
+
+ /* Ensure that passwd and shadow are in the same directory and are not symlinks. */
+
+ FOREACH_STRING(s, "passwd", "shadow") {
+ r = verify_regular_at(pfd, s, /* follow = */ false);
+ if (IN_SET(r, -EISDIR, -ELOOP, -EBADFD))
+ return log_error_errno(r, "/etc/%s is not a regular file", s);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to check whether /etc/%s is a regular file: %m", s);
+
+ r = should_configure(pfd, s);
+ if (r < 0)
+ return r;
+
+ k += r;
+ }
+
+ if (k == 0) {
+ log_debug("Found /etc/passwd and /etc/shadow, assuming root account has been initialized.");
return 0;
}
return 0;
}
- lock = take_etc_passwd_lock(arg_root);
- if (lock < 0)
- return log_error_errno(lock, "Failed to take a lock on %s: %m", etc_passwd);
+ r = make_lock_file_at(pfd, ETC_PASSWD_LOCK_FILENAME, LOCK_EX, &lock);
+ if (r < 0)
+ return log_error_errno(r, "Failed to take a lock on /etc/passwd: %m");
+
+ k = dir_fd_is_root(rfd);
+ if (k < 0)
+ return log_error_errno(k, "Failed to check if directory file descriptor is root: %m");
- if (arg_copy_root_shell && arg_root) {
+ if (arg_copy_root_shell && k == 0) {
struct passwd *p;
errno = 0;
return log_oom();
}
- r = prompt_root_shell();
+ r = prompt_root_shell(rfd);
if (r < 0)
return r;
- if (arg_copy_root_password && arg_root) {
+ if (arg_copy_root_password && k == 0) {
struct spwd *p;
errno = 0;
else
password = hashed_password = PASSWORD_LOCKED_AND_INVALID;
- r = write_root_passwd(etc_passwd, password, arg_root_shell);
+ r = write_root_passwd(pfd, password, arg_root_shell);
if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", etc_passwd);
+ return log_error_errno(r, "Failed to write /etc/passwd: %m");
- log_info("%s written", etc_passwd);
+ log_info("/etc/passwd written.");
- r = write_root_shadow(etc_shadow, hashed_password);
+ r = write_root_shadow(pfd, hashed_password);
if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
+ return log_error_errno(r, "Failed to write /etc/shadow: %m");
- log_info("%s written.", etc_shadow);
+ log_info("/etc/shadow written.");
return 0;
}
-static int process_kernel_cmdline(void) {
- const char *etc_kernel_cmdline;
+static int process_kernel_cmdline(int rfd) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
int r;
- etc_kernel_cmdline = prefix_roota(arg_root, "/etc/kernel/cmdline");
- if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force) {
- log_debug("Found %s, assuming kernel has been configured.",
- etc_kernel_cmdline);
- return 0;
- }
+ assert(rfd >= 0);
+
+ pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline",
+ CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+ &f);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m");
+
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/kernel/cmdline, assuming kernel command line has been configured.");
+ if (r <= 0)
+ return r;
if (!arg_kernel_cmdline) {
log_debug("Creation of /etc/kernel/cmdline was not requested, skipping.");
return 0;
}
- r = write_string_file(etc_kernel_cmdline, arg_kernel_cmdline,
- WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
- (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+ r = write_string_file_at(pfd, "cmdline", arg_kernel_cmdline,
+ WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", etc_kernel_cmdline);
+ return log_error_errno(r, "Failed to write /etc/kernel/cmdline: %m");
+
+ log_info("/etc/kernel/cmdline written.");
+ return 0;
+}
+
+static int reset_one(int rfd, const char *path) {
+ _cleanup_close_ int pfd = -EBADF;
+ _cleanup_free_ char *f = NULL;
+
+ assert(rfd >= 0);
+ assert(path);
+
+ pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f);
+ if (pfd == -ENOENT)
+ return 0;
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to resolve %s: %m", path);
+
+ if (unlinkat(pfd, f, 0) < 0)
+ return errno == ENOENT ? 0 : log_error_errno(errno, "Failed to remove %s: %m", path);
+
+ log_info("Removed %s", path);
+ return 0;
+}
+
+static int process_reset(int rfd) {
+ int r;
+
+ assert(rfd >= 0);
+
+ if (!arg_reset)
+ return 0;
+
+ FOREACH_STRING(p,
+ "/etc/locale.conf",
+ "/etc/vconsole.conf",
+ "/etc/hostname",
+ "/etc/machine-id",
+ "/etc/kernel/cmdline") {
+ r = reset_one(rfd, p);
+ if (r < 0)
+ return r;
+ }
- log_info("%s written.", etc_kernel_cmdline);
return 0;
}
" --force Overwrite existing files\n"
" --delete-root-password Delete root password\n"
" --welcome=no Disable the welcome text\n"
+ " --reset Remove existing files\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
link);
ARG_FORCE,
ARG_DELETE_ROOT_PASSWORD,
ARG_WELCOME,
+ ARG_RESET,
};
static const struct option options[] = {
{ "force", no_argument, NULL, ARG_FORCE },
{ "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD },
{ "welcome", required_argument, NULL, ARG_WELCOME },
+ { "reset", no_argument, NULL, ARG_RESET },
{}
};
break;
case ARG_ROOT_SHELL:
- r = find_shell(optarg, arg_root);
- if (r < 0)
- return r;
-
r = free_and_strdup(&arg_root_shell, optarg);
if (r < 0)
return log_oom();
arg_welcome = r;
break;
+ case ARG_RESET:
+ arg_reset = true;
+ break;
+
case '?':
return -EINVAL;
static int run(int argc, char *argv[]) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
+ _cleanup_close_ int rfd = -EBADF;
int r;
r = parse_argv(argc, argv);
DISSECT_IMAGE_FSCK |
DISSECT_IMAGE_GROWFS,
&unlink_dir,
- /* ret_dir_fd= */ NULL,
+ &rfd,
&loop_device);
if (r < 0)
return r;
arg_root = strdup(unlink_dir);
if (!arg_root)
return log_oom();
+ } else {
+ rfd = open(empty_to_root(arg_root), O_DIRECTORY|O_CLOEXEC);
+ if (rfd < 0)
+ return log_error_errno(errno, "Failed to open %s: %m", empty_to_root(arg_root));
+ }
+
+ LOG_SET_PREFIX(arg_image ?: arg_root);
+
+ if (arg_root_shell) {
+ r = find_shell(rfd, arg_root_shell);
+ if (r < 0)
+ return r;
}
- r = process_locale();
+ r = process_reset(rfd);
+ if (r < 0)
+ return r;
+
+ r = process_locale(rfd);
if (r < 0)
return r;
- r = process_keymap();
+ r = process_keymap(rfd);
if (r < 0)
return r;
- r = process_timezone();
+ r = process_timezone(rfd);
if (r < 0)
return r;
- r = process_hostname();
+ r = process_hostname(rfd);
if (r < 0)
return r;
- r = process_machine_id();
+ r = process_machine_id(rfd);
if (r < 0)
return r;
- r = process_root_account();
+ r = process_root_account(rfd);
if (r < 0)
return r;
- r = process_kernel_cmdline();
+ r = process_kernel_cmdline(rfd);
if (r < 0)
return r;
}
if (sysfs_check < 0) {
- r = getenv_bool_secure("SYSTEMD_SYSFS_CHECK");
- if (r < 0 && r != -ENXIO)
- log_debug_errno(r, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m");
- sysfs_check = r != 0;
+ k = getenv_bool_secure("SYSTEMD_SYSFS_CHECK");
+ if (k < 0 && k != -ENXIO)
+ log_debug_errno(k, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m");
+ sysfs_check = k != 0;
}
if (sysfs_check && is_device_path(what)) {
strv_uniq(arg_fido2_device);
break;
- case ARG_FIDO2_WITH_PIN: {
- bool lock_with_pin;
-
- r = parse_boolean_argument("--fido2-with-client-pin=", optarg, &lock_with_pin);
+ case ARG_FIDO2_WITH_PIN:
+ r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, lock_with_pin);
+ SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r);
break;
- }
- case ARG_FIDO2_WITH_UP: {
- bool lock_with_up;
-
- r = parse_boolean_argument("--fido2-with-user-presence=", optarg, &lock_with_up);
+ case ARG_FIDO2_WITH_UP:
+ r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, lock_with_up);
+ SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r);
break;
- }
- case ARG_FIDO2_WITH_UV: {
- bool lock_with_uv;
-
- r = parse_boolean_argument("--fido2-with-user-verification=", optarg, &lock_with_uv);
+ case ARG_FIDO2_WITH_UV:
+ r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL);
if (r < 0)
return r;
- SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, lock_with_uv);
+ SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r);
break;
- }
case ARG_RECOVERY_KEY:
r = parse_boolean(optarg);
break;
case ARG_DROP_CACHES: {
- bool drop_caches;
-
if (isempty(optarg)) {
r = drop_from_identity("dropCaches");
if (r < 0)
break;
}
- r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
+ r = parse_boolean_argument("--drop-caches=", optarg, NULL);
if (r < 0)
return r;
if (json_variant_elements(parameters) > 0)
return varlink_error_invalid_parameter(link, parameters);
- log_info("Received client request to rotate journal.");
+ log_info("Received client request to sync journal.");
/* We don't do the main work now, but instead enqueue a deferred event loop job which will do
* it. That job is scheduled at low priority, so that we return from this method call only after all
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2235
-set -eu
+set -eux
set -o pipefail
+export SYSTEMD_LOG_LEVEL=debug
+
kernel_install="${1:?}"
plugin="${2:?}"
+if [[ -d "${PROJECT_BUILD_ROOT:-}" ]]; then
+ bootctl="${PROJECT_BUILD_ROOT}/bootctl"
+else
+ bootctl=
+fi
D="$(mktemp --tmpdir --directory "test-kernel-install.XXXXXXXXXX")"
-export _KERNEL_INSTALL_BOOTCTL="$PROJECT_BUILD_ROOT/bootctl"
-
# shellcheck disable=SC2064
trap "rm -rf '$D'" EXIT INT QUIT PIPE
mkdir -p "$D/boot"
"$kernel_install" inspect
"$kernel_install" -v remove 1.1.1
-test ! -f "$entry"
-test ! -f "$BOOT_ROOT/the-token/1.1.1/linux"
-test ! -f "$BOOT_ROOT/the-token/1.1.1/initrd"
+test ! -e "$entry"
+test ! -e "$BOOT_ROOT/the-token/1.1.1/linux"
+test ! -e "$BOOT_ROOT/the-token/1.1.1/initrd"
# Invoke kernel-install as installkernel
ln -s --relative -v "$kernel_install" "$D/sources/installkernel"
grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
-if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
+if test -x "$bootctl"; then
echo "Testing bootctl"
e2="${entry%+*}_2.conf"
cp "$entry" "$e2"
# create file that is not referenced. Check if cleanup removes
# it but leaves the rest alone
:> "$BOOT_ROOT/the-token/1.1.2/initrd"
- "$_KERNEL_INSTALL_BOOTCTL" --root="$D" cleanup
+ "$bootctl" --root="$D" cleanup
test ! -e "$BOOT_ROOT/the-token/1.1.2/initrd"
test -e "$BOOT_ROOT/the-token/1.1.2/linux"
test -e "$BOOT_ROOT/the-token/1.1.1/linux"
test -e "$BOOT_ROOT/the-token/1.1.1/initrd"
# now remove duplicated entry and make sure files are left over
- "$_KERNEL_INSTALL_BOOTCTL" --root="$D" unlink "${e2##*/}"
+ "$bootctl" --root="$D" unlink "${e2##*/}"
test -e "$BOOT_ROOT/the-token/1.1.1/linux"
test -e "$BOOT_ROOT/the-token/1.1.1/initrd"
test -e "$entry"
# remove last entry referencing those files
entry_id="${entry##*/}"
entry_id="${entry_id%+*}.conf"
- "$_KERNEL_INSTALL_BOOTCTL" --root="$D" unlink "$entry_id"
+ "$bootctl" --root="$D" unlink "$entry_id"
test ! -e "$entry"
test ! -e "$BOOT_ROOT/the-token/1.1.1/linux"
test ! -e "$BOOT_ROOT/the-token/1.1.1/initrd"
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH),
SD_BUS_ERROR_MAP(BUS_ERROR_NOT_REFERENCED, EUNATCH),
SD_BUS_ERROR_MAP(BUS_ERROR_DISK_FULL, ENOSPC),
+ SD_BUS_ERROR_MAP(BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED,
+ EHOSTDOWN),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT),
#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy"
#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive"
#define BUS_ERROR_FREEZE_CANCELLED "org.freedesktop.systemd1.FreezeCancelled"
+#define BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED \
+ "org.freedesktop.systemd1.FileDescriptorStoreDisabled"
#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
"s",
unique ?: name);
if (r < 0) {
- if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"))
+ if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN))
return r;
/* no data is fine */
#include "strv.h"
BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = {
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed", EACCES),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory", ENOMEM),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown", EHOSTUNREACH),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner", ENXIO),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply", ETIMEDOUT),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError", EIO),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress", EADDRNOTAVAIL),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported", EOPNOTSUPP),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded", ENOBUFS),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied", EACCES),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed", EACCES),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer", EHOSTDOWN),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout", ETIMEDOUT),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork", ENONET),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse", EADDRINUSE),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected", ECONNRESET),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs", EINVAL),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound", ENOENT),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists", EEXIST),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod", EBADR),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject", EBADR),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface", EBADR),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty", EBADR),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly", EROFS),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown", ESRCH),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature", EINVAL),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage", EBADMSG),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut", ETIMEDOUT),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid", EINVAL),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent", EINVAL),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound", ENOENT),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown", ESRCH),
- SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse", EBUSY),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_FAILED, EACCES),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_MEMORY, ENOMEM),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_SERVICE_UNKNOWN, EHOSTUNREACH),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_NAME_HAS_NO_OWNER, ENXIO),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_REPLY, ETIMEDOUT),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_IO_ERROR, EIO),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_BAD_ADDRESS, EADDRNOTAVAIL),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_NOT_SUPPORTED, EOPNOTSUPP),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_LIMITS_EXCEEDED, ENOBUFS),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_ACCESS_DENIED, EACCES),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_AUTH_FAILED, EACCES),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_SERVER, EHOSTDOWN),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_TIMEOUT, ETIMEDOUT),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_NETWORK, ENONET),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_ADDRESS_IN_USE, EADDRINUSE),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_DISCONNECTED, ECONNRESET),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_INVALID_ARGS, EINVAL),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_FILE_NOT_FOUND, ENOENT),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_FILE_EXISTS, EEXIST),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_METHOD, EBADR),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_OBJECT, EBADR),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_INTERFACE, EBADR),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_PROPERTY, EBADR),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_PROPERTY_READ_ONLY, EROFS),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, ESRCH),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_INVALID_SIGNATURE, EINVAL),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_INCONSISTENT_MESSAGE, EBADMSG),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_TIMED_OUT, ETIMEDOUT),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_MATCH_RULE_NOT_FOUND, ENOENT),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_MATCH_RULE_INVALID, EINVAL),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, EACCES),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_INVALID_FILE_CONTENT, EINVAL),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, ESRCH),
+ SD_BUS_ERROR_MAP(SD_BUS_ERROR_OBJECT_PATH_IN_USE, EBUSY),
SD_BUS_ERROR_MAP_END
};
assert(ret);
- if (major(devnum) == 0 && minor(devnum) == 0)
+ if (devnum_is_zero(devnum))
return device_path_make_inaccessible(mode, ret);
r = device_new_from_mode_and_devnum(&dev, mode, devnum);
#include <fcntl.h>
#include <unistd.h>
-#include "chase.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "hexdecoct.h"
#include "id128-util.h"
#include "io-util.h"
return false;
}
-int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret) {
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) {
char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
+ sd_id128_t id;
ssize_t l;
int r;
return -EUCLEAN;
}
- r = sd_id128_from_string(buffer, ret);
- return r == -EINVAL ? -EUCLEAN : r;
+ r = sd_id128_from_string(buffer, &id);
+ if (r == -EINVAL)
+ return -EUCLEAN;
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+ return -ENOMEDIUM;
+
+ if (ret)
+ *ret = id;
+ return 0;
}
-int id128_read(const char *root, const char *p, Id128FormatFlag f, sd_id128_t *ret) {
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) {
_cleanup_close_ int fd = -EBADF;
- assert(p);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
- fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path = */ NULL);
+ fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, 0);
if (fd < 0)
return fd;
return id128_read_fd(fd, f, ret);
}
-int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id) {
char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
size_t sz;
int r;
assert(fd >= 0);
assert(IN_SET((f & ID128_FORMAT_ANY), ID128_FORMAT_PLAIN, ID128_FORMAT_UUID));
+ if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+ return -ENOMEDIUM;
+
if (FLAGS_SET(f, ID128_FORMAT_PLAIN)) {
assert_se(sd_id128_to_string(id, buffer));
sz = SD_ID128_STRING_MAX;
return 0;
}
-int id128_write(const char *p, Id128FormatFlag f, sd_id128_t id) {
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) {
_cleanup_close_ int fd = -EBADF;
- fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444);
if (fd < 0)
- return -errno;
+ return fd;
return id128_write_fd(fd, f, id);
}
/* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
* particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
- r = id128_read(NULL, "/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
+ r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
if (r == -ENOENT)
- r = id128_read(NULL, "/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
+ r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
if (r < 0)
return r;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <fcntl.h>
#include <stdbool.h>
#include "sd-id128.h"
bool id128_is_valid(const char *s) _pure_;
-typedef enum Id128FormatFlag {
- ID128_FORMAT_PLAIN = 1 << 0, /* formatted as 32 hex chars as-is */
- ID128_FORMAT_UUID = 1 << 1, /* formatted as 36 character uuid string */
- ID128_FORMAT_ANY = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
+typedef enum Id128Flag {
+ ID128_FORMAT_PLAIN = 1 << 0, /* formatted as 32 hex chars as-is */
+ ID128_FORMAT_UUID = 1 << 1, /* formatted as 36 character uuid string */
+ ID128_FORMAT_ANY = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
ID128_SYNC_ON_WRITE = 1 << 2, /* Sync the file after write. Used only when writing an ID. */
-} Id128FormatFlag;
-
-int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret);
-int id128_read(const char *root, const char *p, Id128FormatFlag f, sd_id128_t *ret);
-
-int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id);
-int id128_write(const char *p, Id128FormatFlag f, sd_id128_t id);
+ ID128_REFUSE_NULL = 1 << 3, /* Refuse all zero ID with -ENOMEDIUM. */
+} Id128Flag;
+
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret);
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret);
+static inline int id128_read(const char *path, Id128Flag f, sd_id128_t *ret) {
+ return id128_read_at(AT_FDCWD, path, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id);
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id);
+static inline int id128_write(const char *path, Id128Flag f, sd_id128_t id) {
+ return id128_write_at(AT_FDCWD, path, f, id);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret);
+int id128_get_machine_at(int rfd, sd_id128_t *ret);
void id128_hash_func(const sd_id128_t *p, struct siphash *state);
int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
#include "sd-id128.h"
#include "alloc-util.h"
+#include "chase.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "hmac.h"
#include "macro.h"
#include "missing_syscall.h"
#include "missing_threads.h"
+#include "path-util.h"
#include "random-util.h"
#include "stat-util.h"
#include "user-util.h"
int r;
if (sd_id128_is_null(saved_machine_id)) {
- r = id128_read(NULL, "/etc/machine-id", ID128_FORMAT_PLAIN, &saved_machine_id);
+ r = id128_read("/etc/machine-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &saved_machine_id);
if (r < 0)
return r;
-
- if (sd_id128_is_null(saved_machine_id))
- return -ENOMEDIUM;
}
if (ret)
return 0;
}
+int id128_get_machine_at(int rfd, sd_id128_t *ret) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ r = dir_fd_is_root_or_cwd(rfd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return sd_id128_get_machine(ret);
+
+ fd = chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ if (fd < 0)
+ return fd;
+
+ return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret) {
+ _cleanup_close_ int fd = -EBADF;
+
+ if (empty_or_root(root))
+ return sd_id128_get_machine(ret);
+
+ fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ if (fd < 0)
+ return fd;
+
+ return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
_public_ int sd_id128_get_boot(sd_id128_t *ret) {
static thread_local sd_id128_t saved_boot_id = {};
int r;
if (sd_id128_is_null(saved_boot_id)) {
- r = id128_read(NULL, "/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID, &saved_boot_id);
+ r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id);
if (r == -ENOENT && proc_mounted() == 0)
return -ENOSYS;
if (r < 0)
return r;
-
- if (sd_id128_is_null(saved_boot_id))
- return -ENOMEDIUM;
}
if (ret)
#include "journal-internal.h"
#include "lookup3.h"
#include "memory-util.h"
+#include "missing_threads.h"
#include "path-util.h"
#include "prioq.h"
#include "random-util.h"
}
static bool keyed_hash_requested(void) {
+ static thread_local int cached = -1;
int r;
- r = getenv_bool("SYSTEMD_JOURNAL_KEYED_HASH");
- if (r >= 0)
- return r;
- if (r != -ENXIO)
- log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring: %m");
+ if (cached < 0) {
+ r = getenv_bool("SYSTEMD_JOURNAL_KEYED_HASH");
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring: %m");
+ cached = true;
+ } else
+ cached = r;
+ }
- return true;
+ return cached;
}
static bool compact_mode_requested(void) {
+ static thread_local int cached = -1;
+ int r;
+
+ if (cached < 0) {
+ r = getenv_bool("SYSTEMD_JOURNAL_COMPACT");
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_COMPACT environment variable, ignoring: %m");
+ cached = true;
+ } else
+ cached = r;
+ }
+
+ return cached;
+}
+
+#if HAVE_COMPRESSION
+static Compression getenv_compression(void) {
+ Compression c;
+ const char *e;
int r;
- r = getenv_bool("SYSTEMD_JOURNAL_COMPACT");
+ e = getenv("SYSTEMD_JOURNAL_COMPRESS");
+ if (!e)
+ return DEFAULT_COMPRESSION;
+
+ r = parse_boolean(e);
if (r >= 0)
- return r;
- if (r != -ENXIO)
- log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_COMPACT environment variable, ignoring: %m");
+ return r ? DEFAULT_COMPRESSION : COMPRESSION_NONE;
- return true;
+ c = compression_from_string(e);
+ if (c < 0) {
+ log_debug_errno(c, "Failed to parse SYSTEMD_JOURNAL_COMPRESS value, ignoring: %s", e);
+ return DEFAULT_COMPRESSION;
+ }
+
+ if (!compression_supported(c)) {
+ log_debug("Unsupported compression algorithm specified, ignoring: %s", e);
+ return DEFAULT_COMPRESSION;
+ }
+
+ return c;
+}
+#endif
+
+static Compression compression_requested(void) {
+#if HAVE_COMPRESSION
+ static thread_local Compression cached = _COMPRESSION_INVALID;
+
+ if (cached < 0)
+ cached = getenv_compression();
+
+ return cached;
+#else
+ return COMPRESSION_NONE;
+#endif
}
static int journal_file_init_header(
Header h = {
.header_size = htole64(ALIGN64(sizeof(h))),
.incompatible_flags = htole32(
- FLAGS_SET(file_flags, JOURNAL_COMPRESS) * COMPRESSION_TO_HEADER_INCOMPATIBLE_FLAG(DEFAULT_COMPRESSION) |
+ FLAGS_SET(file_flags, JOURNAL_COMPRESS) * COMPRESSION_TO_HEADER_INCOMPATIBLE_FLAG(compression_requested()) |
keyed_hash_requested() * HEADER_INCOMPATIBLE_KEYED_HASH |
compact_mode_requested() * HEADER_INCOMPATIBLE_COMPACT),
.compatible_flags = htole32(
}
static Compression maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
- Compression compression = COMPRESSION_NONE;
-
assert(f);
assert(f->header);
#if HAVE_COMPRESSION
- if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) {
- compression = compress_blob(src, size, dst, size - 1, rsize);
- if (compression > 0)
- log_debug("Compressed data object %"PRIu64" -> %zu using %s",
- size, *rsize, compression_to_string(compression));
- else
- /* Compression didn't work, we don't really care why, let's continue without compression */
- compression = COMPRESSION_NONE;
+ Compression c;
+ int r;
+
+ c = JOURNAL_FILE_COMPRESSION(f);
+ if (c == COMPRESSION_NONE || size < f->compress_threshold_bytes)
+ return COMPRESSION_NONE;
+
+ r = compress_blob_explicit(c, src, size, dst, size - 1, rsize);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to compress data object using %s, ignoring: %m", compression_to_string(c));
+ /* Compression didn't work, we don't really care why, let's continue without compression */
+ return COMPRESSION_NONE;
}
-#endif
- return compression;
+ assert(r == c);
+ log_debug("Compressed data object %"PRIu64" -> %zu using %s", size, *rsize, compression_to_string(c));
+
+ return c;
+#else
+ return COMPRESSION_NONE;
+#endif
}
static int journal_file_append_data(
f->close_fd = true;
if (DEBUG_LOGGING) {
- static int last_seal = -1, last_compress = -1, last_keyed_hash = -1;
+ static int last_seal = -1, last_keyed_hash = -1;
+ static Compression last_compression = _COMPRESSION_INVALID;
static uint64_t last_bytes = UINT64_MAX;
if (last_seal != JOURNAL_HEADER_SEALED(f->header) ||
last_keyed_hash != JOURNAL_HEADER_KEYED_HASH(f->header) ||
- last_compress != JOURNAL_FILE_COMPRESS(f) ||
+ last_compression != JOURNAL_FILE_COMPRESSION(f) ||
last_bytes != f->compress_threshold_bytes) {
log_debug("Journal effective settings seal=%s keyed_hash=%s compress=%s compress_threshold_bytes=%s",
yes_no(JOURNAL_HEADER_SEALED(f->header)), yes_no(JOURNAL_HEADER_KEYED_HASH(f->header)),
- yes_no(JOURNAL_FILE_COMPRESS(f)), FORMAT_BYTES(f->compress_threshold_bytes));
+ compression_to_string(JOURNAL_FILE_COMPRESSION(f)), FORMAT_BYTES(f->compress_threshold_bytes));
last_seal = JOURNAL_HEADER_SEALED(f->header);
last_keyed_hash = JOURNAL_HEADER_KEYED_HASH(f->header);
- last_compress = JOURNAL_FILE_COMPRESS(f);
+ last_compression = JOURNAL_FILE_COMPRESSION(f);
last_bytes = f->compress_threshold_bytes;
}
}
int journal_file_map_data_hash_table(JournalFile *f);
int journal_file_map_field_hash_table(JournalFile *f);
-static inline bool JOURNAL_FILE_COMPRESS(JournalFile *f) {
+static inline Compression JOURNAL_FILE_COMPRESSION(JournalFile *f) {
assert(f);
- return JOURNAL_HEADER_COMPRESSED_XZ(f->header) || JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ||
- JOURNAL_HEADER_COMPRESSED_ZSTD(f->header);
+
+ if (JOURNAL_HEADER_COMPRESSED_XZ(f->header))
+ return COMPRESSION_XZ;
+ if (JOURNAL_HEADER_COMPRESSED_LZ4(f->header))
+ return COMPRESSION_LZ4;
+ if (JOURNAL_HEADER_COMPRESSED_ZSTD(f->header))
+ return COMPRESSION_ZSTD;
+ return COMPRESSION_NONE;
}
uint64_t journal_file_hash_data(JournalFile *f, const void *data, size_t sz);
if (r < 0)
return r;
- r = id128_read(arg_root, "/etc/machine-id", ID128_FORMAT_PLAIN, &id);
+ r = id128_get_machine(arg_root, &id);
if (r < 0)
return log_error_errno(r, "Failed to read machine ID back: %m");
} else {
static unsigned arg_lines = 10;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+static int check_netns_match(sd_bus *bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ struct stat st;
+ uint64_t id;
+ int r;
+
+ r = bus_get_property_trivial(bus, bus_network_mgr, "NamespaceId", &error, 't', &id);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r));
+ return 0;
+ }
+ if (id == 0) {
+ log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check.");
+ return 0;
+ }
+
+ if (stat("/proc/self/ns/net", &st) < 0)
+ return log_error_errno(errno, "Failed to determine our own network namespace ID: %m");
+
+ if (id != st.st_ino)
+ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
+ "networkctl must be invoked in same network namespace as systemd-networkd.service.");
+
+ return 0;
+}
+
+static bool networkd_is_running(void) {
+ return access("/run/systemd/netif/state", F_OK) >= 0;
+}
+
+static int acquire_bus(sd_bus **ret) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ assert(ret);
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect system bus: %m");
+
+ r = check_netns_match(bus);
+ if (r < 0)
+ return r;
+
+ if (!networkd_is_running())
+ fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n");
+
+ *ret = TAKE_PTR(bus);
+ return 0;
+}
+
static int get_description(sd_bus *bus, JsonVariant **ret) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- const char *text = NULL;
+ const char *text;
int r;
r = bus_call_method(bus, bus_network_mgr, "Describe", &error, &reply, NULL);
const char *iface,
const char *propname) {
- char ifindex_str[DECIMAL_STR_MAX(int)];
_cleanup_free_ char *path = NULL;
+ char ifindex_str[DECIMAL_STR_MAX(int)];
int r;
xsprintf(ifindex_str, "%i", link->ifindex);
if (r < 0)
return r;
- return sd_bus_call_method(
- bus,
- "org.freedesktop.network1",
- path,
- "org.freedesktop.DBus.Properties",
- "Get",
- error,
- reply,
- "ss",
- iface,
- propname);
+ return sd_bus_get_property(bus, "org.freedesktop.network1", path, iface, propname, error, reply, "ss");
}
static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, LinkInfo **ret) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
_cleanup_(link_info_array_freep) LinkInfo *links = NULL;
+ _cleanup_free_ bool *matched_patterns = NULL;
_cleanup_close_ int fd = -EBADF;
size_t c = 0;
int r;
if (r < 0)
return log_error_errno(r, "Failed to enumerate links: %m");
- _cleanup_free_ bool *matched_patterns = NULL;
if (patterns) {
matched_patterns = new0(bool, strv_length(patterns));
if (!matched_patterns)
typesafe_qsort(links, c, link_info_compare);
if (bus)
- for (size_t j = 0; j < c; j++)
- (void) acquire_link_bitrates(bus, links + j);
+ FOREACH_ARRAY(link, links, c)
+ (void) acquire_link_bitrates(bus, link);
*ret = TAKE_PTR(links);
}
static int list_links(int argc, char *argv[], void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
_cleanup_(link_info_array_freep) LinkInfo *links = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
TableCell *cell;
int c, r;
+ r = acquire_bus(&bus);
+ if (r < 0)
+ return r;
+
if (arg_json_format_flags != JSON_FORMAT_OFF) {
if (arg_all || argc <= 1)
return dump_manager_description(bus);
assert_se(cell = table_get_cell(table, 0, 1));
(void) table_set_ellipsize_percent(table, cell, 100);
- for (int i = 0; i < c; i++) {
+ FOREACH_ARRAY(link, links, c) {
_cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
- const char *on_color_operational, *on_color_setup;
_cleanup_free_ char *t = NULL;
+ const char *on_color_operational, *on_color_setup;
- (void) sd_network_link_get_operational_state(links[i].ifindex, &operational_state);
- operational_state_to_color(links[i].name, operational_state, &on_color_operational, NULL);
+ (void) sd_network_link_get_operational_state(link->ifindex, &operational_state);
+ operational_state_to_color(link->name, operational_state, &on_color_operational, NULL);
- (void) sd_network_link_get_setup_state(links[i].ifindex, &setup_state);
+ (void) sd_network_link_get_setup_state(link->ifindex, &setup_state);
setup_state_to_color(setup_state, &on_color_setup, NULL);
- r = net_get_type_string(links[i].sd_device, links[i].iftype, &t);
+ r = net_get_type_string(link->sd_device, link->iftype, &t);
if (r == -ENOMEM)
return log_oom();
r = table_add_many(table,
- TABLE_INT, links[i].ifindex,
- TABLE_STRING, links[i].name,
+ TABLE_INT, link->ifindex,
+ TABLE_STRING, link->name,
TABLE_STRING, t,
TABLE_STRING, operational_state,
TABLE_SET_COLOR, on_color_operational,
/* IEEE Organizationally Unique Identifier vendor string */
static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
+ _cleanup_free_ char *desc = NULL;
const char *description;
- char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1], *desc;
+ char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1];
int r;
assert(ret);
- if (!hwdb)
- return -EINVAL;
-
- if (!mac)
+ if (!hwdb || !mac)
return -EINVAL;
/* skip commonly misused 00:00:00 (Xerox) prefix */
if (memcmp(mac, "\0\0\0", 3) == 0)
return -EINVAL;
- xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR,
- ETHER_ADDR_FORMAT_VAL(*mac));
+ xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description);
if (r < 0)
if (!desc)
return -ENOMEM;
- *ret = desc;
+ *ret = TAKE_PTR(desc);
return 0;
}
int ifindex,
int family,
union in_addr_union *gateway,
- char **gateway_description) {
+ char **ret) {
+
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
int r;
assert(ifindex >= 0);
assert(IN_SET(family, AF_INET, AF_INET6));
assert(gateway);
- assert(gateway_description);
+ assert(ret);
r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
if (r < 0)
r = sd_netlink_message_get_errno(m);
if (r < 0) {
- log_error_errno(r, "got error: %m");
+ log_error_errno(r, "Failed to get netlink message, ignoring: %m");
continue;
}
r = sd_netlink_message_get_type(m, &type);
if (r < 0) {
- log_error_errno(r, "could not get type: %m");
+ log_error_errno(r, "Failed to get netlink message type, ignoring: %m");
continue;
}
if (type != RTM_NEWNEIGH) {
- log_error("type is not RTM_NEWNEIGH");
+ log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Got unexpected netlink message type %u, ignoring",
+ type);
continue;
}
r = sd_rtnl_message_neigh_get_family(m, &fam);
if (r < 0) {
- log_error_errno(r, "could not get family: %m");
+ log_error_errno(r, "Failed to get rtnl family, ignoring: %m");
continue;
}
if (fam != family) {
- log_error("family is not correct");
+ log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got invalid rtnl family %d, ignoring", fam);
continue;
}
r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
if (r < 0) {
- log_error_errno(r, "could not get ifindex: %m");
+ log_error_errno(r, "Failed to get rtnl ifindex, ignoring: %m");
continue;
}
continue;
switch (fam) {
+
case AF_INET:
r = sd_netlink_message_read_in_addr(m, NDA_DST, &gw.in);
if (r < 0)
continue;
break;
+
case AF_INET6:
r = sd_netlink_message_read_in6_addr(m, NDA_DST, &gw.in6);
if (r < 0)
continue;
break;
+
default:
continue;
}
if (r < 0)
continue;
- r = ieee_oui(hwdb, &mac, gateway_description);
+ r = ieee_oui(hwdb, &mac, ret);
if (r < 0)
continue;
return 0;
}
-static int dump_gateways(
- sd_netlink *rtnl,
- sd_hwdb *hwdb,
- Table *table,
- int ifindex) {
- _cleanup_free_ struct local_address *local = NULL;
+static int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) {
+ _cleanup_free_ struct local_address *local_addrs = NULL;
_cleanup_strv_free_ char **buf = NULL;
int r, n;
assert(rtnl);
assert(table);
- n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local);
+ n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local_addrs);
if (n <= 0)
return n;
- for (int i = 0; i < n; i++) {
+ FOREACH_ARRAY(local, local_addrs, n) {
_cleanup_free_ char *description = NULL;
- r = get_gateway_description(rtnl, hwdb, local[i].ifindex, local[i].family, &local[i].address, &description);
+ r = get_gateway_description(rtnl, hwdb, local->ifindex, local->family, &local->address, &description);
if (r < 0)
log_debug_errno(r, "Could not get description of gateway, ignoring: %m");
/* Show interface name for the entry if we show entries for all interfaces */
r = strv_extendf(&buf, "%s%s%s%s%s%s",
- IN_ADDR_TO_STRING(local[i].family, &local[i].address),
+ IN_ADDR_TO_STRING(local->family, &local->address),
description ? " (" : "",
strempty(description),
description ? ")" : "",
ifindex <= 0 ? " on " : "",
- ifindex <= 0 ? FORMAT_IFNAME_FULL(local[i].ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
+ ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
if (r < 0)
return log_oom();
}
Table *table,
int ifindex) {
- _cleanup_free_ struct local_address *local = NULL;
+ _cleanup_free_ struct local_address *local_addrs = NULL;
_cleanup_strv_free_ char **buf = NULL;
struct in_addr dhcp4_address = {};
int r, n;
assert(rtnl);
assert(table);
- n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local);
+ n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local_addrs);
if (n <= 0)
return n;
if (lease)
(void) sd_dhcp_lease_get_address(lease, &dhcp4_address);
- for (int i = 0; i < n; i++) {
+ FOREACH_ARRAY(local, local_addrs, n) {
struct in_addr server_address;
bool dhcp4 = false;
- if (local[i].family == AF_INET && in4_addr_equal(&local[i].address.in, &dhcp4_address))
+ if (local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address))
dhcp4 = sd_dhcp_lease_get_server_identifier(lease, &server_address) >= 0;
r = strv_extendf(&buf, "%s%s%s%s%s%s",
- IN_ADDR_TO_STRING(local[i].family, &local[i].address),
+ IN_ADDR_TO_STRING(local->family, &local->address),
dhcp4 ? " (DHCP4 via " : "",
dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "",
dhcp4 ? ")" : "",
ifindex <= 0 ? " on " : "",
- ifindex <= 0 ? FORMAT_IFNAME_FULL(local[i].ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
+ ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
if (r < 0)
return log_oom();
}
r = sd_netlink_message_get_errno(m);
if (r < 0) {
- log_error_errno(r, "got error: %m");
+ log_error_errno(r, "Failed to get netlink message, ignoring: %m");
continue;
}
if (r < 0)
return log_error_errno(r, "Failed to connect to netlink: %m");
- dump_address_labels(rtnl);
-
- return 0;
+ return dump_address_labels(rtnl);
}
static int open_lldp_neighbors(int ifindex, FILE **ret) {
+ _cleanup_fclose_ FILE *f = NULL;
char p[STRLEN("/run/systemd/netif/lldp/") + DECIMAL_STR_MAX(int)];
- FILE *f;
xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex);
+
f = fopen(p, "re");
if (!f)
return -errno;
- *ret = f;
+ *ret = TAKE_PTR(f);
return 0;
}
}
static int link_status(int argc, char *argv[], void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
_cleanup_(link_info_array_freep) LinkInfo *links = NULL;
int r, c;
+ r = acquire_bus(&bus);
+ if (r < 0)
+ return r;
+
if (arg_json_format_flags != JSON_FORMAT_OFF) {
if (arg_all || argc <= 1)
return dump_manager_description(bus);
table_set_minimum_width(table, cell, 11);
table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
- for (int i = 0; i < c; i++) {
+ FOREACH_ARRAY(link, links, c) {
_cleanup_fclose_ FILE *f = NULL;
- r = open_lldp_neighbors(links[i].ifindex, &f);
+ r = open_lldp_neighbors(link->ifindex, &f);
if (r == -ENOENT)
continue;
if (r < 0) {
- log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", links[i].ifindex);
+ log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", link->ifindex);
continue;
}
}
r = table_add_many(table,
- TABLE_STRING, links[i].name,
+ TABLE_STRING, link->name,
TABLE_STRING, chassis_id,
TABLE_STRING, system_name,
TABLE_STRING, capabilities,
}
static int link_renew(int argc, char *argv[], void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
int index, k = 0, r;
+ r = acquire_bus(&bus);
+ if (r < 0)
+ return r;
+
for (int i = 1; i < argc; i++) {
index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
if (index < 0)
}
static int link_force_renew(int argc, char *argv[], void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
int k = 0, r;
+ r = acquire_bus(&bus);
+ if (r < 0)
+ return r;
+
for (int i = 1; i < argc; i++) {
int index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
if (index < 0)
}
static int verb_reload(int argc, char *argv[], void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
+ r = acquire_bus(&bus);
+ if (r < 0)
+ return r;
+
r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to reload network settings: %s", bus_error_message(&error, r));
}
static int verb_reconfigure(int argc, char *argv[], void *userdata) {
- sd_bus *bus = ASSERT_PTR(userdata);
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
_cleanup_set_free_ Set *indexes = NULL;
int index, r;
void *p;
+ r = acquire_bus(&bus);
+ if (r < 0)
+ return r;
+
indexes = set_new(NULL);
if (!indexes)
return log_oom();
return 1;
}
-static int networkctl_main(sd_bus *bus, int argc, char *argv[]) {
+static int networkctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, list_links },
{ "status", VERB_ANY, VERB_ANY, 0, link_status },
{}
};
- return dispatch_verb(argc, argv, verbs, bus);
-}
-
-static int check_netns_match(sd_bus *bus) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- struct stat st;
- uint64_t id;
- int r;
-
- r = sd_bus_get_property_trivial(
- bus,
- "org.freedesktop.network1",
- "/org/freedesktop/network1",
- "org.freedesktop.network1.Manager",
- "NamespaceId",
- &error,
- 't',
- &id);
- if (r < 0) {
- log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r));
- return 0;
- }
- if (id == 0) {
- log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check.");
- return 0;
- }
-
- if (stat("/proc/self/ns/net", &st) < 0)
- return log_error_errno(r, "Failed to determine our own network namespace ID: %m");
-
- if (id != st.st_ino)
- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
- "networkctl must be invoked in same network namespace as systemd-networkd.service.");
-
- return 0;
-}
-
-static void warn_networkd_missing(void) {
-
- if (access("/run/systemd/netif/state", F_OK) >= 0)
- return;
-
- fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n");
+ return dispatch_verb(argc, argv, verbs, NULL);
}
static int run(int argc, char* argv[]) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
log_setup();
if (r <= 0)
return r;
- r = sd_bus_open_system(&bus);
- if (r < 0)
- return log_error_errno(r, "Failed to connect system bus: %m");
-
- r = check_netns_match(bus);
- if (r < 0)
- return r;
-
- warn_networkd_missing();
-
- return networkctl_main(bus, argc, argv);
+ return networkctl_main(argc, argv);
}
DEFINE_MAIN_FUNCTION(run);
#include "ether-addr-util.h"
#include "networkd-manager.h"
#include "networkd-network-bus.h"
+#include "path-util.h"
#include "string-util.h"
#include "strv.h"
};
static char *network_bus_path(Network *network) {
- _cleanup_free_ char *name = NULL;
- char *networkname, *d, *path;
+ _cleanup_free_ char *name = NULL, *networkname= NULL;
+ char *d, *path;
int r;
assert(network);
if (!name)
return NULL;
- networkname = basename(name);
+ r = path_extract_filename(name, &networkname);
+ if (r < 0)
+ return NULL;
d = strrchr(networkname, '.');
if (!d)
#include "alloc-util.h"
#include "build.h"
#include "env-util.h"
+#include "fd-util.h"
+#include "fdset.h"
#include "format-util.h"
#include "log.h"
#include "main-func.h"
static bool arg_no_block = false;
static char **arg_env = NULL;
static char **arg_exec = NULL;
+static FDSet *arg_fds = NULL;
+static char *arg_fdname = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
" --booted Check if the system was booted up with systemd\n"
" --no-block Do not wait until operation finished\n"
" --exec Execute command line separated by ';' once done\n"
+ " --fd=FD Pass specified file descriptor with along with message\n"
+ " --fdname=NAME Name to assign to passed file descriptor(s)\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
program_invocation_short_name,
ARG_UID,
ARG_NO_BLOCK,
ARG_EXEC,
+ ARG_FD,
+ ARG_FDNAME,
};
static const struct option options[] = {
{ "uid", required_argument, NULL, ARG_UID },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "exec", no_argument, NULL, ARG_EXEC },
+ { "fd", required_argument, NULL, ARG_FD },
+ { "fdname", required_argument, NULL, ARG_FDNAME },
{}
};
+ _cleanup_(fdset_freep) FDSet *passed = NULL;
bool do_exec = false;
int c, r, n_env;
do_exec = true;
break;
+ case ARG_FD: {
+ _cleanup_close_ int owned_fd = -EBADF;
+ int fdnr;
+
+ r = safe_atoi(optarg, &fdnr);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse file descriptor: %s", optarg);
+ if (fdnr < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "File descriptor can't be negative: %i", fdnr);
+
+ if (!passed) {
+ /* Take possession of all passed fds */
+ r = fdset_new_fill(&passed);
+ if (r < 0)
+ return log_error_errno(r, "Failed to take possession of passed file descriptors: %m");
+
+ r = fdset_cloexec(passed, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable O_CLOEXEC for passed file descriptors: %m");
+ }
+
+ if (fdnr < 3) {
+ /* For stdin/stdout/stderr we want to keep the fd, too, hence make a copy */
+ owned_fd = fcntl(fdnr, F_DUPFD_CLOEXEC, 3);
+ if (owned_fd < 0)
+ return log_error_errno(errno, "Failed to duplicate file descriptor: %m");
+ } else {
+ /* Otherwise, move the fd over */
+ owned_fd = fdset_remove(passed, fdnr);
+ if (owned_fd < 0)
+ return log_error_errno(owned_fd, "Specified file descriptor '%i' not passed or specified more than once: %m", fdnr);
+ }
+
+ if (!arg_fds) {
+ arg_fds = fdset_new();
+ if (!arg_fds)
+ return log_oom();
+ }
+
+ r = fdset_consume(arg_fds, TAKE_FD(owned_fd));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add file descriptor to set: %m");
+ break;
+ }
+
+ case ARG_FDNAME:
+ if (!fdname_is_valid(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg);
+
+ if (free_and_strdup(&arg_fdname, optarg) < 0)
+ return log_oom();
+
+ break;
+
case '?':
return -EINVAL;
!arg_reloading &&
!arg_status &&
!arg_pid &&
- !arg_booted) {
+ !arg_booted &&
+ fdset_isempty(arg_fds)) {
help();
return -EINVAL;
}
+ if (arg_fdname && fdset_isempty(arg_fds))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
+
if (do_exec) {
int i;
return log_oom();
}
+ if (!fdset_isempty(passed))
+ log_warning("Warning: %u more file descriptors passed than referenced with --fd=.", fdset_size(passed));
+
return 1;
}
static int run(int argc, char* argv[]) {
- _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL;
+ _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL, *fdn = NULL;
_cleanup_strv_free_ char **final_env = NULL;
- char* our_env[7];
+ char* our_env[9];
size_t i = 0;
pid_t source_pid;
int r;
our_env[i++] = cpid;
}
+ if (!fdset_isempty(arg_fds)) {
+ our_env[i++] = (char*) "FDSTORE=1";
+
+ if (arg_fdname) {
+ fdn = strjoin("FDNAME=", arg_fdname);
+ if (!fdn)
+ return log_oom();
+
+ our_env[i++] = fdn;
+ }
+ }
+
our_env[i++] = NULL;
final_env = strv_env_merge(our_env, arg_env);
* or the service manager itself */
source_pid = 0;
}
- r = sd_pid_notify(source_pid, false, n);
+
+ if (fdset_isempty(arg_fds))
+ r = sd_pid_notify(source_pid, /* unset_environment= */ false, n);
+ else {
+ _cleanup_free_ int *a = NULL;
+ int k;
+
+ k = fdset_to_array(arg_fds, &a);
+ if (k < 0)
+ return log_error_errno(k, "Failed to convert file descriptor set to array: %m");
+
+ r = sd_pid_notify_with_fds(source_pid, /* unset_environment= */ false, n, a, k);
+
+ }
if (r < 0)
return log_error_errno(r, "Failed to notify init system: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"No status data could be sent: $NOTIFY_SOCKET was not set");
+ arg_fds = fdset_free(arg_fds); /* Close before we execute anything */
+
if (!arg_no_block) {
r = sd_notify_barrier(0, 5 * USEC_PER_SEC);
if (r < 0)
}
static int setup_machine_id(const char *directory) {
- sd_id128_t id;
int r;
/* If the UUID in the container is already set, then that's what counts, and we use. If it isn't set, and the
* in the container and our idea of the container UUID will always be in sync (at least if PID 1 in the
* container behaves nicely). */
- r = id128_read(directory, "/etc/machine-id", ID128_FORMAT_PLAIN, &id);
+ r = id128_get_machine(directory, &arg_uuid);
if (r < 0) {
if (!ERRNO_IS_MACHINE_ID_UNSET(r)) /* If the file is missing, empty, or uninitialized, we don't mind */
return log_error_errno(r, "Failed to read machine ID from container image: %m");
if (r < 0)
return log_error_errno(r, "Failed to acquire randomized machine UUID: %m");
}
- } else {
- if (sd_id128_is_null(id))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Machine ID in container image is zero, refusing.");
-
- arg_uuid = id;
}
return 0;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *pubkey = NULL;
- _cleanup_free_ void *blob = NULL, *hash = NULL;
- size_t secret_size, blob_size, hash_size, pubkey_size = 0;
+ _cleanup_free_ void *blob = NULL, *hash = NULL, *srk_buf = NULL;
+ size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0;
ssize_t base64_encoded_size;
uint16_t pcr_bank, primary_alg;
int keyslot;
&blob, &blob_size,
&hash, &hash_size,
&pcr_bank,
- &primary_alg);
+ &primary_alg,
+ &srk_buf,
+ &srk_buf_size);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
blob, blob_size,
hash, hash_size,
NULL, 0, /* no salt because tpm2_seal has no pin */
+ srk_buf, srk_buf_size,
0,
&v);
if (r < 0)
assert(ret);
+ log_info("Populating %s filesystem.", p->format);
+
r = var_tmp_dir(&vt);
if (r < 0)
return log_error_errno(r, "Could not determine temporary directory: %m");
if (r < 0)
return r;
+ log_info("Successfully populated %s filesystem.", p->format);
+
*ret = TAKE_PTR(root);
return 0;
}
assert(p);
assert(node);
- log_info("Populating %s filesystem with files.", p->format);
+ log_info("Populating %s filesystem.", p->format);
/* We copy in a child process, since we have to mount the fs for that, and we don't want that fs to
* appear in the host namespace. Hence we fork a child that has its own file system namespace and
_exit(EXIT_SUCCESS);
}
- log_info("Successfully populated %s filesystem with files.", p->format);
+ log_info("Successfully populated %s filesystem.", p->format);
return 0;
}
return 0;
if (!arg_randomize) {
- _cleanup_close_ int fd = -EBADF;
-
- fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, NULL);
- if (fd == -ENOENT)
- log_info("No machine ID set, using randomized partition UUIDs.");
- else if (fd < 0)
- return log_error_errno(fd, "Failed to determine machine ID of image: %m");
- else {
- r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &context->seed);
- if (r < 0) {
- if (!ERRNO_IS_MACHINE_ID_UNSET(r))
- return log_error_errno(r, "Failed to parse machine ID of image: %m");
+ r = id128_get_machine(root, &context->seed);
+ if (r >= 0)
+ return 0;
- log_info("No machine ID set, using randomized partition UUIDs.");
- }
+ if (!ERRNO_IS_MACHINE_ID_UNSET(r))
+ return log_error_errno(r, "Failed to parse machine ID of image: %m");
- return 0;
- }
+ log_info("No machine ID set, using randomized partition UUIDs.");
}
r = sd_id128_randomize(&context->seed);
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_strv_free_ char **extra_mkfs_options = NULL;
_cleanup_close_ int fd = -EBADF;
+ _cleanup_free_ char *hint = NULL;
sd_id128_t fs_uuid;
uint64_t fsz;
assert(!p->copy_blocks_path);
+ (void) partition_hint(p, context->node, &hint);
+
+ log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size",
+ p->format, strna(hint));
+
r = make_copy_files_denylist(context, p, &denylist);
if (r < 0)
return r;
/* Read-only filesystems are minimal from the first try because they create and size the
* loopback file for us. */
if (fstype_is_ro(p->format)) {
+ struct stat st;
+
+ if (stat(temp, &st) < 0)
+ return log_error_errno(errno, "Failed to stat temporary file: %m");
+
+ log_info("Minimal partition size of %s filesystem of partition %s is %s",
+ p->format, strna(hint), FORMAT_BYTES(st.st_size));
+
p->copy_blocks_path = TAKE_PTR(temp);
p->copy_blocks_path_is_our_file = true;
continue;
if (minimal_size_by_fs_name(p->format) != UINT64_MAX)
fsz = MAX(minimal_size_by_fs_name(p->format), fsz);
+ log_info("Minimal partition size of %s filesystem of partition %s is %s",
+ p->format, strna(hint), FORMAT_BYTES(fsz));
+
d = loop_device_unref(d);
/* Erase the previous filesystem first. */
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
/* First, find os-release/extension-release and send it upstream (or just save it). */
if (path_is_extension) {
os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name);
- r = open_extension_release(where, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
+ r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
} else {
os_release_id = "/etc/os-release";
r = open_os_release(where, &os_release_path, &os_release_fd);
return r;
if (validate_sysext) {
- r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
+ r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
if (r == 0)
return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
if (r < 0)
static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
[IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
- [IMAGE_EXTENSION] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
+ [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
};
static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
[IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
- [IMAGE_EXTENSION] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
+ [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
};
_cleanup_strv_free_ char **fields = NULL;
const char *id = NULL, *version = NULL;
int r;
- assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_EXTENSION));
+ assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT));
assert(!strv_isempty((char *const *)field_ids[type]));
assert(!strv_isempty((char *const *)field_versions[type]));
assert(field_name);
* still be able to identify what applies to what. */
r = append_release_log_fields(&text,
ordered_hashmap_get(extension_releases, ext->name),
- IMAGE_EXTENSION,
+ IMAGE_SYSEXT,
"PORTABLE_EXTENSION_NAME_AND_VERSION");
if (r < 0)
return r;
/* RFC 1035 say 512 is the maximum, for classic unicast DNS */
#define DNS_PACKET_UNICAST_SIZE_MAX 512u
-/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */
-#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096u
+/* With EDNS0 we can use larger packets, default to 1232, which is what is commonly used */
+#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 1232u
struct DnsPacket {
unsigned n_ref;
if (s->family != AF_UNSPEC && f != s->family)
return _DNS_SCOPE_MATCH_INVALID; /* Don't look for IPv4 addresses on LLMNR/mDNS over IPv6 and vice versa */
+ if (in_addr_is_null(f, &ia))
+ return DNS_SCOPE_NO;
+
LIST_FOREACH(addresses, a, s->link->addresses) {
if (a->family != f)
if (a->prefixlen == UCHAR_MAX) /* don't know subnet mask */
continue;
+ /* Don't send mDNS queries for the IPv4 broadcast address */
+ if (f == AF_INET && in_addr_equal(f, &a->in_addr_broadcast, &ia) > 0)
+ return DNS_SCOPE_NO;
+
/* Check if the address is in the local subnet */
r = in_addr_prefix_covers(f, &a->in_addr, a->prefixlen, &ia);
if (r < 0)
return MIN(link->mdns_support, link->manager->mdns_support);
}
-int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) {
+int link_address_new(Link *l,
+ LinkAddress **ret,
+ int family,
+ const union in_addr_union *in_addr,
+ const union in_addr_union *in_addr_broadcast) {
LinkAddress *a;
assert(l);
*a = (LinkAddress) {
.family = family,
.in_addr = *in_addr,
+ .in_addr_broadcast = *in_addr_broadcast,
.link = l,
.prefixlen = UCHAR_MAX,
};
int family;
union in_addr_union in_addr;
+ union in_addr_union in_addr_broadcast;
unsigned char prefixlen;
unsigned char flags, scope;
int link_load_user(Link *l);
void link_remove_user(Link *l);
-int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr);
+int link_address_new(Link *l,
+ LinkAddress **ret,
+ int family,
+ const union in_addr_union *in_addr,
+ const union in_addr_union *in_addr_broadcast);
LinkAddress *link_address_free(LinkAddress *a);
int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m);
bool link_address_relevant(LinkAddress *l, bool local_multicast);
static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
- union in_addr_union address;
+ union in_addr_union address, broadcast = {};
uint16_t type;
int r, ifindex, family;
LinkAddress *a;
switch (family) {
case AF_INET:
+ sd_netlink_message_read_in_addr(mm, IFA_BROADCAST, &broadcast.in);
r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in);
if (r < 0) {
r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in);
case RTM_NEWADDR:
if (!a) {
- r = link_address_new(l, &a, family, &address);
+ r = link_address_new(l, &a, family, &address, &broadcast);
if (r < 0)
return r;
}
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
+static const char* const boot_entry_type_json_table[_BOOT_ENTRY_TYPE_MAX] = {
+ [BOOT_ENTRY_CONF] = "type1",
+ [BOOT_ENTRY_UNIFIED] = "type2",
+ [BOOT_ENTRY_LOADER] = "loader",
+ [BOOT_ENTRY_LOADER_AUTO] = "auto",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
+
static void boot_entry_free(BootEntry *entry) {
assert(entry);
}
r = json_append(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)),
JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)),
JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)),
* arguments and trigger false positive warnings. Let's not add too many json objects
* at once. */
r = json_append(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)),
JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)),
JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)),
JSON_BUILD_PAIR_CONDITION(config->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) config->default_entry)),
}
const char* boot_entry_type_to_string(BootEntryType);
+const char* boot_entry_type_json_to_string(BootEntryType);
BootEntry* boot_config_find_entry(BootConfig *config, const char *id);
return RET_NERRNO(ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args));
}
-int btrfs_get_block_device_fd(int fd, dev_t *dev) {
+int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) {
struct btrfs_ioctl_fs_info_args fsi = {};
- _cleanup_close_ int regfd = -EBADF;
+ _cleanup_close_ int fd = -EBADF;
uint64_t id;
int r;
- assert(fd >= 0);
- assert(dev);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
- fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, O_PATH, ®fd);
+ fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, 0);
if (fd < 0)
return fd;
/* We won't do this for btrfs RAID */
if (fsi.num_devices != 1) {
- *dev = 0;
+ *ret = 0;
return 0;
}
if (major(st.st_rdev) == 0)
return -ENODEV;
- *dev = st.st_rdev;
+ *ret = st.st_rdev;
return 1;
}
return -ENODEV;
}
-int btrfs_get_block_device(const char *path, dev_t *dev) {
- _cleanup_close_ int fd = -EBADF;
-
- assert(path);
- assert(dev);
-
- fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
- if (fd < 0)
- return -errno;
-
- return btrfs_get_block_device_fd(fd, dev);
-}
-
int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) {
struct btrfs_ioctl_ino_lookup_args args = {
.objectid = BTRFS_FIRST_FREE_OBJECTID
int btrfs_reflink(int infd, int outfd);
int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz);
-int btrfs_get_block_device_fd(int fd, dev_t *dev);
-int btrfs_get_block_device(const char *path, dev_t *dev);
+int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret);
+static inline int btrfs_get_block_device(const char *path, dev_t *ret) {
+ return btrfs_get_block_device_at(AT_FDCWD, path, ret);
+}
+static inline int btrfs_get_block_device_fd(int fd, dev_t *ret) {
+ return btrfs_get_block_device_at(fd, "", ret);
+}
int btrfs_defrag_fd(int fd);
int btrfs_defrag(const char *p);
&tpm2_blob, &tpm2_blob_size,
&tpm2_policy_hash, &tpm2_policy_hash_size,
&tpm2_pcr_bank,
- &tpm2_primary_alg);
+ &tpm2_primary_alg,
+ /* ret_srk_buf= */ NULL,
+ /* ret_srk_buf_size= */ 0);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
le32toh(z->size));
}
+ // TODO: Add the SRK data to the credential structure so it can be plumbed
+ // through and used to verify the TPM session.
r = tpm2_unseal(tpm2_device,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
le32toh(t->blob_size),
t->policy_hash_and_blob + le32toh(t->blob_size),
le32toh(t->policy_hash_size),
+ /* srk_buf= */ NULL,
+ /* srk_buf_size= */ 0,
&tpm2_key,
&tpm2_key_size);
if (r < 0)
#include "dissect-image.h"
#include "env-file.h"
#include "env-util.h"
+#include "extension-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "hashmap.h"
"/usr/local/lib/portables\0"
"/usr/lib/portables\0",
- [IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
- "/run/extensions\0" /* and here too */
- "/var/lib/extensions\0" /* the main place for images */
- "/usr/local/lib/extensions\0"
- "/usr/lib/extensions\0",
+ /* Note that we don't allow storing extensions under /usr/, unlike with other image types. That's
+ * because extension images are supposed to extend /usr/, so you get into recursive races, especially
+ * with directory-based extensions, as the kernel's OverlayFS explicitly checks for this and errors
+ * out with -ELOOP if it finds that a lowerdir= is a child of another lowerdir=. */
+ [IMAGE_SYSEXT] = "/etc/extensions\0" /* only place symlinks here */
+ "/run/extensions\0" /* and here too */
+ "/var/lib/extensions\0", /* the main place for images */
+
+ [IMAGE_CONFEXT] = "/run/confexts\0" /* only place symlinks here */
+ "/var/lib/confexts\0" /* the main place for images */
+ "/usr/local/lib/confexts\0"
+ "/usr/lib/confexts\0",
};
static Image *image_free(Image *i) {
_cleanup_free_ char *hostname = NULL;
_cleanup_free_ char *path = NULL;
+ if (i->class == IMAGE_SYSEXT) {
+ r = extension_has_forbidden_content(i->path);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+ "Conflicting content found in image %s, refusing.",
+ i->name);
+ }
+
r = chase("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
if (r < 0 && r != -ENOENT)
log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name);
path = mfree(path);
- r = chase("/etc/machine-id", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
- if (r < 0 && r != -ENOENT)
- log_debug_errno(r, "Failed to chase /etc/machine-id in image %s: %m", i->name);
- else if (r >= 0) {
- _cleanup_close_ int fd = -EBADF;
-
- fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (fd < 0)
- log_debug_errno(errno, "Failed to open %s: %m", path);
- else {
- r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id);
- if (r < 0)
- log_debug_errno(r, "Image %s contains invalid machine ID.", i->name);
- }
- }
-
- path = mfree(path);
+ r = id128_get_machine(i->path, &machine_id);
+ if (r < 0)
+ log_debug_errno(r, "Failed to read machine ID in image %s, ignoring: %m", i->name);
r = chase("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
if (r < 0 && r != -ENOENT)
if (r < 0)
log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
- r = load_extension_release_pairs(i->path, i->name, /* relax_extension_release_check= */ false, &extension_release);
+ r = load_extension_release_pairs(i->path, i->class, i->name, /* relax_extension_release_check= */ false, &extension_release);
if (r < 0)
log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m");
static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "machine",
[IMAGE_PORTABLE] = "portable",
- [IMAGE_EXTENSION] = "extension",
+ [IMAGE_SYSEXT] = "extension",
+ [IMAGE_CONFEXT] = "confext"
};
DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
#include "hashmap.h"
#include "lock-util.h"
#include "macro.h"
+#include "os-util.h"
#include "path-util.h"
#include "string-util.h"
#include "time-util.h"
-typedef enum ImageClass {
- IMAGE_MACHINE,
- IMAGE_PORTABLE,
- IMAGE_EXTENSION,
- _IMAGE_CLASS_MAX,
- _IMAGE_CLASS_INVALID = -EINVAL,
-} ImageClass;
-
typedef enum ImageType {
IMAGE_DIRECTORY,
IMAGE_SUBVOLUME,
const char* image_type_to_string(ImageType t) _const_;
ImageType image_type_from_string(const char *s) _pure_;
-const char* image_class_to_string(ImageClass cl) _const_;
-ImageClass image_class_from_string(const char *s) _pure_;
-
int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local);
int image_name_lock(const char *name, int operation, LockFile *ret);
#include "dm-util.h"
#include "env-file.h"
#include "env-util.h"
-#include "extension-release.h"
+#include "extension-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
ok = true;
}
if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) {
- r = path_is_extension_tree(where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
+ r = extension_has_forbidden_content(where);
if (r < 0)
return r;
- if (r > 0)
- ok = true;
+ if (r == 0) {
+ r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ ok = true;
+ }
}
if (!ok)
* we allow a fallback that matches on the first extension-release
* file found in the directory, if one named after the image cannot
* be found first. */
- r = open_extension_release(t, m->image_name, /* relax_extension_release_check= */ false, NULL, &fd);
+ r = open_extension_release(t, IMAGE_SYSEXT, m->image_name, /* relax_extension_release_check= */ false, NULL, &fd);
if (r < 0)
fd = r; /* Propagate the error. */
break;
assert(!isempty(required_host_os_release_id));
- r = load_extension_release_pairs(dest, dissected_image->image_name, relax_extension_release_check, &extension_release);
+ r = load_extension_release_pairs(dest, IMAGE_SYSEXT, dissected_image->image_name, relax_extension_release_check, &extension_release);
if (r < 0)
return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
required_host_os_release_version_id,
required_host_os_release_sysext_level,
required_sysext_scope,
- extension_release);
+ extension_release,
+ IMAGE_SYSEXT);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
if (r < 0)
pid_t _pid;
int r;
- if (null_or_empty_path(path)) {
+ if (null_or_empty_path(path) > 0) {
log_debug("%s is empty (a mask).", path);
return 0;
}
_cleanup_strv_free_ char **ret_opts = NULL;
ExecCommandFlags it = flags;
const char *str;
- int i, r;
+ int r;
assert(ex_opts);
if (flags < 0)
return flags;
- for (i = 0; it != 0; it &= ~(1 << i), i++) {
+ for (unsigned i = 0; it != 0; it &= ~(1 << i), i++)
if (FLAGS_SET(flags, (1 << i))) {
str = exec_command_flags_to_string(1 << i);
if (!str)
if (r < 0)
return r;
}
- }
*ex_opts = TAKE_PTR(ret_opts);
};
const char* exec_command_flags_to_string(ExecCommandFlags i) {
- size_t idx;
-
- for (idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
+ for (size_t idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
if (i == (1 << idx))
return exec_command_strings[idx];
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-/* Given an image name (for logging purposes), a set of os-release values from the host and a key-value pair
- * vector of extension-release variables, check that the distro and (system extension level or distro
- * version) match and return 1, and 0 otherwise. */
-int extension_release_validate(
- const char *name,
- const char *host_os_release_id,
- const char *host_os_release_version_id,
- const char *host_os_release_sysext_level,
- const char *host_sysext_scope,
- char **extension_release);
-
-/* Parse SYSTEMD_SYSEXT_HIERARCHIES and if not set, return "/usr /opt" */
-int parse_env_extension_hierarchies(char ***ret_hierarchies);
#include "alloc-util.h"
#include "architecture.h"
+#include "chase.h"
#include "env-util.h"
-#include "extension-release.h"
+#include "extension-util.h"
#include "log.h"
#include "os-util.h"
#include "strv.h"
const char *name,
const char *host_os_release_id,
const char *host_os_release_version_id,
- const char *host_os_release_sysext_level,
- const char *host_sysext_scope,
- char **extension_release) {
+ const char *host_os_extension_release_level,
+ const char *host_extension_scope,
+ char **extension_release,
+ ImageClass image_class) {
- const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;
+ const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL;
+ const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
+ const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
assert(name);
assert(!isempty(host_os_release_id));
- /* Now that we can look into the extension image, let's see if the OS version is compatible */
+ /* Now that we can look into the extension/confext image, let's see if the OS version is compatible */
if (strv_isempty(extension_release)) {
- log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
+ log_debug("Extension '%s' carries no release data, ignoring.", name);
return 0;
}
- if (host_sysext_scope) {
- _cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
- const char *extension_sysext_scope;
+ if (host_extension_scope) {
+ _cleanup_strv_free_ char **scope_list = NULL;
+ const char *scope;
bool valid;
- extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
- if (extension_sysext_scope) {
- extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
- if (!extension_sysext_scope_list)
+ scope = strv_env_pairs_get(extension_release, extension_scope);
+ if (scope) {
+ scope_list = strv_split(scope, WHITESPACE);
+ if (!scope_list)
return -ENOMEM;
}
- /* by default extension are good for attachment in portable service and on the system */
+ /* By default extension are good for attachment in portable service and on the system */
valid = strv_contains(
- extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
- host_sysext_scope);
+ scope_list ?: STRV_MAKE("system", "portable"),
+ host_extension_scope);
if (!valid) {
- log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
+ log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope);
return 0;
}
}
* the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
- !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
+ !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
- name, extension_architecture, architecture_to_string(uname_architecture()));
+ name, extension_architecture, architecture_to_string(uname_architecture()));
return 0;
}
extension_release_id = strv_env_pairs_get(extension_release, "ID");
if (isempty(extension_release_id)) {
- log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s' or be '_any'",
- name, host_os_release_id);
+ log_debug("Extension '%s' does not contain ID in release file but requested to match '%s' or be '_any'",
+ name, host_os_release_id);
return 0;
}
- /* A sysext with no host OS dependency (static binaries or scripts) can match
- * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL are not required anywhere */
+ /* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match
+ * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */
if (streq(extension_release_id, "_any")) {
log_debug("Extension '%s' matches '_any' OS.", name);
return 1;
}
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
- if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
+ if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
return 1;
}
/* If the extension has a sysext API level declared, then it must match the host API
* level. Otherwise, compare OS version as a whole */
- extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
- if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
- if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
- log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
- name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
+ extension_release_level = strv_env_pairs_get(extension_release, extension_level);
+ if (!isempty(host_os_extension_release_level) && !isempty(extension_release_level)) {
+ if (!streq_ptr(host_os_extension_release_level, extension_release_level)) {
+ log_debug("Extension '%s' is for API level '%s', but running on API level '%s'",
+ name, strna(extension_release_level), strna(host_os_extension_release_level));
return 0;
}
} else if (!isempty(host_os_release_version_id)) {
extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
if (isempty(extension_release_version_id)) {
- log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
+ log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'",
name, strna(host_os_release_version_id));
return 0;
}
name, strna(extension_release_version_id), strna(host_os_release_version_id));
return 0;
}
- } else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
+ } else if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
return 1;
return 1;
}
-int parse_env_extension_hierarchies(char ***ret_hierarchies) {
+int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) {
_cleanup_free_ char **l = NULL;
int r;
- r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &l);
+ assert(hierarchy_env);
+ r = getenv_path_list(hierarchy_env, &l);
if (r == -ENXIO) {
- /* Default when unset */
- l = strv_new("/usr", "/opt");
- if (!l)
- return -ENOMEM;
+ if (streq(hierarchy_env, "SYSTEMD_CONFEXT_HIERARCHIES"))
+ /* Default for confext when unset */
+ l = strv_new("/etc");
+ else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_HIERARCHIES"))
+ /* Default for sysext when unset */
+ l = strv_new("/usr", "/opt");
+ else
+ return -ENXIO;
} else if (r < 0)
return r;
*ret_hierarchies = TAKE_PTR(l);
return 0;
}
+
+int extension_has_forbidden_content(const char *root) {
+ int r;
+
+ /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
+ * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
+ * merged.) */
+ r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
+ if (r > 0) {
+ log_debug("Extension contains '/usr/lib/os-release', which is not allowed, refusing.");
+ return 1;
+ }
+ if (r < 0 && r != -ENOENT)
+ return log_debug_errno(r, "Failed to determine whether '/usr/lib/os-release' exists in the extension: %m");
+
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "os-util.h"
+
+/* Given an image name (for logging purposes), a set of os-release values from the host and a key-value pair
+ * vector of extension-release variables, check that the distro and (system extension level or distro
+ * version) match and return 1, and 0 otherwise. */
+int extension_release_validate(
+ const char *name,
+ const char *host_os_release_id,
+ const char *host_os_release_version_id,
+ const char *host_os_extension_release_level,
+ const char *host_extension_scope,
+ char **extension_release,
+ ImageClass image_class);
+
+/* Parse hierarchy variables and if not set, return "/usr /opt" for sysext and "/etc" for confext */
+int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env);
+
+/* Insist that extension images do not overwrite the underlying OS release file (it's fine if they place one
+ * in /etc/os-release, i.e. where things don't matter, as they aren't merged.) */
+int extension_has_forbidden_content(const char *root);
#include "parse-util.h"
#include "path-util.h"
#include "set.h"
+#include "stat-util.h"
#define MAKE_SET(s) ((Set*) s)
#define MAKE_FDSET(s) ((FDSet*) s)
return MAKE_FDSET(set_new(NULL));
}
-int fdset_new_array(FDSet **ret, const int *fds, size_t n_fds) {
- size_t i;
- FDSet *s;
+static inline void fdset_shallow_freep(FDSet **s) {
+ /* Destroys the set, but does not free the fds inside, like fdset_free()! */
+ set_free(MAKE_SET(*ASSERT_PTR(s)));
+}
+
+int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) {
+ _cleanup_(fdset_shallow_freep) FDSet *s = NULL;
int r;
assert(ret);
+ assert(fds || n_fds == 0);
s = fdset_new();
if (!s)
return -ENOMEM;
- for (i = 0; i < n_fds; i++) {
-
+ for (size_t i = 0; i < n_fds; i++) {
r = fdset_put(s, fds[i]);
- if (r < 0) {
- set_free(MAKE_SET(s));
+ if (r < 0)
return r;
- }
}
- *ret = s;
+ *ret = TAKE_PTR(s);
return 0;
}
return set_put(MAKE_SET(s), FD_TO_PTR(fd));
}
+int fdset_consume(FDSet *s, int fd) {
+ int r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ r = fdset_put(s, fd);
+ if (r < 0)
+ safe_close(fd);
+
+ return r;
+}
+
int fdset_put_dup(FDSet *s, int fd) {
- int copy, r;
+ _cleanup_close_ int copy = -EBADF;
+ int r;
assert(s);
assert(fd >= 0);
return -errno;
r = fdset_put(s, copy);
- if (r < 0) {
- safe_close(copy);
+ if (r < 0)
return r;
- }
- return copy;
+ return TAKE_FD(copy);
}
bool fdset_contains(FDSet *s, int fd) {
return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT;
}
-int fdset_new_fill(FDSet **_s) {
+int fdset_new_fill(FDSet **ret) {
+ _cleanup_(fdset_shallow_freep) FDSet *s = NULL;
_cleanup_closedir_ DIR *d = NULL;
- int r = 0;
- FDSet *s;
+ int r;
- assert(_s);
+ assert(ret);
- /* Creates an fdset and fills in all currently open file
- * descriptors. */
+ /* Creates an fdset and fills in all currently open file descriptors. */
d = opendir("/proc/self/fd");
- if (!d)
+ if (!d) {
+ if (errno == ENOENT && proc_mounted() == 0)
+ return -ENOSYS;
+
return -errno;
+ }
s = fdset_new();
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
FOREACH_DIRENT(de, d, return -errno) {
int fd = -EBADF;
r = safe_atoi(de->d_name, &fd);
if (r < 0)
- goto finish;
+ return r;
if (fd < 3)
continue;
-
if (fd == dirfd(d))
continue;
r = fdset_put(s, fd);
if (r < 0)
- goto finish;
+ return r;
}
- r = 0;
- *_s = TAKE_PTR(s);
-
-finish:
- /* We won't close the fds here! */
- if (s)
- set_free(MAKE_SET(s));
-
- return r;
+ *ret = TAKE_PTR(s);
+ return 0;
}
int fdset_cloexec(FDSet *fds, bool b) {
return 0;
}
-int fdset_new_listen_fds(FDSet **_s, bool unset) {
+int fdset_new_listen_fds(FDSet **ret, bool unset) {
+ _cleanup_(fdset_shallow_freep) FDSet *s = NULL;
int n, fd, r;
- FDSet *s;
- assert(_s);
+ assert(ret);
/* Creates an fdset and fills in all passed file descriptors */
s = fdset_new();
- if (!s) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!s)
+ return -ENOMEM;
n = sd_listen_fds(unset);
for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
r = fdset_put(s, fd);
if (r < 0)
- goto fail;
+ return r;
}
- *_s = s;
+ *ret = TAKE_PTR(s);
return 0;
-
-fail:
- if (s)
- set_free(MAKE_SET(s));
-
- return r;
}
-int fdset_close_others(FDSet *fds) {
+int fdset_to_array(FDSet *fds, int **ret) {
+ unsigned j = 0, m;
void *e;
- int *a = NULL;
- size_t j = 0, m;
+ int *a;
- m = fdset_size(fds);
+ assert(ret);
- if (m > 0) {
- a = newa(int, m);
- SET_FOREACH(e, MAKE_SET(fds))
- a[j++] = PTR_TO_FD(e);
+ m = fdset_size(fds);
+ if (m > INT_MAX) /* We want to be able to return an "int" */
+ return -ENOMEM;
+ if (m == 0) {
+ *ret = NULL; /* suppress array allocation if empty */
+ return 0;
}
+ a = new(int, m);
+ if (!a)
+ return -ENOMEM;
+
+ SET_FOREACH(e, MAKE_SET(fds))
+ a[j++] = PTR_TO_FD(e);
+
assert(j == m);
- return close_all_fds(a, j);
+ *ret = TAKE_PTR(a);
+ return (int) m;
+}
+
+int fdset_close_others(FDSet *fds) {
+ _cleanup_free_ int *a = NULL;
+ int n;
+
+ n = fdset_to_array(fds, &a);
+ if (n < 0)
+ return n;
+
+ return close_all_fds(a, n);
}
unsigned fdset_size(FDSet *fds) {
FDSet* fdset_free(FDSet *s);
int fdset_put(FDSet *s, int fd);
+int fdset_consume(FDSet *s, int fd);
int fdset_put_dup(FDSet *s, int fd);
bool fdset_contains(FDSet *s, int fd);
int fdset_cloexec(FDSet *fds, bool b);
+int fdset_to_array(FDSet *fds, int **ret);
+
int fdset_close_others(FDSet *fds);
unsigned fdset_size(FDSet *fds);
int create_shutdown_run_nologin_or_warn(void) {
int r;
- /* This is used twice: once in systemd-user-sessions.service, in order to block logins when we actually go
- * down, and once in systemd-logind.service when shutdowns are scheduled, and logins are to be turned off a bit
- * in advance. We use the same wording of the message in both cases. */
+ /* This is used twice: once in systemd-user-sessions.service, in order to block logins when we
+ * actually go down, and once in systemd-logind.service when shutdowns are scheduled, and logins are
+ * to be turned off a bit in advance. We use the same wording of the message in both cases.
+ *
+ * Traditionally, there was only /etc/nologin, and we managed that. Then, in PAM 1.1
+ * support for /run/nologin was added as alternative
+ * (https://github.com/linux-pam/linux-pam/commit/e9e593f6ddeaf975b7fe8446d184e6bc387d450b).
+ * 13 years later we stopped managing /etc/nologin, leaving it for the administrator to manage.
+ */
r = write_string_file_atomic_label("/run/nologin",
"System is going down. Unprivileged users are not permitted to log in anymore. "
}
static int verify_fsroot_dir(
+ int dir_fd,
const char *path,
bool searching,
bool unprivileged_mode,
dev_t *ret_dev) {
- _cleanup_close_ int fd = -EBADF;
+ _cleanup_free_ char *f = NULL;
STRUCT_NEW_STATX_DEFINE(sxa);
STRUCT_NEW_STATX_DEFINE(sxb);
int r;
/* Checks if the specified directory is at the root of its file system, and returns device
* major/minor of the device, if it is. */
+ assert(dir_fd >= 0);
assert(path);
- /* We are using O_PATH here, since that way we can operate on directory inodes we cannot look into,
- * which is quite likely if we run unprivileged */
- fd = open(path, O_CLOEXEC|O_DIRECTORY|O_PATH);
- if (fd < 0)
- return log_full_errno((searching && errno == ENOENT) ||
- (unprivileged_mode && ERRNO_IS_PRIVILEGE(errno)) ? LOG_DEBUG : LOG_ERR, errno,
- "Failed to open directory \"%s\": %m", path);
+ /* We pass the full path from the root directory file descriptor so we can use it for logging, but
+ * dir_fd points to the parent directory of the final component of the given path, so we extract the
+ * filename and operate on that. */
- /* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the
- * directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here,
- * before stat()ing */
- (void) faccessat(fd, "trigger", F_OK, AT_SYMLINK_NOFOLLOW); /* Filename doesn't matter... */
+ r = path_extract_filename(path, &f);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return log_error_errno(r, "Failed to extract filename of %s: %m", path);
- r = statx_fallback(fd, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxa.sx);
+ r = statx_fallback(dir_fd, strempty(f), AT_SYMLINK_NOFOLLOW|(isempty(f) ? AT_EMPTY_PATH : 0),
+ STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxa.sx);
if (r < 0)
- return log_full_errno((unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r,
+ return log_full_errno((searching && r == -ENOENT) ||
+ (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r,
"Failed to determine block device node of \"%s\": %m", path);
assert(S_ISDIR(sxa.sx.stx_mode)); /* We used O_DIRECTORY above, when opening, so this must hold */
}
/* Now let's look at the parent */
- r = statx_fallback(fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb.sx);
- if (r < 0 && ERRNO_IS_PRIVILEGE(r)) {
- _cleanup_free_ char *parent = NULL;
-
- /* If going via ".." didn't work due to EACCESS, then let's determine the parent path
- * directly instead. It's not as good, due to symlinks and such, but we can't do anything
- * better here.
- *
- * (In case you wonder where this fallback is useful: consider a classic Fedora setup with
- * /boot/ being an ext4 partition and /boot/efi/ being the VFAT ESP. The latter is mounted
- * inaccessible for regular users via the dmask= mount option. In that case as unprivileged
- * user we can stat() /boot/efi/, and we can stat()/enumerate /boot/. But we cannot look into
- * /boot/efi/, and in particular not use /boot/efi/../ – hence this work-around.) */
-
- if (path_equal(path, "/"))
- goto success;
-
- r = path_extract_directory(path, &parent);
- if (r < 0)
- return log_error_errno(r, "Failed to extract parent path from '%s': %m", path);
-
- r = statx_fallback(AT_FDCWD, parent, AT_SYMLINK_NOFOLLOW, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb.sx);
- }
+ r = statx_fallback(dir_fd, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb.sx);
if (r < 0)
return log_full_errno(unprivileged_mode && ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_ERR, r,
"Failed to determine block device node of parent of \"%s\": %m", path);
if (!ret_dev)
return 0;
- if (sxa.sx.stx_dev_major == 0) { /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */
- _cleanup_close_ int real_fd = -EBADF;
-
- /* The statx() above we can execute on an O_PATH fd. But the btrfs ioctl we cannot. Hence
- * acquire a "real" fd first, without the O_PATH flag. */
-
- real_fd = fd_reopen(fd, O_DIRECTORY|O_CLOEXEC);
- if (real_fd < 0)
- return real_fd;
-
- return btrfs_get_block_device_fd(real_fd, ret_dev);
- }
+ if (sxa.sx.stx_dev_major == 0) /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */
+ return btrfs_get_block_device_at(dir_fd, strempty(f), ret_dev);
*ret_dev = makedev(sxa.sx.stx_dev_major, sxa.sx.stx_dev_minor);
return 0;
}
static int verify_esp(
- const char *p,
+ int rfd,
+ const char *path,
+ char **ret_path,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
bool relax_checks, searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING),
unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE);
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int pfd = -EBADF;
dev_t devid = 0;
int r;
- assert(p);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(path);
/* This logs about all errors, except:
*
/* Non-root user can only check the status, so if an error occurred in the following, it does not cause any
* issues. Let's also, silence the error messages. */
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT, &p, &pfd);
+ if (r < 0)
+ return log_full_errno((searching && r == -ENOENT) ||
+ (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR,
+ r, "Failed to open parent directory of \"%s\": %m", path);
+
if (!relax_checks) {
+ _cleanup_free_ char *f = NULL;
struct statfs sfs;
- if (statfs(p, &sfs) < 0)
+ r = path_extract_filename(p, &f);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return log_error_errno(r, "Failed to extract filename of %s: %m", p);
+
+ r = xstatfsat(pfd, strempty(f), &sfs);
+ if (r < 0)
/* If we are searching for the mount point, don't generate a log message if we can't find the path */
- return log_full_errno((searching && errno == ENOENT) ||
- (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
+ return log_full_errno((searching && r == -ENOENT) ||
+ (unprivileged_mode && r == -EACCES) ? LOG_DEBUG : LOG_ERR, r,
"Failed to check file system type of \"%s\": %m", p);
if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
relax_checks ||
detect_container() > 0;
- r = verify_fsroot_dir(p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
+ r = verify_fsroot_dir(pfd, p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
if (r < 0)
return r;
if (r < 0)
return r;
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
if (ret_devid)
*ret_devid = devid;
return 0;
finish:
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
if (ret_part)
*ret_part = 0;
if (ret_pstart)
return 0;
}
-int find_esp_and_warn(
- const char *root,
+int find_esp_and_warn_at(
+ int rfd,
const char *path,
bool unprivileged_mode,
char **ret_path,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
- VerifyESPFlags flags = (unprivileged_mode ? VERIFY_ESP_UNPRIVILEGED_MODE : 0) |
- (root ? VERIFY_ESP_RELAX_CHECKS : 0);
- _cleanup_free_ char *p = NULL;
+ VerifyESPFlags flags = (unprivileged_mode ? VERIFY_ESP_UNPRIVILEGED_MODE : 0);
int r;
/* This logs about all errors except:
* -EACCESS → when unprivileged_mode is true, and we can't access something
*/
- if (path) {
- r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
- if (r < 0)
- return log_error_errno(r,
- "Failed to resolve path %s%s%s: %m",
- path,
- root ? " under directory " : "",
- strempty(root));
+ assert(rfd >= 0 || rfd == AT_FDCWD);
- r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags);
- if (r < 0)
- return r;
+ r = dir_fd_is_root_or_cwd(rfd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+ if (r == 0)
+ flags |= VERIFY_ESP_RELAX_CHECKS;
- goto found;
- }
+ if (path)
+ return verify_esp(rfd, path, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags);
path = getenv("SYSTEMD_ESP_PATH");
if (path) {
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int fd = -EBADF;
struct stat st;
- r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
- if (r < 0)
- return log_error_errno(r,
- "Failed to resolve path %s%s%s: %m",
- path,
- root ? " under directory " : "",
- strempty(root));
-
- if (!path_is_valid(p) || !path_is_absolute(p))
+ if (!path_is_valid(path) || !path_is_absolute(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
- p);
+ "$SYSTEMD_ESP_PATH does not refer to an absolute path, refusing to use it: %s",
+ path);
+
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, &p, &fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve path %s: %m", path);
/* Note: when the user explicitly configured things with an env var we won't validate the
* path beyond checking it refers to a directory. After all we want this to be useful for
* testing. */
- if (stat(p, &st) < 0)
+ if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", p);
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p);
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
if (ret_part)
*ret_part = 0;
if (ret_pstart)
if (ret_devid)
*ret_devid = st.st_dev;
- goto found;
+ return 0;
}
FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") {
- r = chase(dir, root, CHASE_PREFIX_ROOT, &p, NULL);
- if (r == -ENOENT)
- continue;
- if (r < 0)
- return log_error_errno(r,
- "Failed to resolve path %s%s%s: %m",
- dir,
- root ? " under directory " : "",
- strempty(root));
-
- r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid,
+ r = verify_esp(rfd, dir, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid,
flags | VERIFY_ESP_SEARCHING);
if (r >= 0)
- goto found;
+ return 0;
if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR, -ENOTTY)) /* This one is not it */
return r;
-
- p = mfree(p);
}
/* No logging here */
return -ENOKEY;
+}
-found:
- if (ret_path)
- *ret_path = TAKE_PTR(p);
+int find_esp_and_warn(
+ const char *root,
+ const char *path,
+ bool unprivileged_mode,
+ char **ret_path,
+ uint32_t *ret_part,
+ uint64_t *ret_pstart,
+ uint64_t *ret_psize,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
+
+ _cleanup_close_ int rfd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ uint32_t part;
+ uint64_t pstart, psize;
+ sd_id128_t uuid;
+ dev_t devid;
+ int r;
+
+ rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC);
+ if (rfd < 0)
+ return -errno;
+
+ r = find_esp_and_warn_at(rfd, path, unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_part ? &part : NULL,
+ ret_pstart ? &pstart : NULL,
+ ret_psize ? &psize : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
+ if (r < 0)
+ return r;
+
+ if (ret_path) {
+ char *q = path_join(empty_to_root(root), p);
+ if (!q)
+ return -ENOMEM;
+
+ *ret_path = TAKE_PTR(q);
+ }
+ if (ret_part)
+ *ret_part = part;
+ if (ret_pstart)
+ *ret_pstart = pstart;
+ if (ret_psize)
+ *ret_psize = psize;
+ if (ret_uuid)
+ *ret_uuid = uuid;
+ if (ret_devid)
+ *ret_devid = devid;
return 0;
}
}
static int verify_xbootldr(
- const char *p,
+ int rfd,
+ const char *path,
bool searching,
bool unprivileged_mode,
+ char **ret_path,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int pfd = -EBADF;
bool relax_checks;
dev_t devid = 0;
int r;
- assert(p);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(path);
+
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT, &p, &pfd);
+ if (r < 0)
+ return log_full_errno((searching && r == -ENOENT) ||
+ (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR,
+ r, "Failed to open parent directory of \"%s\": %m", path);
relax_checks =
getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0 ||
detect_container() > 0;
- r = verify_fsroot_dir(p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
+ r = verify_fsroot_dir(pfd, p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
if (r < 0)
return r;
if (r < 0)
return r;
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
if (ret_devid)
*ret_devid = devid;
return 0;
finish:
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
return 0;
}
-int find_xbootldr_and_warn(
- const char *root,
+int find_xbootldr_and_warn_at(
+ int rfd,
const char *path,
bool unprivileged_mode,
char **ret_path,
sd_id128_t *ret_uuid,
dev_t *ret_devid) {
- _cleanup_free_ char *p = NULL;
int r;
/* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
- if (path) {
- r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
- if (r < 0)
- return log_error_errno(r,
- "Failed to resolve path %s%s%s: %m",
- path,
- root ? " under directory " : "",
- strempty(root));
-
- r = verify_xbootldr(p, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid);
- if (r < 0)
- return r;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
- goto found;
- }
+ if (path)
+ return verify_xbootldr(rfd, path, /* searching= */ false, unprivileged_mode, ret_path, ret_uuid, ret_devid);
path = getenv("SYSTEMD_XBOOTLDR_PATH");
if (path) {
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int fd = -EBADF;
struct stat st;
- r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
- if (r < 0)
- return log_error_errno(r,
- "Failed to resolve path %s%s%s: %m",
- path,
- root ? " under directory " : "",
- strempty(root));
-
- if (!path_is_valid(p) || !path_is_absolute(p))
+ if (!path_is_valid(path) || !path_is_absolute(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
- p);
+ "$SYSTEMD_XBOOTLDR_PATH does not refer to an absolute path, refusing to use it: %s",
+ path);
- if (stat(p, &st) < 0)
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, &p, &fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve path %s: %m", p);
+
+ if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", p);
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p);
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
if (ret_uuid)
*ret_uuid = SD_ID128_NULL;
if (ret_devid)
*ret_devid = st.st_dev;
- goto found;
+ return 0;
}
- r = chase("/boot", root, CHASE_PREFIX_ROOT, &p, NULL);
- if (r == -ENOENT)
+ r = verify_xbootldr(rfd, "/boot", /* searching= */ true, unprivileged_mode, ret_path, ret_uuid, ret_devid);
+ if (r < 0) {
+ if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+ return r;
+
return -ENOKEY;
- if (r < 0)
- return log_error_errno(r,
- "Failed to resolve path /boot%s%s: %m",
- root ? " under directory " : "",
- strempty(root));
+ }
+
+ return 0;
+}
- r = verify_xbootldr(p, /* searching= */ true, unprivileged_mode, ret_uuid, ret_devid);
- if (r >= 0)
- goto found;
- if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+int find_xbootldr_and_warn(
+ const char *root,
+ const char *path,
+ bool unprivileged_mode,
+ char **ret_path,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
+
+ _cleanup_close_ int rfd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ sd_id128_t uuid;
+ dev_t devid;
+ int r;
+
+ rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC);
+ if (rfd < 0)
+ return -errno;
+
+ r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
+ if (r < 0)
return r;
- return -ENOKEY;
+ if (ret_path) {
+ char *q = path_join(empty_to_root(root), p);
+ if (!q)
+ return -ENOMEM;
-found:
- if (ret_path)
- *ret_path = TAKE_PTR(p);
+ *ret_path = TAKE_PTR(q);
+ }
+ if (ret_uuid)
+ *ret_uuid = uuid;
+ if (ret_devid)
+ *ret_devid = devid;
return 0;
}
#include "sd-id128.h"
+int find_esp_and_warn_at(int rfd, const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
int find_esp_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
+
+int find_xbootldr_and_warn_at(int rfd, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid);
int find_xbootldr_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid);
#include "sd-id128.h"
#include "alloc-util.h"
+#include "devnum-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "process-util.h"
#include "signal-util.h"
#include "sort-util.h"
+#include "stat-util.h"
#include "string-util.h"
#include "strxcpyx.h"
#include "terminal-util.h"
gid_t gid;
pid_t pid;
mode_t mode;
+ dev_t devnum;
/* … add more here as we start supporting more cell data types … */
};
} TableData;
return sizeof(pid_t);
case TABLE_MODE:
+ case TABLE_MODE_INODE_TYPE:
return sizeof(mode_t);
+ case TABLE_DEVNUM:
+ return sizeof(dev_t);
+
default:
assert_not_reached();
}
gid_t gid;
pid_t pid;
mode_t mode;
+ dev_t devnum;
} buffer;
switch (type) {
break;
case TABLE_MODE:
+ case TABLE_MODE_INODE_TYPE:
buffer.mode = va_arg(ap, mode_t);
data = &buffer.mode;
break;
+ case TABLE_DEVNUM:
+ buffer.devnum = va_arg(ap, dev_t);
+ data = &buffer.devnum;
+ break;
+
case TABLE_SET_MINIMUM_WIDTH: {
size_t w = va_arg(ap, size_t);
}
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
+ int r;
+
assert(a);
assert(b);
return CMP(a->pid, b->pid);
case TABLE_MODE:
+ case TABLE_MODE_INODE_TYPE:
return CMP(a->mode, b->mode);
+ case TABLE_DEVNUM:
+ r = CMP(major(a->devnum), major(b->devnum));
+ if (r != 0)
+ return r;
+
+ return CMP(minor(a->devnum), minor(b->devnum));
+
default:
;
}
break;
}
+ case TABLE_MODE_INODE_TYPE:
+
+ if (d->mode == MODE_INVALID)
+ return table_ersatz_string(t);
+
+ return inode_type_to_string(d->mode);
+
+ case TABLE_DEVNUM:
+ if (devnum_is_zero(d->devnum))
+ return table_ersatz_string(t);
+
+ if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
+ return NULL;
+
+ break;
+
default:
assert_not_reached();
}
return json_variant_new_integer(ret, d->int_val);
case TABLE_MODE:
+ case TABLE_MODE_INODE_TYPE:
if (d->mode == MODE_INVALID)
return json_variant_new_null(ret);
return json_variant_new_unsigned(ret, d->mode);
+ case TABLE_DEVNUM:
+ if (devnum_is_zero(d->devnum))
+ return json_variant_new_null(ret);
+
+ return json_build(ret, JSON_BUILD_ARRAY(
+ JSON_BUILD_UNSIGNED(major(d->devnum)),
+ JSON_BUILD_UNSIGNED(minor(d->devnum))));
+
default:
return -EINVAL;
}
TABLE_GID,
TABLE_PID,
TABLE_SIGNAL,
- TABLE_MODE, /* as in UNIX file mode (mode_t), in typical octal output */
+ TABLE_MODE, /* as in UNIX file mode (mode_t), in typical octal output */
+ TABLE_MODE_INODE_TYPE, /* also mode_t, but displays only the inode type as string */
+ TABLE_DEVNUM, /* a dev_t, displayed in the usual major:minor way */
_TABLE_DATA_TYPE_MAX,
/* The following are not really data types, but commands for table_add_cell_many() to make changes to
#include "sd-id128.h"
#include "alloc-util.h"
+#include "chase.h"
#include "fd-util.h"
#include "id128-util.h"
#include "io-util.h"
#include "virt.h"
static int generate_machine_id(const char *root, sd_id128_t *ret) {
- const char *dbus_machine_id;
_cleanup_close_ int fd = -EBADF;
int r;
assert(ret);
/* First, try reading the D-Bus machine id, unless it is a symlink */
- dbus_machine_id = prefix_roota(root, "/var/lib/dbus/machine-id");
- fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
- if (fd >= 0) {
- if (id128_read_fd(fd, ID128_FORMAT_PLAIN, ret) >= 0) {
- log_info("Initializing machine ID from D-Bus machine ID.");
- return 0;
- }
-
- fd = safe_close(fd);
+ fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT | CHASE_NOFOLLOW, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ if (fd >= 0 && id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret) >= 0) {
+ log_info("Initializing machine ID from D-Bus machine ID.");
+ return 0;
}
if (isempty(root) && running_in_chroot() <= 0) {
'ethtool-util.c',
'exec-util.c',
'exit-status.c',
- 'extension-release.c',
+ 'extension-util.c',
'fdset.c',
'fileio-label.c',
'find-esp.c',
if (extra_mkfs_args && strv_extend_strv(&argv, extra_mkfs_args, false) < 0)
return log_oom();
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *j = NULL;
+
+ j = strv_join(argv, " ");
+ log_debug("Executing mkfs command: %s", strna(j));
+ }
+
r = safe_fork_full(
"(mkfs)",
stdio_fds,
#include "cgroup-util.h"
#include "dirent-util.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "log.h"
#include "macro.h"
#include "mountpoint-util.h"
static int patch_dirfd_mode(
int dfd,
+ bool refuse_already_set,
mode_t *ret_old_mode) {
struct stat st;
+ int r;
assert(dfd >= 0);
assert(ret_old_mode);
return -errno;
if (!S_ISDIR(st.st_mode))
return -ENOTDIR;
- if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */
- return -EACCES; /* original error */
+
+ if (FLAGS_SET(st.st_mode, 0700)) { /* Already set? */
+ if (refuse_already_set)
+ return -EACCES; /* original error */
+
+ *ret_old_mode = st.st_mode;
+ return 0;
+ }
+
if (st.st_uid != geteuid()) /* this only works if the UID matches ours */
return -EACCES;
- if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
- return -errno;
+ r = fchmod_opath(dfd, (st.st_mode | 0700) & 07777);
+ if (r < 0)
+ return r;
*ret_old_mode = st.st_mode;
- return 0;
+ return 1;
}
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
return -errno;
- r = patch_dirfd_mode(dfd, &old_mode);
+ r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
if (r < 0)
return r;
if (unlinkat(dfd, filename, unlink_flags) < 0) {
r = -errno;
/* Try to restore the original access mode if this didn't work */
- (void) fchmod(dfd, old_mode);
+ (void) fchmod(dfd, old_mode & 07777);
return r;
}
- if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
+ if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
return -errno;
/* If this worked, we won't reset the old mode by default, since we'll need it for other entries too,
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
return -errno;
- r = patch_dirfd_mode(dfd, &old_mode);
+ r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
if (r < 0)
return r;
if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
r = -errno;
- (void) fchmod(dfd, old_mode);
+ (void) fchmod(dfd, old_mode & 07777);
return r;
}
- if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
+ if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
return -errno;
return 0;
}
+static int openat_harder(int dfd, const char *path, int open_flags, RemoveFlags remove_flags, mode_t *ret_old_mode) {
+ _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
+ bool chmod_done = false;
+ mode_t old_mode;
+ int r;
+
+ assert(dfd >= 0 || dfd == AT_FDCWD);
+ assert(path);
+
+ /* Unlike unlink_harder() and fstatat_harder(), this chmod the specified path. */
+
+ if (FLAGS_SET(open_flags, O_PATH) ||
+ !FLAGS_SET(open_flags, O_DIRECTORY) ||
+ !FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
+
+ fd = RET_NERRNO(openat(dfd, path, open_flags));
+ if (fd < 0)
+ return fd;
+
+ if (ret_old_mode) {
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ *ret_old_mode = st.st_mode;
+ }
+
+ return TAKE_FD(fd);
+ }
+
+ pfd = RET_NERRNO(openat(dfd, path, (open_flags & (O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW)) | O_PATH));
+ if (pfd < 0)
+ return pfd;
+
+ if (FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
+ r = patch_dirfd_mode(pfd, /* refuse_already_set = */ false, &old_mode);
+ if (r < 0)
+ return r;
+
+ chmod_done = r;
+ }
+
+ fd = fd_reopen(pfd, open_flags & ~O_NOFOLLOW);
+ if (fd < 0) {
+ if (chmod_done)
+ (void) fchmod_opath(pfd, old_mode & 07777);
+ return fd;
+ }
+
+ if (ret_old_mode)
+ *ret_old_mode = old_mode;
+
+ return TAKE_FD(fd);
+}
+
+static int rm_rf_children_impl(
+ int fd,
+ RemoveFlags flags,
+ const struct stat *root_dev,
+ mode_t old_mode);
+
static int rm_rf_inner_child(
int fd,
const char *fname,
if (!allow_recursion)
return -EISDIR;
- int subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ mode_t old_mode;
+ int subdir_fd = openat_harder(fd, fname,
+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
+ flags, &old_mode);
if (subdir_fd < 0)
- return -errno;
+ return subdir_fd;
/* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
* again for each directory */
- q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
+ q = rm_rf_children_impl(subdir_fd, flags | REMOVE_PHYSICAL, root_dev, old_mode);
} else if (flags & REMOVE_ONLY_DIRECTORIES)
return 0;
typedef struct TodoEntry {
DIR *dir; /* A directory that we were operating on. */
char *dirname; /* The filename of that directory itself. */
+ mode_t old_mode; /* The original file mode. */
} TodoEntry;
static void free_todo_entries(TodoEntry **todos) {
RemoveFlags flags,
const struct stat *root_dev) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ return rm_rf_children_impl(fd, flags, root_dev, st.st_mode);
+}
+
+static int rm_rf_children_impl(
+ int fd,
+ RemoveFlags flags,
+ const struct stat *root_dev,
+ mode_t old_mode) {
+
_cleanup_(free_todo_entries) TodoEntry *todos = NULL;
size_t n_todo = 0;
_cleanup_free_ char *dirname = NULL; /* Set when we are recursing and want to delete ourselves */
* We need to remove the inner directory we were operating on. */
assert(dirname);
r = unlinkat_harder(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR, flags);
- if (r < 0 && r != -ENOENT && ret == 0)
- ret = r;
+ if (r < 0 && r != -ENOENT) {
+ if (ret == 0)
+ ret = r;
+
+ if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE))
+ (void) fchmodat(dirfd(todos[n_todo-1].dir), dirname, old_mode & 07777, 0);
+ }
dirname = mfree(dirname);
/* And now let's back out one level up */
n_todo --;
d = TAKE_PTR(todos[n_todo].dir);
dirname = TAKE_PTR(todos[n_todo].dirname);
+ old_mode = todos[n_todo].old_mode;
assert(d);
fd = dirfd(d); /* Retrieve the file descriptor from the DIR object */
if (!newdirname)
return log_oom();
- int newfd = openat(fd, de->d_name,
- O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ mode_t mode;
+ int newfd = openat_harder(fd, de->d_name,
+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
+ flags, &mode);
if (newfd >= 0) {
- todos[n_todo++] = (TodoEntry) { TAKE_PTR(d), TAKE_PTR(dirname) };
+ todos[n_todo++] = (TodoEntry) {
+ .dir = TAKE_PTR(d),
+ .dirname = TAKE_PTR(dirname),
+ .old_mode = old_mode
+ };
+
fd = newfd;
dirname = TAKE_PTR(newdirname);
+ old_mode = mode;
goto next_fd;
- } else if (errno != -ENOENT && ret == 0)
- ret = -errno;
+ } else if (newfd != -ENOENT && ret == 0)
+ ret = newfd;
} else if (r < 0 && r != -ENOENT && ret == 0)
ret = r;
if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0)
ret = -errno;
- if (n_todo == 0)
+ if (n_todo == 0) {
+ if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE) &&
+ fchmod(fd, old_mode & 07777) < 0 && ret >= 0)
+ ret = -errno;
+
break;
+ }
}
return ret;
}
int rm_rf(const char *path, RemoveFlags flags) {
+ mode_t old_mode;
int fd, r, q = 0;
assert(path);
/* Not btrfs or not a subvolume */
}
- fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ fd = openat_harder(AT_FDCWD, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode);
if (fd >= 0) {
/* We have a dir */
- r = rm_rf_children(fd, flags, NULL);
+ r = rm_rf_children_impl(fd, flags, NULL, old_mode);
if (FLAGS_SET(flags, REMOVE_ROOT))
q = RET_NERRNO(rmdir(path));
} else {
- if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
+ r = fd;
+ if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
return 0;
- if (!IN_SET(errno, ENOTDIR, ELOOP))
- return -errno;
+ if (!IN_SET(r, -ENOTDIR, -ELOOP))
+ return r;
if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT))
return 0;
assert(ret);
- if (root) {
- _cleanup_close_ int fd = -EBADF;
-
- fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
- if (fd < 0)
- /* Translate error for missing os-release file to EUNATCH. */
- return fd == -ENOENT ? -EUNATCH : fd;
-
- r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &id);
- } else
- r = sd_id128_get_machine(&id);
- if (r < 0)
- return r;
+ r = id128_get_machine(root, &id);
+ if (r < 0) /* Translate error for missing /etc/machine-id file to EUNATCH. */
+ return r == -ENOENT ? -EUNATCH : r;
return specifier_id128(specifier, &id, root, userdata, ret);
}
#include "fs-util.h"
#include "hexdecoct.h"
#include "hmac.h"
+#include "lock-util.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "parse-util.h"
TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
+TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle);
void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
void (*sym_Esys_Free)(void *ptr) = NULL;
TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
+TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName);
TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask);
TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name);
+TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle);
+TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object);
+TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size);
TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL;
TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation);
&libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG,
DLSYM_ARG(Esys_Create),
DLSYM_ARG(Esys_CreatePrimary),
+ DLSYM_ARG(Esys_EvictControl),
DLSYM_ARG(Esys_Finalize),
DLSYM_ARG(Esys_FlushContext),
DLSYM_ARG(Esys_Free),
DLSYM_ARG(Esys_PolicyAuthValue),
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyPCR),
+ DLSYM_ARG(Esys_ReadPublic),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
DLSYM_ARG(Esys_TRSess_SetAttributes),
+ DLSYM_ARG(Esys_TR_FromTPMPublic),
DLSYM_ARG(Esys_TR_GetName),
+ DLSYM_ARG(Esys_TR_Deserialize),
+ DLSYM_ARG(Esys_TR_Serialize),
DLSYM_ARG(Esys_TR_SetAuth),
DLSYM_ARG(Esys_Unseal),
DLSYM_ARG(Esys_VerifySignature));
return NULL;
_cleanup_tpm2_context_ Tpm2Context *context = (Tpm2Context*)handle->tpm2_context;
- if (context)
+ if (context && !handle->keep)
tpm2_handle_flush(context->esys_context, handle->esys_handle);
return mfree(handle);
return 0;
}
-static int tpm2_make_primary(
- Tpm2Context *c,
- Tpm2Handle **ret_primary,
- TPMI_ALG_PUBLIC alg,
- TPMI_ALG_PUBLIC *ret_alg) {
+const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) {
- static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
- static const TPM2B_PUBLIC primary_template_ecc = {
- .size = sizeof(TPMT_PUBLIC),
- .publicArea = {
- .type = TPM2_ALG_ECC,
- .nameAlg = TPM2_ALG_SHA256,
- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
- .parameters.eccDetail = {
- .symmetric = {
- .algorithm = TPM2_ALG_AES,
- .keyBits.aes = 128,
- .mode.aes = TPM2_ALG_CFB,
+ /*
+ * Set up array so flags can be used directly as an input.
+ *
+ * Templates for SRK come from the spec:
+ * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
+ *
+ * However, note their is some lore here. On Linux, the SRK has it's unique field set to size 0 and
+ * on Windows the SRK has their unique data set to keyLen in bytes of zeros.
+ */
+ assert(flags >= 0);
+ assert(flags <= _TPM2_SRK_TEMPLATE_MAX);
+
+ static const TPM2B_PUBLIC templ[_TPM2_SRK_TEMPLATE_MAX + 1] = {
+ /* index 0 RSA old */
+ [0] = {
+ .publicArea = {
+ .type = TPM2_ALG_RSA,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+ .parameters.rsaDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .keyBits = 2048,
},
- .scheme.scheme = TPM2_ALG_NULL,
- .curveID = TPM2_ECC_NIST_P256,
- .kdf.scheme = TPM2_ALG_NULL,
},
},
- };
- static const TPM2B_PUBLIC primary_template_rsa = {
- .size = sizeof(TPMT_PUBLIC),
- .publicArea = {
- .type = TPM2_ALG_RSA,
- .nameAlg = TPM2_ALG_SHA256,
- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
- .parameters.rsaDetail = {
- .symmetric = {
- .algorithm = TPM2_ALG_AES,
- .keyBits.aes = 128,
- .mode.aes = TPM2_ALG_CFB,
+ [TPM2_SRK_TEMPLATE_ECC] = {
+ .publicArea = {
+ .type = TPM2_ALG_ECC,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+ .parameters.eccDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .curveID = TPM2_ECC_NIST_P256,
+ .kdf.scheme = TPM2_ALG_NULL,
+ },
+ },
+ },
+ [TPM2_SRK_TEMPLATE_NEW_STYLE] = {
+ .publicArea = {
+ .type = TPM2_ALG_RSA,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+ .parameters.rsaDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .keyBits = 2048,
+ },
+ },
+ },
+ [TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC] = {
+ .publicArea = {
+ .type = TPM2_ALG_ECC,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+ .parameters.eccDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .curveID = TPM2_ECC_NIST_P256,
+ .kdf.scheme = TPM2_ALG_NULL,
},
- .scheme.scheme = TPM2_ALG_NULL,
- .keyBits = 2048,
},
},
};
+ return &templ[flags];
+}
+
+/*
+ * Why and what is an SRK?
+ * TL;DR provides a working space for those without owner auth. The user enrolling
+ * the disk may not have access to the TPMs owner hierarchy auth, so they need a
+ * working space. This working space is at the defined address of 0x81000001.
+ * Details can be found here:
+ * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
+ */
+#define SRK_HANDLE UINT32_C(0x81000001)
+
+/*
+ * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present
+ * and < 0 on error
+ */
+static int tpm2_get_srk(
+ Tpm2Context *c,
+ TPMI_ALG_PUBLIC *ret_alg,
+ Tpm2Handle *ret_primary) {
+
+ TPMI_YES_NO more_data;
+ ESYS_TR primary_tr = ESYS_TR_NONE;
+ _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL;
+
+ assert(c);
+ assert(ret_primary);
+
+ TSS2_RC rc = sym_Esys_GetCapability(c->esys_context,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ TPM2_CAP_HANDLES,
+ SRK_HANDLE,
+ 1,
+ &more_data,
+ &cap_data);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to enumerate handles searching for SRK: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ /* Did Not find SRK, indicate this by returning 0 */
+ if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) {
+ ret_primary->esys_handle = ESYS_TR_NONE;
+
+ if (ret_alg)
+ *ret_alg = 0;
+ return 0;
+ }
+
+ log_debug("Found SRK on TPM.");
+
+ /* convert the raw handle to an ESYS_TR */
+ TPM2_HANDLE handle = cap_data->data.handles.handle[0];
+ rc = sym_Esys_TR_FromTPMPublic(c->esys_context,
+ handle,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &primary_tr);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to convert ray handle to ESYS_TR for SRK: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ /* Get the algorithm if the caller wants it */
+ _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL;
+ if (ret_alg) {
+ rc = sym_Esys_ReadPublic(
+ c->esys_context,
+ primary_tr,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &out_public,
+ NULL,
+ NULL);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to convert ray handle to ESYS_TR for SRK: %s",
+ sym_Tss2_RC_Decode(rc));
+ }
+
+ ret_primary->esys_handle = primary_tr;
+
+ if (ret_alg)
+ *ret_alg = out_public->publicArea.type;
+
+ return 1;
+}
+
+static int tpm2_make_primary(
+ Tpm2Context *c,
+ TPMI_ALG_PUBLIC alg,
+ bool use_srk_model,
+ TPMI_ALG_PUBLIC *ret_alg,
+ Tpm2Handle **ret_primary) {
+
+ static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
static const TPML_PCR_SELECTION creation_pcr = {};
+ const TPM2B_PUBLIC *primary_template = NULL;
+ Tpm2SRKTemplateFlags base_flags = use_srk_model ? TPM2_SRK_TEMPLATE_NEW_STYLE : 0;
+ _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT;
TSS2_RC rc;
usec_t ts;
int r;
- log_debug("Creating primary key on TPM.");
+ log_debug("Creating %s on TPM.", use_srk_model ? "SRK" : "Transient Primary Key");
/* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much
* faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's
if (r < 0)
return r;
+ /* we only need the SRK lock when making the SRK since its not atomic, transient
+ * primary creations don't even matter if they stomp on each other, the TPM will
+ * keep kicking back the same key.
+ */
+ if (use_srk_model) {
+ r = make_lock_file("/run/systemd/tpm2-srk-init", LOCK_EX, &srk_lock);
+ if (r < 0)
+ return log_error_errno(r, "Failed to take TPM SRK lock: %m");
+ }
+
+ /* Find existing SRK and use it if present */
+ if (use_srk_model) {
+ TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL;
+ r = tpm2_get_srk(c, &got_alg, primary);
+ if (r < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to establish if SRK is present");
+ if (r == 1) {
+ log_debug("Discovered existing SRK");
+
+ if (alg != 0 && alg != got_alg)
+ log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring",
+ alg, got_alg);
+
+ if (ret_alg)
+ *ret_alg = alg;
+ if (ret_primary)
+ *ret_primary = TAKE_PTR(primary);
+ return 0;
+ }
+ log_debug("Did not find SRK, generating...");
+ }
+
if (IN_SET(alg, 0, TPM2_ALG_ECC)) {
+ primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC);
+
rc = sym_Esys_CreatePrimary(
c->esys_context,
ESYS_TR_RH_OWNER,
ESYS_TR_NONE,
ESYS_TR_NONE,
&primary_sensitive,
- &primary_template_ecc,
+ primary_template,
NULL,
&creation_pcr,
&primary->esys_handle,
}
if (IN_SET(alg, 0, TPM2_ALG_RSA)) {
+ primary_template = tpm2_get_primary_template(base_flags);
+
rc = sym_Esys_CreatePrimary(
c->esys_context,
ESYS_TR_RH_OWNER,
ESYS_TR_NONE,
ESYS_TR_NONE,
&primary_sensitive,
- &primary_template_rsa,
+ primary_template,
NULL,
&creation_pcr,
&primary->esys_handle,
log_debug("Successfully created RSA primary key on TPM.");
}
- log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC));
+ log_debug("Generating %s on the TPM2 took %s.", use_srk_model ? "SRK" : "Transient Primary Key",
+ FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC));
+
+ if (use_srk_model) {
+ rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle,
+ ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, SRK_HANDLE, &primary->esys_handle);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc));
+ primary->keep = true;
+ }
if (ret_primary)
*ret_primary = TAKE_PTR(primary);
void **ret_pcr_hash,
size_t *ret_pcr_hash_size,
uint16_t *ret_pcr_bank,
- uint16_t *ret_primary_alg) {
+ uint16_t *ret_primary_alg,
+ void **ret_srk_buf,
+ size_t *ret_srk_buf_size) {
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
+ _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL;
static const TPML_PCR_SELECTION creation_pcr = {};
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *hash = NULL;
TPM2B_PUBLIC hmac_template;
usec_t start;
TSS2_RC rc;
+ size_t srk_buf_size;
int r;
assert(pubkey || pubkey_size == 0);
return r;
_cleanup_tpm2_handle_ Tpm2Handle *primary = NULL;
- r = tpm2_make_primary(c, &primary, 0, &primary_alg);
+ r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary);
if (r < 0)
return r;
if (!hash)
return log_oom();
+ /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both
+ * the raw TPM handle as well as the object name. The object name is used to verify that
+ * the key we use later is the key we expect to establish the session with.
+ */
+ if (ret_srk_buf) {
+ log_debug("Serializing SRK ESYS_TR reference");
+ rc = sym_Esys_TR_Serialize(c->esys_context, primary->esys_handle, &srk_buf, &srk_buf_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to serialize primary key: %s", sym_Tss2_RC_Decode(rc));
+ }
+
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
+ if (ret_srk_buf) {
+ /*
+ * make a copy since we don't want the caller to understand that
+ * ESYS allocated the pointer. It would make tracking what deallocator
+ * to use for srk_buf in which context a PITA.
+ */
+ void *tmp = memdup(srk_buf, srk_buf_size);
+ if (!tmp)
+ return log_oom();
+
+ *ret_srk_buf = TAKE_PTR(tmp);
+ *ret_srk_buf_size = srk_buf_size;
+ }
+
*ret_secret = TAKE_PTR(secret);
*ret_secret_size = hmac_sensitive.sensitive.data.size;
*ret_blob = TAKE_PTR(blob);
size_t blob_size,
const void *known_policy_hash,
size_t known_policy_hash_size,
+ const void *srk_buf,
+ size_t srk_buf_size,
void **ret_secret,
size_t *ret_secret_size) {
if (r < 0)
return r;
+ /* If their is a primary key we trust, like an SRK, use it */
_cleanup_tpm2_handle_ Tpm2Handle *primary = NULL;
- r = tpm2_make_primary(c, &primary, primary_alg, NULL);
- if (r < 0)
- return r;
+ if (srk_buf) {
+
+ r = tpm2_handle_new(c, &primary);
+ if (r < 0)
+ return r;
+
+ primary->keep = true;
+
+ log_debug("Found existing SRK key to use, deserializing ESYS_TR");
+ rc = sym_Esys_TR_Deserialize(
+ c->esys_context,
+ srk_buf,
+ srk_buf_size,
+ &primary->esys_handle);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc));
+ /* old callers without an SRK still need to create a key */
+ } else {
+ r = tpm2_make_primary(c, primary_alg, false, NULL, &primary);
+ if (r < 0)
+ return r;
+ }
log_debug("Loading HMAC key into TPM.");
/*
* Nothing sensitive on the bus, no need for encryption. Even if an attacker
- * gives you back a different key, the session initiation will fail if a pin
- * is provided. If an attacker gives back a bad key, we already lost since
- * primary key is not verified and they could attack there as well.
+ * gives you back a different key, the session initiation will fail. In the
+ * SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey
+ * provides protections.
*/
_cleanup_tpm2_handle_ Tpm2Handle *hmac_key = NULL;
r = tpm2_handle_new(c, &hmac_key);
size_t policy_hash_size,
const void *salt,
size_t salt_size,
+ const void *srk_buf,
+ size_t srk_buf_size,
TPM2Flags flags,
JsonVariant **ret) {
JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
- JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size))));
+ JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
+ JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size))));
if (r < 0)
return r;
size_t *ret_policy_hash_size,
void **ret_salt,
size_t *ret_salt_size,
+ void **ret_srk_buf,
+ size_t *ret_srk_buf_size,
TPM2Flags *ret_flags) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0;
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
+ size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
} else if (pubkey_pcr_mask != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing.");
+ w = json_variant_by_key(v, "tpm2_srk");
+ if (w) {
+ r = json_variant_unbase64(w, &srk_buf, &srk_buf_size);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
+ }
+
if (ret_keyslot)
*ret_keyslot = keyslot;
if (ret_hash_pcr_mask)
*ret_salt_size = salt_size;
if (ret_flags)
*ret_flags = flags;
+ if (ret_srk_buf)
+ *ret_srk_buf = TAKE_PTR(srk_buf);
+ if (ret_srk_buf_size)
+ *ret_srk_buf_size = srk_buf_size;
return 0;
}
} TPM2Flags;
+typedef enum Tpm2SRKTemplateFlags {
+ TPM2_SRK_TEMPLATE_ECC = 1 << 0,
+ TPM2_SRK_TEMPLATE_NEW_STYLE = 1 << 1,
+ _TPM2_SRK_TEMPLATE_MAX = TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC,
+} Tpm2SRKTemplateFlags;
+
/* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
* TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */
#define TPM2_PCRS_MAX 24U
int dlopen_tpm2(void);
-int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
-int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
+int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
typedef struct {
unsigned n_ref;
typedef struct {
Tpm2Context *tpm2_context;
ESYS_TR esys_handle;
+ bool keep;
} Tpm2Handle;
#define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), }
size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l);
#define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0)
+const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags);
+
#else /* HAVE_TPM2 */
typedef struct {} Tpm2Context;
typedef struct {} Tpm2Handle;
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret);
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)
# SPDX-License-Identifier: LGPL-2.1-or-later
systemd_sysext_sources = files('sysext.c')
+
+meson.add_install_script(meson_make_symlink,
+ rootbindir / 'systemd-sysext',
+ rootbindir / 'systemd-confext')
#include "dissect-image.h"
#include "env-util.h"
#include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "user-util.h"
#include "verbs.h"
-static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default */
+static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
static char *arg_root = NULL;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
static bool arg_force = false;
+/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
+static ImageClass arg_image_class = IMAGE_SYSEXT;
+
STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+ const char *dot_directory_name;
+ const char *directory_name;
+ const char *short_identifier;
+ const char *short_identifier_plural;
+ const char *level_env;
+ const char *scope_env;
+ const char *name_env;
+} image_class_info[_IMAGE_CLASS_MAX] = {
+ [IMAGE_SYSEXT] = {
+ .dot_directory_name = ".systemd-sysext",
+ .directory_name = "systemd-sysext",
+ .short_identifier = "sysext",
+ .short_identifier_plural = "extensions",
+ .level_env = "SYSEXT_LEVEL",
+ .scope_env = "SYSEXT_SCOPE",
+ .name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
+ },
+ [IMAGE_CONFEXT] = {
+ .dot_directory_name = ".systemd-confext",
+ .directory_name = "systemd-confext",
+ .short_identifier = "confext",
+ .short_identifier_plural = "confexts",
+ .level_env = "CONFEXT_LEVEL",
+ .scope_env = "CONFEXT_SCOPE",
+ .name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
+ }
+};
+
static int is_our_mount_point(const char *p) {
_cleanup_free_ char *buf = NULL, *f = NULL;
struct stat st;
/* So we know now that it's a mount point. Now let's check if it's one of ours, so that we don't
* accidentally unmount the user's own /usr/ but just the mounts we established ourselves. We do this
* check by looking into the metadata directory we place in merged mounts: if the file
- * .systemd-sysext/dev contains the major/minor device pair of the mount we have a good reason to
+ * ../dev contains the major/minor device pair of the mount we have a good reason to
* believe this is one of our mounts. This thorough check has the benefit that we aren't easily
* confused if people tar up one of our merged trees and untar them elsewhere where we might mistake
* them for a live sysext tree. */
- f = path_join(p, ".systemd-sysext/dev");
+ f = path_join(p, image_class_info[arg_image_class].dot_directory_name, "dev");
if (!f)
return log_oom();
r = read_one_line_file(f, &buf);
if (r == -ENOENT) {
- log_debug("Hierarchy '%s' does not carry a .systemd-sysext/dev file, not a sysext merged tree.", p);
+ log_debug("Hierarchy '%s' does not carry a %s/dev file, not a merged tree.", p, image_class_info[arg_image_class].dot_directory_name);
return false;
}
if (r < 0)
- return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '.systemd-sysext/dev': %m", p);
+ return log_error_errno(r, "Failed to determine whether hierarchy '%s' contains '%s/dev': %m", p, image_class_info[arg_image_class].dot_directory_name);
r = parse_devnum(buf, &dev);
if (r < 0)
- return log_error_errno(r, "Failed to parse device major/minor stored in '.systemd-sysext/dev' file on '%s': %m", p);
+ return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[arg_image_class].dot_directory_name, p);
if (lstat(p, &st) < 0)
return log_error_errno(r, "Failed to stat %s: %m", p);
continue;
}
- f = path_join(*p, ".systemd-sysext/extensions");
+ f = path_join(*p, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
if (!f)
return log_oom();
}
/* Now mount the actual overlayfs */
- r = mount_nofollow_verbose(LOG_ERR, "sysext", where, "overlay", MS_RDONLY, options);
+ r = mount_nofollow_verbose(LOG_ERR, image_class_info[arg_image_class].short_identifier, where, "overlay", MS_RDONLY, options);
if (r < 0)
return r;
/* Let's generate a metadata file that lists all extensions we took into account for this
* hierarchy. We include this in the final fs, to make things nicely discoverable and
* recognizable. */
- f = path_join(meta_path, ".systemd-sysext/extensions");
+ f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural);
if (!f)
return log_oom();
/* Now we have mounted the new file system. Let's now figure out its .st_dev field, and make that
* available in the metadata directory. This is useful to detect whether the metadata dir actually
* belongs to the fs it is found on: if .st_dev of the top-level mount matches it, it's pretty likely
- * we are looking at a live sysext tree, and not an unpacked tar or so of one. */
+ * we are looking at a live tree, and not an unpacked tar or so of one. */
if (stat(overlay_path, &st) < 0)
return log_error_errno(r, "Failed to stat mount '%s': %m", overlay_path);
free(f);
- f = path_join(meta_path, ".systemd-sysext/dev");
+ f = path_join(meta_path, image_class_info[arg_image_class].dot_directory_name, "dev");
if (!f)
return log_oom();
return strverscmp_improved(*a, *b);
}
-static int validate_version(
- const char *root,
- const Image *img,
- const char *host_os_release_id,
- const char *host_os_release_version_id,
- const char *host_os_release_sysext_level) {
-
- int r;
-
- assert(root);
- assert(img);
-
- if (arg_force) {
- log_debug("Force mode enabled, skipping version validation.");
- return 1;
- }
-
- /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
- * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
- * merged.) */
- r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
- if (r < 0) {
- if (r != -ENOENT)
- return log_error_errno(r, "Failed to determine whether /usr/lib/os-release exists in the extension image: %m");
- } else
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Extension image contains /usr/lib/os-release file, which is not allowed (it may carry /etc/os-release), refusing.");
-
- r = extension_release_validate(
- img->name,
- host_os_release_id,
- host_os_release_version_id,
- host_os_release_sysext_level,
- in_initrd() ? "initrd" : "system",
- img->extension_release);
- if (r < 0)
- return log_error_errno(r, "Failed to validate extension release information: %m");
-
- return r;
-}
-
static int merge_subprocess(Hashmap *images, const char *workspace) {
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL,
- *buf = NULL;
+ *host_os_release_confext_level = NULL, *buf = NULL;
_cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
size_t n_extensions = 0;
unsigned n_ignored = 0;
return r;
/* Acquire host OS release info, so that we can compare it with the extension's data */
+ char **host_os_release_level = (arg_image_class == IMAGE_CONFEXT) ? &host_os_release_confext_level : &host_os_release_sysext_level;
r = parse_os_release(
arg_root,
"ID", &host_os_release_id,
"VERSION_ID", &host_os_release_version_id,
- "SYSEXT_LEVEL", &host_os_release_sysext_level);
+ image_class_info[arg_image_class].level_env,
+ host_os_release_level);
if (r < 0)
return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root));
if (isempty(host_os_release_id))
HASHMAP_FOREACH(img, images) {
_cleanup_free_ char *p = NULL;
- p = path_join(workspace, "extensions", img->name);
+ p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
if (!p)
return log_oom();
switch (img->type) {
case IMAGE_DIRECTORY:
case IMAGE_SUBVOLUME:
+
+ if (!arg_force) {
+ r = extension_has_forbidden_content(p);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ n_ignored++;
+ continue;
+ }
+ }
+
r = mount_nofollow_verbose(LOG_ERR, img->path, p, NULL, MS_BIND, NULL);
if (r < 0)
return r;
if (verity_settings.data_path)
flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
+ if (!arg_force)
+ flags |= DISSECT_IMAGE_VALIDATE_OS_EXT;
+
r = loop_device_make_by_path(
img->path,
O_RDONLY,
UID_INVALID,
UID_INVALID,
flags);
- if (r < 0)
+ if (r < 0 && r != -ENOMEDIUM)
return r;
+ if (r == -ENOMEDIUM && !arg_force) {
+ n_ignored++;
+ continue;
+ }
r = dissected_image_relinquish(m);
if (r < 0)
assert_not_reached();
}
- r = validate_version(
- p,
- img,
- host_os_release_id,
- host_os_release_version_id,
- host_os_release_sysext_level);
- if (r < 0)
- return r;
- if (r == 0) {
- n_ignored++;
- continue;
+ if (arg_force)
+ log_debug("Force mode enabled, skipping version validation.");
+ else {
+ r = extension_release_validate(
+ img->name,
+ host_os_release_id,
+ host_os_release_version_id,
+ host_os_release_sysext_level,
+ in_initrd() ? "initrd" : "system",
+ img->extension_release,
+ arg_image_class);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ n_ignored++;
+ continue;
+ }
}
/* Nice! This one is an extension we want. */
/* Nothing left? Then shortcut things */
if (n_extensions == 0) {
if (n_ignored > 0)
- log_info("No suitable extensions found (%u ignored due to incompatible version).", n_ignored);
+ log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored);
else
log_info("No extensions found.");
return 0;
assert_se(img = hashmap_get(images, extensions[n_extensions - 1 - k]));
- p = path_join(workspace, "extensions", img->name);
+ p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
if (!p)
return log_oom();
pid_t pid;
int r;
- r = safe_fork("(sd-sysext)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
+ r = safe_fork("(sd-merge)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
if (r < 0)
return log_error_errno(r, "Failed to fork off child: %m");
if (r == 0) {
_exit(r > 0 ? EXIT_SUCCESS : 123); /* 123 means: didn't find any extensions */
}
- r = wait_for_terminate_and_check("(sd-sysext)", pid, WAIT_LOG_ABNORMAL);
+ r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
if (r < 0)
return r;
if (!images)
return log_oom();
- r = image_discover(IMAGE_EXTENSION, arg_root, images);
+ r = image_discover(arg_image_class, arg_root, images);
if (r < 0)
- return log_error_errno(r, "Failed to discover extension images: %m");
+ return log_error_errno(r, "Failed to discover images: %m");
HASHMAP_FOREACH(img, images) {
r = image_read_metadata(img);
if (!images)
return log_oom();
- r = image_discover(IMAGE_EXTENSION, arg_root, images);
+ r = image_discover(arg_image_class, arg_root, images);
if (r < 0)
- return log_error_errno(r, "Failed to discover extension images: %m");
+ return log_error_errno(r, "Failed to discover images: %m");
if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
log_info("No OS extensions found.");
return log_oom();
printf("%1$s [OPTIONS...] COMMAND\n"
- "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies.%6$s\n"
- "\n%3$sCommands:%4$s\n"
+ "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies for\n"
+ " sysext and into the /etc/ hierarchy for confext.%6$s\n"
" status Show current merge status (default)\n"
- " merge Merge extensions into /usr/ and /opt/\n"
- " unmerge Unmerge extensions from /usr/ and /opt/\n"
+ " merge Merge extensions into relevant hierarchies\n"
+ " unmerge Unmerge extensions from relevant hierarchies\n"
" refresh Unmerge/merge extensions again\n"
" list List installed extensions\n"
" -h --help Show this help\n"
static int run(int argc, char *argv[]) {
int r;
+ arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
log_setup();
/* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
* /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
* switch. */
- r = parse_env_extension_hierarchies(&arg_hierarchies);
+ r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env);
if (r < 0)
- return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES environment variable: %m");
+ return log_error_errno(r, "Failed to parse environment variable: %m");
return sysext_main(argc, argv);
}
/* Well-known errors. Note that this is only a sanitized subset of the
* errors that the reference implementation generates. */
-#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed"
-#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory"
-#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown"
-#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner"
-#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply"
-#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError"
-#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress"
-#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported"
-#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded"
-#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied"
-#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed"
-#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer"
-#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout"
-#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork"
-#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse"
-#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected"
-#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs"
-#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound"
-#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists"
-#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod"
-#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
-#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface"
-#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
-#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly"
-#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
-#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature"
-#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage"
-#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound"
-#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid"
-#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \
- "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"
+#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed"
+#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory"
+#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown"
+#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner"
+#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply"
+#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError"
+#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress"
+#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported"
+#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded"
+#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied"
+#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed"
+#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer"
+#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout"
+#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork"
+#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse"
+#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected"
+#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs"
+#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound"
+#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists"
+#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod"
+#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"
+#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface"
+#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty"
+#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly"
+#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
+#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature"
+#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage"
+#define SD_BUS_ERROR_TIMED_OUT "org.freedesktop.DBus.Error.TimedOut"
+#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound"
+#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid"
+#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"
+#define SD_BUS_ERROR_INVALID_FILE_CONTENT "org.freedesktop.DBus.Error.InvalidFileContent"
+#define SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"
+#define SD_BUS_ERROR_OBJECT_PATH_IN_USE "org.freedesktop.DBus.Error.ObjectPathInUse"
/* https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature */
#define SD_BUS_MAXIMUM_SIGNATURE_LENGTH 255
'test-cgroup-setup.c',
'test-cgroup-util.c',
'test-cgroup.c',
+ 'test-chase.c',
'test-clock.c',
'test-compare-operator.c',
'test-condition.c',
'base' : test_core_base,
},
{
- 'sources' : files('test-chase.c'),
+ 'sources' : files('test-chase-manual.c'),
'type' : 'manual',
},
{
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <getopt.h>
+
+#include "chase.h"
+#include "fd-util.h"
+#include "log.h"
+#include "main-func.h"
+
+static char *arg_root = NULL;
+static int arg_flags = 0;
+static bool arg_open = false;
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_ROOT = 0x1000,
+ ARG_OPEN,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "open", no_argument, NULL, ARG_OPEN },
+
+ { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT },
+ { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT },
+ { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS },
+ { "safe", no_argument, NULL, CHASE_SAFE },
+ { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH },
+ { "step", no_argument, NULL, CHASE_STEP },
+ { "nofollow", no_argument, NULL, CHASE_NOFOLLOW },
+ { "warn", no_argument, NULL, CHASE_WARN },
+ {}
+ };
+
+ int c;
+
+ assert_se(argc >= 0);
+ assert_se(argv);
+
+ while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ printf("Syntax:\n"
+ " %s [OPTION...] path...\n"
+ "Options:\n"
+ , argv[0]);
+ for (size_t i = 0; i < ELEMENTSOF(options) - 1; i++)
+ printf(" --%s\n", options[i].name);
+ return 0;
+
+ case ARG_ROOT:
+ arg_root = optarg;
+ break;
+
+ case ARG_OPEN:
+ arg_open = true;
+ break;
+
+ case CHASE_PREFIX_ROOT:
+ case CHASE_NONEXISTENT:
+ case CHASE_NO_AUTOFS:
+ case CHASE_SAFE:
+ case CHASE_TRAIL_SLASH:
+ case CHASE_STEP:
+ case CHASE_NOFOLLOW:
+ case CHASE_WARN:
+ arg_flags |= c;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (optind == argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required.");
+
+ return 1;
+}
+
+static int run(int argc, char **argv) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ for (int i = optind; i < argc; i++) {
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ printf("%s ", argv[i]);
+ fflush(stdout);
+
+ r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
+ if (r < 0)
+ log_error_errno(r, "failed: %m");
+ else {
+ log_info("→ %s", p);
+ if (arg_open)
+ assert_se(fd >= 0);
+ else
+ assert_se(fd == -EBADF);
+ }
+ }
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <getopt.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
#include "chase.h"
+#include "dirent-util.h"
#include "fd-util.h"
-#include "log.h"
-#include "main-func.h"
+#include "fs-util.h"
+#include "id128-util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "string-util.h"
+#include "tests.h"
+#include "tmpfile-util.h"
-static char *arg_root = NULL;
-static int arg_flags = 0;
-static bool arg_open = false;
+static const char *arg_test_dir = NULL;
-static int parse_argv(int argc, char *argv[]) {
- enum {
- ARG_ROOT = 0x1000,
- ARG_OPEN,
- };
+TEST(chase) {
+ _cleanup_free_ char *result = NULL, *pwd = NULL;
+ _cleanup_close_ int pfd = -EBADF;
+ char *temp;
+ const char *top, *p, *pslash, *q, *qslash;
+ struct stat st;
+ int r;
+
+ temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
+ assert_se(mkdtemp(temp));
+
+ top = strjoina(temp, "/top");
+ assert_se(mkdir(top, 0700) >= 0);
- static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "root", required_argument, NULL, ARG_ROOT },
- { "open", no_argument, NULL, ARG_OPEN },
-
- { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT },
- { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT },
- { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS },
- { "safe", no_argument, NULL, CHASE_SAFE },
- { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH },
- { "step", no_argument, NULL, CHASE_STEP },
- { "nofollow", no_argument, NULL, CHASE_NOFOLLOW },
- { "warn", no_argument, NULL, CHASE_WARN },
- {}
+ p = strjoina(top, "/dot");
+ if (symlink(".", p) < 0) {
+ assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
+ log_tests_skipped_errno(errno, "symlink() not possible");
+ goto cleanup;
};
- int c;
-
- assert_se(argc >= 0);
- assert_se(argv);
-
- while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
- switch (c) {
-
- case 'h':
- printf("Syntax:\n"
- " %s [OPTION...] path...\n"
- "Options:\n"
- , argv[0]);
- for (size_t i = 0; i < ELEMENTSOF(options) - 1; i++)
- printf(" --%s\n", options[i].name);
- return 0;
-
- case ARG_ROOT:
- arg_root = optarg;
- break;
-
- case ARG_OPEN:
- arg_open = true;
- break;
-
- case CHASE_PREFIX_ROOT:
- case CHASE_NONEXISTENT:
- case CHASE_NO_AUTOFS:
- case CHASE_SAFE:
- case CHASE_TRAIL_SLASH:
- case CHASE_STEP:
- case CHASE_NOFOLLOW:
- case CHASE_WARN:
- arg_flags |= c;
- break;
-
- case '?':
- return -EINVAL;
-
- default:
- assert_not_reached();
- }
-
- if (optind == argc)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required.");
-
- return 1;
-}
+ p = strjoina(top, "/dotdot");
+ assert_se(symlink("..", p) >= 0);
-static int run(int argc, char **argv) {
- int r;
+ p = strjoina(top, "/dotdota");
+ assert_se(symlink("../a", p) >= 0);
+
+ p = strjoina(temp, "/a");
+ assert_se(symlink("b", p) >= 0);
+
+ p = strjoina(temp, "/b");
+ assert_se(symlink("/usr", p) >= 0);
+
+ p = strjoina(temp, "/start");
+ assert_se(symlink("top/dot/dotdota", p) >= 0);
+
+ /* Paths that use symlinks underneath the "root" */
+
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, "/usr"));
+ result = mfree(result);
+
+ pslash = strjoina(p, "/");
+ r = chase(pslash, NULL, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, "/usr/"));
+ result = mfree(result);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ r = chase(pslash, temp, 0, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ q = strjoina(temp, "/usr");
+
+ r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+ assert_se(path_equal(result, q));
+ result = mfree(result);
+
+ qslash = strjoina(q, "/");
+
+ r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+ assert_se(path_equal(result, qslash));
+ result = mfree(result);
+
+ assert_se(mkdir(q, 0700) >= 0);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, q));
+ result = mfree(result);
+
+ r = chase(pslash, temp, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, qslash));
+ result = mfree(result);
+
+ p = strjoina(temp, "/slash");
+ assert_se(symlink("/", p) >= 0);
+
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, "/"));
+ result = mfree(result);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, temp));
+ result = mfree(result);
+
+ /* Paths that would "escape" outside of the "root" */
+
+ p = strjoina(temp, "/6dots");
+ assert_se(symlink("../../..", p) >= 0);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r > 0 && path_equal(result, temp));
+ result = mfree(result);
+
+ p = strjoina(temp, "/6dotsusr");
+ assert_se(symlink("../../../usr", p) >= 0);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r > 0 && path_equal(result, q));
+ result = mfree(result);
+
+ p = strjoina(temp, "/top/8dotsusr");
+ assert_se(symlink("../../../../usr", p) >= 0);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r > 0 && path_equal(result, q));
+ result = mfree(result);
+
+ /* Paths that contain repeated slashes */
+
+ p = strjoina(temp, "/slashslash");
+ assert_se(symlink("///usr///", p) >= 0);
+
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, "/usr"));
+ assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
+ result = mfree(result);
+
+ r = chase(p, temp, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, q));
+ result = mfree(result);
+
+ /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
+
+ if (geteuid() == 0) {
+ p = strjoina(temp, "/user");
+ assert_se(mkdir(p, 0755) >= 0);
+ assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
+
+ q = strjoina(temp, "/user/root");
+ assert_se(mkdir(q, 0755) >= 0);
+
+ p = strjoina(q, "/link");
+ assert_se(symlink("/", p) >= 0);
+
+ /* Fail when user-owned directories contain root-owned subdirectories. */
+ r = chase(p, temp, CHASE_SAFE, &result, NULL);
+ assert_se(r == -ENOLINK);
+ result = mfree(result);
+
+ /* Allow this when the user-owned directories are all in the "root". */
+ r = chase(p, q, CHASE_SAFE, &result, NULL);
+ assert_se(r > 0);
+ result = mfree(result);
+ }
+
+ /* Paths using . */
+
+ r = chase("/etc/./.././", NULL, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(result, "/"));
+ result = mfree(result);
+
+ r = chase("/etc/./.././", "/etc", 0, &result, NULL);
+ assert_se(r > 0 && path_equal(result, "/etc"));
+ result = mfree(result);
+
+ r = chase("/../.././//../../etc", NULL, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(streq(result, "/etc"));
+ result = mfree(result);
+
+ r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+ assert_se(streq(result, "/test-chase.fsldajfl"));
+ result = mfree(result);
+
+ r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
+ assert_se(r > 0);
+ assert_se(streq(result, "/etc"));
+ result = mfree(result);
+
+ r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+ assert_se(streq(result, "/test-chase.fsldajfl"));
+ result = mfree(result);
+
+ r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL);
+ assert_se(IN_SET(r, -ENOTDIR, -ENOENT));
+ result = mfree(result);
+
+ /* Path that loops back to self */
+
+ p = strjoina(temp, "/recursive-symlink");
+ assert_se(symlink("recursive-symlink", p) >= 0);
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r == -ELOOP);
+
+ /* Path which doesn't exist */
+
+ p = strjoina(temp, "/idontexist");
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ p = strjoina(temp, "/idontexist/meneither");
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ /* Relative paths */
+
+ assert_se(safe_getcwd(&pwd) >= 0);
+
+ assert_se(chdir(temp) >= 0);
+
+ p = "this/is/a/relative/path";
+ r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+
+ p = strjoina(temp, "/", p);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ p = "this/is/a/relative/path";
+ r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == 0);
+
+ p = strjoina(temp, "/", p);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ assert_se(chdir(pwd) >= 0);
+
+ /* Path which doesn't exist, but contains weird stuff */
+
+ p = strjoina(temp, "/idontexist/..");
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ p = strjoina(temp, "/target");
+ q = strjoina(temp, "/top");
+ assert_se(symlink(q, p) >= 0);
+ p = strjoina(temp, "/target/idontexist");
+ r = chase(p, NULL, 0, &result, NULL);
+ assert_se(r == -ENOENT);
+
+ if (geteuid() == 0) {
+ p = strjoina(temp, "/priv1");
+ assert_se(mkdir(p, 0755) >= 0);
- log_setup();
+ q = strjoina(p, "/priv2");
+ assert_se(mkdir(q, 0755) >= 0);
- r = parse_argv(argc, argv);
- if (r <= 0)
- return r;
+ assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
- for (int i = optind; i < argc; i++) {
- _cleanup_free_ char *p = NULL;
+ assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
+ assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+ assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
+ assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+ assert_se(chown(q, 0, 0) >= 0);
+ assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
+
+ assert_se(rmdir(q) >= 0);
+ assert_se(symlink("/etc/passwd", q) >= 0);
+ assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
+
+ assert_se(chown(p, 0, 0) >= 0);
+ assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+ }
+
+ p = strjoina(temp, "/machine-id-test");
+ assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
+
+ r = chase(p, NULL, 0, NULL, &pfd);
+ if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) {
_cleanup_close_ int fd = -EBADF;
+ sd_id128_t a, b;
+
+ assert_se(pfd >= 0);
+
+ fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
+ assert_se(fd >= 0);
+ safe_close(pfd);
- printf("%s ", argv[i]);
- fflush(stdout);
-
- r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
- if (r < 0)
- log_error_errno(r, "failed: %m");
- else {
- log_info("→ %s", p);
- if (arg_open)
- assert_se(fd >= 0);
- else
- assert_se(fd == -EBADF);
- }
+ assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0);
+ assert_se(sd_id128_get_machine(&b) >= 0);
+ assert_se(sd_id128_equal(a, b));
}
- return 0;
+ assert_se(lstat(p, &st) >= 0);
+ r = chase_and_unlink(p, NULL, 0, 0, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+ assert_se(lstat(p, &st) == -1 && errno == ENOENT);
+
+ /* Test CHASE_NOFOLLOW */
+
+ p = strjoina(temp, "/target");
+ q = strjoina(temp, "/symlink");
+ assert_se(symlink(p, q) >= 0);
+ r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
+ assert_se(r >= 0);
+ assert_se(pfd >= 0);
+ assert_se(path_equal(result, q));
+ assert_se(fstat(pfd, &st) >= 0);
+ assert_se(S_ISLNK(st.st_mode));
+ result = mfree(result);
+ pfd = safe_close(pfd);
+
+ /* s1 -> s2 -> nonexistent */
+ q = strjoina(temp, "/s1");
+ assert_se(symlink("s2", q) >= 0);
+ p = strjoina(temp, "/s2");
+ assert_se(symlink("nonexistent", p) >= 0);
+ r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
+ assert_se(r >= 0);
+ assert_se(pfd >= 0);
+ assert_se(path_equal(result, q));
+ assert_se(fstat(pfd, &st) >= 0);
+ assert_se(S_ISLNK(st.st_mode));
+ result = mfree(result);
+ pfd = safe_close(pfd);
+
+ /* Test CHASE_STEP */
+
+ p = strjoina(temp, "/start");
+ r = chase(p, NULL, CHASE_STEP, &result, NULL);
+ assert_se(r == 0);
+ p = strjoina(temp, "/top/dot/dotdota");
+ assert_se(streq(p, result));
+ result = mfree(result);
+
+ r = chase(p, NULL, CHASE_STEP, &result, NULL);
+ assert_se(r == 0);
+ p = strjoina(temp, "/top/dotdota");
+ assert_se(streq(p, result));
+ result = mfree(result);
+
+ r = chase(p, NULL, CHASE_STEP, &result, NULL);
+ assert_se(r == 0);
+ p = strjoina(temp, "/top/../a");
+ assert_se(streq(p, result));
+ result = mfree(result);
+
+ r = chase(p, NULL, CHASE_STEP, &result, NULL);
+ assert_se(r == 0);
+ p = strjoina(temp, "/a");
+ assert_se(streq(p, result));
+ result = mfree(result);
+
+ r = chase(p, NULL, CHASE_STEP, &result, NULL);
+ assert_se(r == 0);
+ p = strjoina(temp, "/b");
+ assert_se(streq(p, result));
+ result = mfree(result);
+
+ r = chase(p, NULL, CHASE_STEP, &result, NULL);
+ assert_se(r == 0);
+ assert_se(streq("/usr", result));
+ result = mfree(result);
+
+ r = chase("/usr", NULL, CHASE_STEP, &result, NULL);
+ assert_se(r > 0);
+ assert_se(streq("/usr", result));
+ result = mfree(result);
+
+ /* Make sure that symlinks in the "root" path are not resolved, but those below are */
+ p = strjoina("/etc/..", temp, "/self");
+ assert_se(symlink(".", p) >= 0);
+ q = strjoina(p, "/top/dot/dotdota");
+ r = chase(q, p, 0, &result, NULL);
+ assert_se(r > 0);
+ assert_se(path_equal(path_startswith(result, p), "usr"));
+ result = mfree(result);
+
+ /* Test CHASE_PROHIBIT_SYMLINKS */
+
+ assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+ assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+ assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+ assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+ assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+ assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+
+ cleanup:
+ assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+}
+
+TEST(chaseat) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+ _cleanup_free_ char *result = NULL;
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ struct stat st;
+ const char *p;
+
+ assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+ /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working
+ * directory. */
+
+ assert_se(symlinkat("/usr", tfd, "abc") >= 0);
+
+ p = strjoina(t, "/abc");
+ assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "/usr"));
+ result = mfree(result);
+
+ /* Test that absolute path or not are the same when resolving relative to a directory file
+ * descriptor and that we always get a relative path back. */
+
+ assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0);
+ fd = safe_close(fd);
+ assert_se(symlinkat("/def", tfd, "qed") >= 0);
+ assert_se(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "def"));
+ result = mfree(result);
+ assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "def"));
+ result = mfree(result);
+
+ /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
+ * host's root. */
+ assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
+
+ /* Test CHASE_PARENT */
+
+ assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
+ assert_se(symlinkat("/def", fd, "parent") >= 0);
+ fd = safe_close(fd);
+
+ /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
+ * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
+ * directory of the symlink itself. */
+
+ assert_se(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+ assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
+ assert_se(streq(result, "def"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
+ assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+ assert_se(streq(result, "chase/parent"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+ assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
+ assert_se(streq(result, "chase"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "."));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "."));
+ result = mfree(result);
+
+ /* Test CHASE_MKDIR_0755 */
+
+ assert_se(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+ assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
+ assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
+ assert_se(streq(result, "m/k/d/i/r"));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+ assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
+ assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
+ assert_se(streq(result, "q"));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT);
+
+ /* Test CHASE_FILENAME */
+
+ assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
+ assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+ assert_se(streq(result, "parent"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
+ assert_se(faccessat(fd, result, F_OK, 0) >= 0);
+ assert_se(streq(result, "chase"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
+ assert_se(streq(result, "."));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
+ assert_se(streq(result, "."));
+ result = mfree(result);
+
+ /* Test chase_and_openat() */
+
+ fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) >= 0);
+ fd = safe_close(fd);
+
+ fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_directory(fd) >= 0);
+ fd = safe_close(fd);
+
+ /* Test chase_and_openatdir() */
+
+ assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0);
+ FOREACH_DIRENT(de, dir, assert_not_reached())
+ assert_se(streq(de->d_name, "r"));
+ assert_se(streq(result, "o/p/e/n/d/i"));
+ result = mfree(result);
+
+ /* Test chase_and_statat() */
+
+ assert_se(chase_and_statat(tfd, "o/p", 0, &result, &st) >= 0);
+ assert_se(stat_verify_directory(&st) >= 0);
+ assert_se(streq(result, "o/p"));
+ result = mfree(result);
+
+ /* Test chase_and_accessat() */
+
+ assert_se(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result) >= 0);
+ assert_se(streq(result, "o/p/e"));
+ result = mfree(result);
+
+ /* Test chase_and_fopenat_unlocked() */
+
+ assert_se(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0);
+ assert_se(fread(&(char[1]) {}, 1, 1, f) == 0);
+ assert_se(feof(f));
+ f = safe_fclose(f);
+ assert_se(streq(result, "o/p/e/n/f/i/l/e"));
+ result = mfree(result);
+
+ /* Test chase_and_unlinkat() */
+
+ assert_se(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0);
+ assert_se(streq(result, "o/p/e/n/f/i/l/e"));
+ result = mfree(result);
+
+ /* Test chase_and_open_parent_at() */
+
+ assert_se((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)) >= 0);
+ assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+ assert_se(streq(result, "parent"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+ assert_se(faccessat(fd, result, F_OK, 0) >= 0);
+ assert_se(streq(result, "chase"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+ assert_se(streq(result, "."));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+ assert_se(streq(result, "."));
+ fd = safe_close(fd);
+ result = mfree(result);
+}
+
+static int intro(void) {
+ arg_test_dir = saved_argv[1];
+ return EXIT_SUCCESS;
}
-DEFINE_MAIN_FUNCTION(run);
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
const char *str_e = "!=!="; /* parse_compare_operator() moves the pointer */
assert_se(parse_compare_operator(&str_e, COMPARE_EQUAL_BY_STRING) == COMPARE_STRING_UNEQUAL);
assert_se(parse_compare_operator(&str_e, 0) == COMPARE_UNEQUAL);
+ assert_se(parse_compare_operator(&str_e, 0) == _COMPARE_OPERATOR_INVALID);
}
TEST(test_order) {
assert_se(r > 0);
}
+TEST(strv_env_name_is_valid) {
+ const char *valid_env_names[] = {"HOME", "USER", "SHELL", "PATH", NULL};
+ const char *invalid_env_names[] = {"", "PATH", "home", "user", "SHELL", NULL};
+ const char *repeated_env_names[] = {"HOME", "USER", "SHELL", "USER", NULL};
+ assert_se(strv_env_name_is_valid((char **) valid_env_names));
+ assert_se(!strv_env_name_is_valid((char **) invalid_env_names));
+ assert_se(!strv_env_name_is_valid((char **) repeated_env_names));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
#include "data-fd-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "fs-util.h"
#include "macro.h"
#include "memory-util.h"
#include "missing_syscall.h"
_cleanup_close_ int fd = -EBADF;
int r;
+ assert_se(dir_fd_is_root_or_cwd(AT_FDCWD) > 0);
+
assert_se((fd = open("/", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
assert_se(dir_fd_is_root(fd) > 0);
+ assert_se(dir_fd_is_root_or_cwd(fd) > 0);
fd = safe_close(fd);
assert_se((fd = open("/usr", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
assert_se(dir_fd_is_root(fd) == 0);
+ assert_se(dir_fd_is_root_or_cwd(fd) == 0);
r = detach_mount_namespace();
if (r < 0)
assert_se((fd = open(tmp, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
assert_se(dir_fd_is_root(fd) == 0);
+ assert_se(dir_fd_is_root_or_cwd(fd) == 0);
fd = safe_close(fd);
assert_se((fd = open(x, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
assert_se(dir_fd_is_root(fd) == 0);
+ assert_se(dir_fd_is_root_or_cwd(fd) == 0);
fd = safe_close(fd);
assert_se((fd = open(y, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
assert_se(dir_fd_is_root(fd) == 0);
+ assert_se(dir_fd_is_root_or_cwd(fd) == 0);
+}
+
+TEST(fd_get_path) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+ _cleanup_free_ char *p = NULL, *q = NULL, *saved_cwd = NULL;
+
+ tfd = mkdtemp_open(NULL, O_PATH, &t);
+ assert_se(tfd >= 0);
+ assert_se(fd_get_path(tfd, &p) >= 0);
+ assert_se(streq(p, t));
+
+ p = mfree(p);
+
+ assert_se(safe_getcwd(&saved_cwd) >= 0);
+ assert_se(chdir(t) >= 0);
+
+ assert_se(fd_get_path(AT_FDCWD, &p) >= 0);
+ assert_se(streq(p, t));
+
+ p = mfree(p);
+
+ assert_se(q = path_join(t, "regular"));
+ assert_se(touch(q) >= 0);
+ assert_se(mkdirat_parents(tfd, "subdir/symlink", 0755) >= 0);
+ assert_se(symlinkat("../regular", tfd, "subdir/symlink") >= 0);
+ assert_se(symlinkat("subdir", tfd, "symdir") >= 0);
+
+ fd = openat(tfd, "regular", O_CLOEXEC|O_PATH);
+ assert_se(fd >= 0);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(AT_FDCWD, "regular", O_CLOEXEC|O_PATH);
+ assert_se(fd >= 0);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(tfd, "subdir/symlink", O_CLOEXEC|O_PATH);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) >= 0);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(AT_FDCWD, "subdir/symlink", O_CLOEXEC|O_PATH);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) >= 0);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(tfd, "symdir//./symlink", O_CLOEXEC|O_PATH);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) >= 0);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(AT_FDCWD, "symdir//./symlink", O_CLOEXEC|O_PATH);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) >= 0);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ q = mfree(q);
+ fd = safe_close(fd);
+
+ assert_se(q = path_join(t, "subdir/symlink"));
+ fd = openat(tfd, "subdir/symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) == -ELOOP);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(AT_FDCWD, "subdir/symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) == -ELOOP);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(tfd, "symdir//./symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) == -ELOOP);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ p = mfree(p);
+ fd = safe_close(fd);
+
+ fd = openat(AT_FDCWD, "symdir//./symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) == -ELOOP);
+ assert_se(fd_get_path(fd, &p) >= 0);
+ assert_se(streq(p, q));
+
+ assert_se(chdir(saved_cwd) >= 0);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
#include <unistd.h>
#include "alloc-util.h"
-#include "chase.h"
#include "copy.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
-#include "id128-util.h"
#include "macro.h"
#include "mkdir.h"
#include "path-util.h"
#include "process-util.h"
#include "random-util.h"
#include "rm-rf.h"
+#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
static const char *arg_test_dir = NULL;
-TEST(chase) {
- _cleanup_free_ char *result = NULL, *pwd = NULL;
- _cleanup_close_ int pfd = -EBADF;
- char *temp;
- const char *top, *p, *pslash, *q, *qslash;
- struct stat st;
- int r;
-
- temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
- assert_se(mkdtemp(temp));
-
- top = strjoina(temp, "/top");
- assert_se(mkdir(top, 0700) >= 0);
-
- p = strjoina(top, "/dot");
- if (symlink(".", p) < 0) {
- assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
- log_tests_skipped_errno(errno, "symlink() not possible");
- goto cleanup;
- };
-
- p = strjoina(top, "/dotdot");
- assert_se(symlink("..", p) >= 0);
-
- p = strjoina(top, "/dotdota");
- assert_se(symlink("../a", p) >= 0);
-
- p = strjoina(temp, "/a");
- assert_se(symlink("b", p) >= 0);
-
- p = strjoina(temp, "/b");
- assert_se(symlink("/usr", p) >= 0);
-
- p = strjoina(temp, "/start");
- assert_se(symlink("top/dot/dotdota", p) >= 0);
-
- /* Paths that use symlinks underneath the "root" */
-
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/usr"));
- result = mfree(result);
-
- pslash = strjoina(p, "/");
- r = chase(pslash, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/usr/"));
- result = mfree(result);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase(pslash, temp, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- q = strjoina(temp, "/usr");
-
- r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(path_equal(result, q));
- result = mfree(result);
-
- qslash = strjoina(q, "/");
-
- r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(path_equal(result, qslash));
- result = mfree(result);
-
- assert_se(mkdir(q, 0700) >= 0);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, q));
- result = mfree(result);
-
- r = chase(pslash, temp, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, qslash));
- result = mfree(result);
-
- p = strjoina(temp, "/slash");
- assert_se(symlink("/", p) >= 0);
-
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/"));
- result = mfree(result);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, temp));
- result = mfree(result);
-
- /* Paths that would "escape" outside of the "root" */
-
- p = strjoina(temp, "/6dots");
- assert_se(symlink("../../..", p) >= 0);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r > 0 && path_equal(result, temp));
- result = mfree(result);
-
- p = strjoina(temp, "/6dotsusr");
- assert_se(symlink("../../../usr", p) >= 0);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r > 0 && path_equal(result, q));
- result = mfree(result);
-
- p = strjoina(temp, "/top/8dotsusr");
- assert_se(symlink("../../../../usr", p) >= 0);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r > 0 && path_equal(result, q));
- result = mfree(result);
-
- /* Paths that contain repeated slashes */
-
- p = strjoina(temp, "/slashslash");
- assert_se(symlink("///usr///", p) >= 0);
-
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/usr"));
- assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
- result = mfree(result);
-
- r = chase(p, temp, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, q));
- result = mfree(result);
-
- /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
-
- if (geteuid() == 0) {
- p = strjoina(temp, "/user");
- assert_se(mkdir(p, 0755) >= 0);
- assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
-
- q = strjoina(temp, "/user/root");
- assert_se(mkdir(q, 0755) >= 0);
-
- p = strjoina(q, "/link");
- assert_se(symlink("/", p) >= 0);
-
- /* Fail when user-owned directories contain root-owned subdirectories. */
- r = chase(p, temp, CHASE_SAFE, &result, NULL);
- assert_se(r == -ENOLINK);
- result = mfree(result);
-
- /* Allow this when the user-owned directories are all in the "root". */
- r = chase(p, q, CHASE_SAFE, &result, NULL);
- assert_se(r > 0);
- result = mfree(result);
- }
-
- /* Paths using . */
-
- r = chase("/etc/./.././", NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/"));
- result = mfree(result);
-
- r = chase("/etc/./.././", "/etc", 0, &result, NULL);
- assert_se(r > 0 && path_equal(result, "/etc"));
- result = mfree(result);
-
- r = chase("/../.././//../../etc", NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(streq(result, "/etc"));
- result = mfree(result);
-
- r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(streq(result, "/test-chase.fsldajfl"));
- result = mfree(result);
-
- r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
- assert_se(r > 0);
- assert_se(streq(result, "/etc"));
- result = mfree(result);
-
- r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(streq(result, "/test-chase.fsldajfl"));
- result = mfree(result);
-
- r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL);
- assert_se(IN_SET(r, -ENOTDIR, -ENOENT));
- result = mfree(result);
-
- /* Path that loops back to self */
-
- p = strjoina(temp, "/recursive-symlink");
- assert_se(symlink("recursive-symlink", p) >= 0);
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r == -ELOOP);
-
- /* Path which doesn't exist */
-
- p = strjoina(temp, "/idontexist");
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(path_equal(result, p));
- result = mfree(result);
-
- p = strjoina(temp, "/idontexist/meneither");
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(path_equal(result, p));
- result = mfree(result);
-
- /* Relative paths */
-
- assert_se(safe_getcwd(&pwd) >= 0);
-
- assert_se(chdir(temp) >= 0);
-
- p = "this/is/a/relative/path";
- r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
-
- p = strjoina(temp, "/", p);
- assert_se(path_equal(result, p));
- result = mfree(result);
-
- p = "this/is/a/relative/path";
- r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
-
- p = strjoina(temp, "/", p);
- assert_se(path_equal(result, p));
- result = mfree(result);
-
- assert_se(chdir(pwd) >= 0);
-
- /* Path which doesn't exist, but contains weird stuff */
-
- p = strjoina(temp, "/idontexist/..");
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == -ENOENT);
-
- p = strjoina(temp, "/target");
- q = strjoina(temp, "/top");
- assert_se(symlink(q, p) >= 0);
- p = strjoina(temp, "/target/idontexist");
- r = chase(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- if (geteuid() == 0) {
- p = strjoina(temp, "/priv1");
- assert_se(mkdir(p, 0755) >= 0);
-
- q = strjoina(p, "/priv2");
- assert_se(mkdir(q, 0755) >= 0);
-
- assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
- assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
- assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
- assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
- assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
- assert_se(chown(q, 0, 0) >= 0);
- assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
- assert_se(rmdir(q) >= 0);
- assert_se(symlink("/etc/passwd", q) >= 0);
- assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
- assert_se(chown(p, 0, 0) >= 0);
- assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
- }
-
- p = strjoina(temp, "/machine-id-test");
- assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
-
- r = chase(p, NULL, 0, NULL, &pfd);
- if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) {
- _cleanup_close_ int fd = -EBADF;
- sd_id128_t a, b;
-
- assert_se(pfd >= 0);
-
- fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
- assert_se(fd >= 0);
- safe_close(pfd);
-
- assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0);
- assert_se(sd_id128_get_machine(&b) >= 0);
- assert_se(sd_id128_equal(a, b));
- }
-
- assert_se(lstat(p, &st) >= 0);
- r = chase_and_unlink(p, NULL, 0, 0, &result);
- assert_se(r == 0);
- assert_se(path_equal(result, p));
- result = mfree(result);
- assert_se(lstat(p, &st) == -1 && errno == ENOENT);
-
- /* Test CHASE_NOFOLLOW */
-
- p = strjoina(temp, "/target");
- q = strjoina(temp, "/symlink");
- assert_se(symlink(p, q) >= 0);
- r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
- assert_se(r >= 0);
- assert_se(pfd >= 0);
- assert_se(path_equal(result, q));
- assert_se(fstat(pfd, &st) >= 0);
- assert_se(S_ISLNK(st.st_mode));
- result = mfree(result);
- pfd = safe_close(pfd);
-
- /* s1 -> s2 -> nonexistent */
- q = strjoina(temp, "/s1");
- assert_se(symlink("s2", q) >= 0);
- p = strjoina(temp, "/s2");
- assert_se(symlink("nonexistent", p) >= 0);
- r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
- assert_se(r >= 0);
- assert_se(pfd >= 0);
- assert_se(path_equal(result, q));
- assert_se(fstat(pfd, &st) >= 0);
- assert_se(S_ISLNK(st.st_mode));
- result = mfree(result);
- pfd = safe_close(pfd);
-
- /* Test CHASE_STEP */
-
- p = strjoina(temp, "/start");
- r = chase(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/top/dot/dotdota");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/top/dotdota");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/top/../a");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/a");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/b");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- assert_se(streq("/usr", result));
- result = mfree(result);
-
- r = chase("/usr", NULL, CHASE_STEP, &result, NULL);
- assert_se(r > 0);
- assert_se(streq("/usr", result));
- result = mfree(result);
-
- /* Make sure that symlinks in the "root" path are not resolved, but those below are */
- p = strjoina("/etc/..", temp, "/self");
- assert_se(symlink(".", p) >= 0);
- q = strjoina(p, "/top/dot/dotdota");
- r = chase(q, p, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(path_startswith(result, p), "usr"));
- result = mfree(result);
-
- /* Test CHASE_PROHIBIT_SYMLINKS */
-
- assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
- assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
- assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
- assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
- assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
- assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-
- cleanup:
- assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
-}
-
-TEST(chaseat) {
- _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
- _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
- _cleanup_free_ char *result = NULL;
- _cleanup_closedir_ DIR *dir = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- struct stat st;
- const char *p;
-
- assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
-
- /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working
- * directory. */
-
- assert_se(symlinkat("/usr", tfd, "abc") >= 0);
-
- p = strjoina(t, "/abc");
- assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "/usr"));
- result = mfree(result);
-
- /* Test that absolute path or not are the same when resolving relative to a directory file
- * descriptor and that we always get a relative path back. */
-
- assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0);
- fd = safe_close(fd);
- assert_se(symlinkat("/def", tfd, "qed") >= 0);
- assert_se(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "def"));
- result = mfree(result);
- assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "def"));
- result = mfree(result);
-
- /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
- * host's root. */
- assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
-
- /* Test CHASE_PARENT */
-
- assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
- assert_se(symlinkat("/def", fd, "parent") >= 0);
- fd = safe_close(fd);
-
- /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
- * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
- * directory of the symlink itself. */
-
- assert_se(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
- assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
- assert_se(streq(result, "def"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
- assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
- assert_se(streq(result, "chase/parent"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
- assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
- assert_se(streq(result, "chase"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "."));
- result = mfree(result);
-
- assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "."));
- result = mfree(result);
-
- /* Test CHASE_MKDIR_0755 */
-
- assert_se(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
- assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
- assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
- assert_se(streq(result, "m/k/d/i/r"));
- result = mfree(result);
-
- assert_se(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
- assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
- assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
- assert_se(streq(result, "q"));
- result = mfree(result);
-
- assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT);
-
- /* Test CHASE_FILENAME */
-
- assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
- assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
- assert_se(streq(result, "parent"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
- assert_se(faccessat(fd, result, F_OK, 0) >= 0);
- assert_se(streq(result, "chase"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
- assert_se(streq(result, "."));
- result = mfree(result);
-
- assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
- assert_se(streq(result, "."));
- result = mfree(result);
-
- /* Test chase_and_openat() */
-
- fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
- assert_se(fd >= 0);
- assert_se(fd_verify_regular(fd) >= 0);
- fd = safe_close(fd);
-
- fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
- assert_se(fd >= 0);
- assert_se(fd_verify_directory(fd) >= 0);
- fd = safe_close(fd);
-
- /* Test chase_and_openatdir() */
-
- assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0);
- FOREACH_DIRENT(de, dir, assert_not_reached())
- assert_se(streq(de->d_name, "r"));
- assert_se(streq(result, "o/p/e/n/d/i"));
- result = mfree(result);
-
- /* Test chase_and_statat() */
-
- assert_se(chase_and_statat(tfd, "o/p", 0, &result, &st) >= 0);
- assert_se(stat_verify_directory(&st) >= 0);
- assert_se(streq(result, "o/p"));
- result = mfree(result);
-
- /* Test chase_and_accessat() */
-
- assert_se(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result) >= 0);
- assert_se(streq(result, "o/p/e"));
- result = mfree(result);
-
- /* Test chase_and_fopenat_unlocked() */
-
- assert_se(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0);
- assert_se(fread(&(char[1]) {}, 1, 1, f) == 0);
- assert_se(feof(f));
- f = safe_fclose(f);
- assert_se(streq(result, "o/p/e/n/f/i/l/e"));
- result = mfree(result);
-
- /* Test chase_and_unlinkat() */
-
- assert_se(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0);
- assert_se(streq(result, "o/p/e/n/f/i/l/e"));
- result = mfree(result);
-
- /* Test chase_and_open_parent_at() */
-
- assert_se((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)) >= 0);
- assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
- assert_se(streq(result, "parent"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
- assert_se(faccessat(fd, result, F_OK, 0) >= 0);
- assert_se(streq(result, "chase"));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
- assert_se(streq(result, "."));
- fd = safe_close(fd);
- result = mfree(result);
-
- assert_se((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
- assert_se(streq(result, "."));
- fd = safe_close(fd);
- result = mfree(result);
-}
-
TEST(readlink_and_make_absolute) {
const char *tempdir, *name, *name2, *name_alias;
_cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL;
#include "fd-util.h"
#include "id128-util.h"
#include "macro.h"
+#include "path-util.h"
+#include "rm-rf.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
log_info("%lf µs each\n", (double) q / iterations);
}
+TEST(id128_at) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ sd_id128_t id, i;
+
+ tfd = mkdtemp_open(NULL, O_PATH, &t);
+ assert_se(tfd >= 0);
+ assert_se(mkdirat(tfd, "etc", 0755) >= 0);
+ assert_se(symlinkat("etc", tfd, "etc2") >= 0);
+ assert_se(symlinkat("machine-id", tfd, "etc/hoge-id") >= 0);
+
+ assert_se(sd_id128_randomize(&id) == 0);
+
+ assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) >= 0);
+ if (geteuid() == 0)
+ assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) >= 0);
+ else
+ assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) == -EACCES);
+ assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0);
+ assert_se(id128_write_at(tfd, "etc2/machine-id", ID128_FORMAT_PLAIN, id) >= 0);
+ assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0);
+ assert_se(id128_write_at(tfd, "etc/hoge-id", ID128_FORMAT_PLAIN, id) >= 0);
+ assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0);
+ assert_se(id128_write_at(tfd, "etc2/hoge-id", ID128_FORMAT_PLAIN, id) >= 0);
+
+ /* id128_read_at() */
+ i = SD_ID128_NULL; /* Not necessary in real code, but for testing that the id is really assigned. */
+ assert_se(id128_read_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read_at(tfd, "etc2/machine-id", ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read_at(tfd, "etc/hoge-id", ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read_at(tfd, "etc2/hoge-id", ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ /* id128_read() */
+ assert_se(p = path_join(t, "/etc/machine-id"));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ free(p);
+ assert_se(p = path_join(t, "/etc2/machine-id"));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ free(p);
+ assert_se(p = path_join(t, "/etc/hoge-id"));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ free(p);
+ assert_se(p = path_join(t, "/etc2/hoge-id"));
+
+ i = SD_ID128_NULL;
+ assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ /* id128_get_machine_at() */
+ i = SD_ID128_NULL;
+ assert_se(id128_get_machine_at(tfd, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+
+ /* id128_get_machine() */
+ i = SD_ID128_NULL;
+ assert_se(id128_get_machine(t, &i) >= 0);
+ assert_se(sd_id128_equal(id, i));
+}
+
+TEST(ID128_REFUSE_NULL) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF;
+ sd_id128_t id;
+
+ tfd = mkdtemp_open(NULL, O_PATH, &t);
+ assert_se(tfd >= 0);
+
+ assert_se(id128_write_at(tfd, "zero-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, (sd_id128_t) {}) == -ENOMEDIUM);
+ assert_se(unlinkat(tfd, "zero-id", 0) >= 0);
+ assert_se(id128_write_at(tfd, "zero-id", ID128_FORMAT_PLAIN, (sd_id128_t) {}) >= 0);
+
+ assert_se(sd_id128_randomize(&id) == 0);
+ assert_se(!sd_id128_equal(id, SD_ID128_NULL));
+ assert_se(id128_read_at(tfd, "zero-id", ID128_FORMAT_PLAIN, &id) >= 0);
+ assert_se(sd_id128_equal(id, SD_ID128_NULL));
+
+ assert_se(id128_read_at(tfd, "zero-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &id) == -ENOMEDIUM);
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
assert_se(log_context_num_fields() == 0);
}
+static void test_log_prefix(void) {
+ {
+ LOG_SET_PREFIX("ABC");
+
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+
+ {
+ LOG_SET_PREFIX("QED");
+
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+ }
+
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+ }
+
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+}
+
int main(int argc, char* argv[]) {
test_file();
test_long_lines();
test_log_syntax();
test_log_context();
+ test_log_prefix();
}
return 0;
.address.in = { htobe32(0x7F000002) } };
addrs[n++] = (struct local_address) { .family = AF_INET6,
.address.in6 = in6addr_loopback };
- return 0;
+
+ *addresses = TAKE_PTR(addrs);
+ return n;
}
static int test_one_module(const char *dir,
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
-
+#include "fileio.h"
#include "fs-util.h"
#include "log.h"
+#include "mkdir.h"
#include "os-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
+#include "tmpfile-util.h"
TEST(path_is_os_tree) {
assert_se(path_is_os_tree("/") > 0);
assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
}
+TEST(parse_extension_release) {
+ /* Let's assume that we have a valid extension image */
+ _cleanup_free_ char *id = NULL, *version_id = NULL, *foobar = NULL, *a = NULL, *b = NULL;
+ _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
+
+ int r = mkdtemp_malloc("/tmp/test-os-util.XXXXXX", &tempdir);
+ if (r < 0)
+ log_error_errno(r, "Failed to setup working directory: %m");
+
+ assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test"));
+ assert_se(mkdir_parents(a, 0777) >= 0);
+
+ r = write_string_file(a, "ID=the-id \n VERSION_ID=the-version-id", WRITE_STRING_FILE_CREATE);
+ if (r < 0)
+ log_error_errno(r, "Failed to write file: %m");
+
+ assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, false, "test", "ID", &id, "VERSION_ID", &version_id) == 0);
+ log_info("ID: %s VERSION_ID: %s", id, version_id);
+ assert_se(streq(id, "the-id"));
+ assert_se(streq(version_id, "the-version-id"));
+
+ assert_se(b = path_join(tempdir, "/etc/extension-release.d/extension-release.tester"));
+ assert_se(mkdir_parents(b, 0777) >= 0);
+
+ r = write_string_file(b, "ID=\"ignored\" \n ID=\"the-id\" \n VERSION_ID='the-version-id'", WRITE_STRING_FILE_CREATE);
+ if (r < 0)
+ log_error_errno(r, "Failed to write file: %m");
+
+ assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, false, "tester", "ID", &id, "VERSION_ID", &version_id) == 0);
+ log_info("ID: %s VERSION_ID: %s", id, version_id);
+ assert_se(streq(id, "the-id"));
+ assert_se(streq(version_id, "the-version-id"));
+
+ assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, false, "tester", "FOOBAR", &foobar) == 0);
+ log_info("FOOBAR: %s", strnull(foobar));
+ assert_se(foobar == NULL);
+
+ assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, false, "test", "FOOBAR", &foobar) == 0);
+ log_info("FOOBAR: %s", strnull(foobar));
+ assert_se(foobar == NULL);
+}
+
TEST(load_os_release_pairs) {
_cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
assert_se(write_tmpfile(tmpfile,
#include "tmpfile-util.h"
static void test_rm_rf_chmod_inner(void) {
- _cleanup_free_ char *d = NULL;
- const char *x, *y;
+ _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+ const char *a, *b, *x, *y;
+ struct stat st;
assert_se(getuid() != 0);
- assert_se(mkdtemp_malloc(NULL, &d) >= 0);
+ assert_se(mkdtemp_malloc("/tmp/test-rm-rf.XXXXXXX", &d) >= 0);
+ a = strjoina(d, "/a");
+ b = strjoina(a, "/b");
+ x = strjoina(d, "/x");
+ y = strjoina(x, "/y");
- x = strjoina(d, "/d");
assert_se(mkdir(x, 0700) >= 0);
- y = strjoina(x, "/f");
assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
assert_se(chmod(y, 0400) >= 0);
assert_se(chmod(x, 0500) >= 0);
assert_se(chmod(d, 0500) >= 0);
- assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT) == -EACCES);
+ assert_se(rm_rf(d, REMOVE_PHYSICAL) == -EACCES);
assert_se(access(d, F_OK) >= 0);
assert_se(access(x, F_OK) >= 0);
assert_se(access(y, F_OK) >= 0);
- assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT|REMOVE_CHMOD) >= 0);
+ assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD) >= 0);
+
+ assert_se(access(d, F_OK) >= 0);
+ assert_se(access(x, F_OK) < 0 && errno == ENOENT);
+ assert_se(access(y, F_OK) < 0 && errno == ENOENT);
+
+ assert_se(mkdir(a, 0700) >= 0);
+ assert_se(mkdir(b, 0700) >= 0);
+ assert_se(mkdir(x, 0700) >= 0);
+ assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
+
+ assert_se(chmod(b, 0000) >= 0);
+ assert_se(chmod(a, 0000) >= 0);
+ assert_se(chmod(y, 0000) >= 0);
+ assert_se(chmod(x, 0000) >= 0);
+ assert_se(chmod(d, 0500) >= 0);
+
+ assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_CHMOD_RESTORE|REMOVE_ONLY_DIRECTORIES) == -ENOTEMPTY);
+
+ assert_se(access(a, F_OK) < 0 && errno == ENOENT);
+ assert_se(access(d, F_OK) >= 0);
+ assert_se(stat(d, &st) >= 0 && (st.st_mode & 07777) == 0500);
+ assert_se(access(x, F_OK) >= 0);
+ assert_se(stat(x, &st) >= 0 && (st.st_mode & 07777) == 0000);
+ assert_se(chmod(x, 0700) >= 0);
+ assert_se(access(y, F_OK) >= 0);
+ assert_se(stat(y, &st) >= 0 && (st.st_mode & 07777) == 0000);
+
+ assert_se(chmod(y, 0000) >= 0);
+ assert_se(chmod(x, 0000) >= 0);
+ assert_se(chmod(d, 0000) >= 0);
+
+ assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_CHMOD_RESTORE) >= 0);
+
+ assert_se(stat(d, &st) >= 0 && (st.st_mode & 07777) == 0000);
+ assert_se(access(d, F_OK) >= 0);
+ assert_se(chmod(d, 0700) >= 0);
+ assert_se(access(x, F_OK) < 0 && errno == ENOENT);
+
+ assert_se(mkdir(x, 0700) >= 0);
+ assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
+
+ assert_se(chmod(y, 0000) >= 0);
+ assert_se(chmod(x, 0000) >= 0);
+ assert_se(chmod(d, 0000) >= 0);
+
+ assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_ROOT) >= 0);
- errno = 0;
assert_se(access(d, F_OK) < 0 && errno == ENOENT);
}
xsprintf(spec, "%%%c", s->specifier);
r = specifier_printf(spec, SIZE_MAX, specifier_table, NULL, NULL, &resolved);
- if (s->specifier == 'm' && IN_SET(r, -ENOENT, -ENOMEDIUM)) /* machine-id might be missing in build chroots */
+ if (s->specifier == 'm' && IN_SET(r, -EUNATCH, -ENOMEDIUM)) /* machine-id might be missing in build chroots */
continue;
assert_se(r >= 0);
expected2, expected2_count);
}
+/* this test includes TPM2 specific data structures */
+TEST(tpm2_get_primary_template) {
+
+ /*
+ * Verify that if someone changes the template code, they know they're breaking things.
+ * Templates MUST be changed in a backwards compatible way.
+ *
+ */
+ static const TPM2B_PUBLIC templ[] = {
+ /* index 0 RSA old */
+ [0] = {
+ .publicArea = {
+ .type = TPM2_ALG_RSA,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+ .parameters.rsaDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .keyBits = 2048,
+ },
+ },
+ },
+ /* Index 1 ECC old */
+ [TPM2_SRK_TEMPLATE_ECC] = {
+ .publicArea = {
+ .type = TPM2_ALG_ECC,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+ .parameters.eccDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .curveID = TPM2_ECC_NIST_P256,
+ .kdf.scheme = TPM2_ALG_NULL,
+ },
+ },
+ },
+ /* index 2 RSA SRK */
+ [TPM2_SRK_TEMPLATE_NEW_STYLE] = {
+ .publicArea = {
+ .type = TPM2_ALG_RSA,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+ .parameters.rsaDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .keyBits = 2048,
+ },
+ },
+ },
+ /* Index 3 ECC SRK */
+ [TPM2_SRK_TEMPLATE_NEW_STYLE | TPM2_SRK_TEMPLATE_ECC] = {
+ .publicArea = {
+ .type = TPM2_ALG_ECC,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+ .parameters.eccDetail = {
+ .symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ },
+ .scheme.scheme = TPM2_ALG_NULL,
+ .curveID = TPM2_ECC_NIST_P256,
+ .kdf.scheme = TPM2_ALG_NULL,
+ },
+ },
+ },
+ };
+
+ assert_cc(ELEMENTSOF(templ) == _TPM2_SRK_TEMPLATE_MAX + 1);
+
+ for (size_t i = 0; i < ELEMENTSOF(templ); i++) {
+ /* the index counter lines up with the flags and the expected template received */
+ const TPM2B_PUBLIC *got = tpm2_get_primary_template((Tpm2SRKTemplateFlags)i);
+ assert_se(memcmp(&templ[i], got, sizeof(*got)) == 0);
+ }
+}
+
#endif /* HAVE_TPM2 */
DEFINE_TEST_MAIN(LOG_DEBUG);
r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path);
} else {
+ _cleanup_close_ int fd = -EBADF;
+
/* Skip files for which the sticky bit is set. These are semantics we define, and are
* unknown elsewhere. See XDG_RUNTIME_DIR specification for details. */
if (sx.stx_mode & S_ISVTX) {
cutoff_nsec, sub_path, age_by_file, false))
continue;
+ fd = xopenat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, 0);
+ if (fd < 0 && fd != -ENOENT)
+ log_warning_errno(fd, "Opening file \"%s\" failed, ignoring: %m", sub_path);
+ if (fd >= 0 && flock(fd, LOCK_EX|LOCK_NB) < 0 && errno == EAGAIN) {
+ log_debug_errno(errno, "Couldn't acquire shared BSD lock on file \"%s\", skipping: %m", p);
+ continue;
+ }
+
log_debug("Removing \"%s\".", sub_path);
if (unlinkat(dirfd(d), de->d_name, 0) < 0)
if (errno != ENOENT)
return 1;
}
+static void check_tokens_order(UdevRuleLine *rule_line) {
+ bool has_result = false;
+
+ assert(rule_line);
+
+ LIST_FOREACH(tokens, t, rule_line->tokens)
+ if (t->type == TK_M_RESULT)
+ has_result = true;
+ else if (has_result && t->type == TK_M_PROGRAM) {
+ log_line_warning(rule_line, "Reordering RESULT check after PROGRAM assignment.");
+ break;
+ }
+}
+
static void sort_tokens(UdevRuleLine *rule_line) {
assert(rule_line);
return 0;
}
+ if (extra_checks)
+ check_tokens_order(rule_line);
+
sort_tokens(rule_line);
TAKE_PTR(rule_line);
return 0;
assert(filename);
f = fopen(filename, "re");
- if (!f)
+ if (!f) {
+ if (!extra_checks && errno == ENOENT)
+ return 0;
+
return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
+ }
if (fstat(fileno(f), &st) < 0)
return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
if (fd < 0)
return fd;
- r = fdset_put(fds, fd);
+ r = fdset_consume(fds, TAKE_FD(fd));
if (r < 0)
return log_oom();
-
- TAKE_FD(fd);
}
}
def pe_add_sections(uki: UKI, output: str):
pe = pefile.PE(uki.executable, fast_load=True)
+ # Old stubs do not have the symbol/string table stripped, even though image files should not have one.
+ if symbol_table := pe.FILE_HEADER.PointerToSymbolTable:
+ symbol_table_size = 18 * pe.FILE_HEADER.NumberOfSymbols
+ if string_table_size := pe.get_dword_from_offset(symbol_table + symbol_table_size):
+ symbol_table_size += string_table_size
+
+ # Let's be safe and only strip it if it's at the end of the file.
+ if symbol_table + symbol_table_size == len(pe.__data__):
+ pe.__data__ = pe.__data__[:symbol_table]
+ pe.FILE_HEADER.PointerToSymbolTable = 0
+ pe.FILE_HEADER.NumberOfSymbols = 0
+ pe.FILE_HEADER.IMAGE_FILE_LOCAL_SYMS_STRIPPED = True
+
# Old stubs might have been stripped, leading to unaligned raw data values, so let's fix them up here.
for i, section in enumerate(pe.sections):
oldp = section.PointerToRawData
#include "string-util.h"
static int run(int argc, char *argv[]) {
- int r, k;
+ int r;
if (argc != 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
if (r < 0)
return r;
- if (streq(argv[1], "start")) {
- r = unlink_or_warn("/run/nologin");
- k = unlink_or_warn("/etc/nologin");
- if (r < 0)
- return r;
- return k;
+ /* We only touch /run/nologin. See create_shutdown_run_nologin_or_warn() for details. */
- } else if (streq(argv[1], "stop"))
+ if (streq(argv[1], "start"))
+ return unlink_or_warn("/run/nologin");
+ if (streq(argv[1], "stop"))
return create_shutdown_run_nologin_or_warn();
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb '%s'.", argv[1]);
The results are then located in the `results.csv` file as a comma separated
values list (obviously), which is the most human-friendly output format the
CodeQL utility provides (so far).
+
+Code coverage
+=============
+
+We have a daily cron job in CentOS CI which runs all unit and integration tests,
+collects coverage using gcov/lcov, and uploads the report to Coveralls[0]. In
+order to collect the most accurate coverage information, some measures have
+to be taken regarding sandboxing, namely:
+
+ - ProtectSystem= and ProtectHome= need to be turned off
+ - the $BUILD_DIR with necessary .gcno files needs to be present in the image
+ and needs to be writable by all processes
+
+The first point is relatively easy to handle and is handled automagically by
+our test "framework" by creating necessary dropins.
+
+Making the $BUILD_DIR accessible to _everything_ is slightly more complicated.
+First, and foremost, the $BUILD_DIR has a POSIX ACL that makes it writable
+to everyone. However, this is not enough in some cases, like for services
+that use DynamicUser=yes, since that implies ProtectSystem=strict that can't
+be turned off. A solution to this is to use ReadWritePaths=$BUILD_DIR, which
+works for the majority of cases, but can't be turned on globally, since
+ReadWritePaths= creates its own mount namespace which might break some
+services. Hence, the ReadWritePaths=$BUILD_DIR is enabled for all services
+with the `test-` prefix (i.e. test-foo.service or test-foo-bar.service), both
+in the system and the user managers.
+
+So, if you're considering writing an integration test that makes use
+of DynamicUser=yes, or other sandboxing stuff that implies it, please prefix
+the test unit (be it a static one or a transient one created via systemd-run),
+with `test-`, unless the test unit needs to be able to install mount points
+in the main mount namespace - in that case use IGNORE_MISSING_COVERAGE=yes
+in the test definition (i.e. TEST-*-NAME/test.sh), which will skip the post-test
+check for missing coverage for the respective test.
+
+[0] https://coveralls.io/github/systemd/systemd
set -e
TEST_DESCRIPTION="Test queue signal logic"
-# Ignore gcov complaints caused by DynamicUser=true
-IGNORE_MISSING_COVERAGE=yes
# shellcheck source=test/test-functions
. "$TEST_BASE_DIR/test-functions"
set -e
TEST_DESCRIPTION="Test Memory Pressure handling"
-# Ignore gcov complaints caused by DynamicUser=true
-IGNORE_MISSING_COVERAGE=yes
# shellcheck source=test/test-functions
. "$TEST_BASE_DIR/test-functions"
set -e
TEST_DESCRIPTION="test NotifyAccess through sd-notify"
-TEST_NO_QEMU=1
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
--- /dev/null
+../TEST-01-BASIC/Makefile
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+
+TEST_DESCRIPTION="Test systemd generators"
+
+# shellcheck source=test/test-functions
+. "${TEST_BASE_DIR:?}/test-functions"
+
+do_test "$@"
'test-path',
'test-path-util',
'test-umount',
+ 'test-network',
'test-network-generator-conversion',
'testsuite-03.units',
'testsuite-04.units',
+++ /dev/null
-#!/bin/sh
-# SPDX-License-Identifier: LGPL-2.1-or-later
-set -ex
-
-# First, source in the main build script
-. "$SRCDIR"/mkosi.build
-
-mkdir -p "$DESTDIR"/usr/local/bin
-cp "$SRCDIR"/test/networkd-test.py "$DESTDIR"/usr/local/bin/networkd-test.py
-
-mkdir -p "$DESTDIR"/etc/systemd/system
-cat >"$DESTDIR"/etc/systemd/system/networkd-test.service <<EOF
-[Unit]
-Description=networkd test service
-SuccessAction=exit
-FailureAction=exit
-
-[Service]
-ExecStart=/usr/local/bin/networkd-test.py
-EOF
-
-mkdir -p "$DESTDIR"/etc/systemd/system/multi-user.target.wants/
-ln -s ../networkd-test.service "$DESTDIR"/etc/systemd/system/multi-user.target.wants/
-
-systemctl --root="$DESTDIR" disable systemd-networkd.service
+++ /dev/null
-# SPDX-License-Identifier: LGPL-2.1-or-later
-#
-# Puts together an nspawn container and runs networkd-test.py in it, inside a
-# network namespace and everything. Run this with
-#
-# mkosi -C test --default=mkosi.default.networkd-test boot
-#
-# This will start the test and eventually exit with success in case the test
-# succeeded.
-
-[Distribution]
-Distribution=fedora
-Release=33
-
-[Output]
-Format=raw_btrfs
-Bootable=yes
-OutputDirectory=../mkosi.output
-Output=networkd-test.raw
-
-[Partitions]
-RootSize=3G
-
-[Content]
-BuildPackages=
- audit-libs-devel
- bzip2-devel
- cryptsetup-devel
- dbus-devel
- diffutils
- docbook-style-xsl
- elfutils-devel
- gcc
- gettext
- git
- gnutls-devel
- gperf
- hostname
- iptables-devel
- kmod-devel
- libacl-devel
- libblkid-devel
- libcap-devel
- libcurl-devel
- libgcrypt-devel
- libidn2-devel
- libmicrohttpd-devel
- libmount-devel
- libseccomp-devel
- libselinux-devel
- libxkbcommon-devel
- libxslt
- lz4
- lz4-devel
- meson
- ninja-build
- pam-devel
- pcre2-devel
- perl(IPC::SysV)
- perl(Time::HiRes)
- pkgconfig
- python3-devel
- python3-lxml
- qrencode-devel
- tree
-
-Packages=
- dnsmasq
- iproute
- libidn2
- polkit
- python3
-
-# Share caches with the top-level mkosi
-BuildDirectory=../mkosi/mkosi.builddir
-Cache=../mkosi/mkosi.cache
-
-# Run our own script
-BuildScript=mkosi.build.networkd-test
-
-BuildSources=..
-NSpawnSettings=mkosi.nspawn.networkd-test
+++ /dev/null
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-[Network]
-Private=yes
seq
setfattr
setfont
+ setpriv
setsid
sfdisk
sh
sleep
stat
+ stty
su
sulogin
sysctl
route
sort
strace
- stty
tty
vi
/usr/libexec/vi
ddebug "Install files from package $p"
while read -r f; do
[ -e "$f" ] || continue
- [ ! -L "$file" ] && [ -d "$file" ] && continue
+ [ ! -L "$f" ] && [ -d "$f" ] && continue
inst "$f"
done < <(rpm -ql "$p")
done
mkdir -p "$initdir/etc/systemd/system/service.d/"
echo -e "[Service]\nProtectSystem=no\nProtectHome=no\n" >"$initdir/etc/systemd/system/service.d/99-gcov-override.conf"
# Similarly, set ReadWritePaths= to the $BUILD_DIR in the test image
- # to make the coverage work with units utilizing DynamicUser=yes. Do
- # this only for services from TEST-20, as setting this system-wide
- # has many undesirable side-effects
- mkdir -p "$initdir/etc/systemd/system/test20-.service.d/"
- echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/system/test20-.service.d/99-gcov-rwpaths-override.conf"
+ # to make the coverage work with units using DynamicUser=yes. Do this
+ # only for services with test- prefix, as setting this system-wide
+ # has many undesirable side-effects, as it creates its own namespace.
+ mkdir -p "$initdir/etc/systemd/system/test-.service.d/"
+ echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/system/test-.service.d/99-gcov-rwpaths-override.conf"
+ # Ditto, but for the user daemon
+ mkdir -p "$initdir/etc/systemd/user/test-.service.d/"
+ echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/user/test-.service.d/99-gcov-rwpaths-override.conf"
fi
# If we're built with -Dportabled=false, tests with systemd-analyze
# Same as above, but we need to wrap certain libraries unconditionally
#
# chown, getent, login, su, useradd, userdel - dlopen() (not only) systemd's PAM modules
- # ls, mkfs.*, mksquashfs, mkswap, stat
+ # ls, mkfs.*, mksquashfs, mkswap, setpriv, stat
# - pull in nss_systemd with certain options (like ls -l) when
# nsswitch.conf uses [SUCCESS=merge] (like on Arch Linux)
# delv, dig - pull in nss_resolve if `resolve` is in nsswitch.conf
# tar - called by machinectl in TEST-25
- bin_rx='/(chown|delv|dig|getent|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|stat|su|tar|useradd|userdel)$'
+ bin_rx='/(chown|delv|dig|getent|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|setpriv|stat|su|tar|useradd|userdel)$'
if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$bin" =~ $bin_rx ]]; then
wrap_binary=1
fi
fi
[[ -n "$TESTDIR" ]] && rm -vfr "$TESTDIR"
[[ -n "$STATEFILE" ]] && rm -vf "$STATEFILE"
- [[ -n "$STATEDIR" ]] && rm -vfr "$STATEDIR"
) || :
}
fi
test_cleanup
if [ $ret -eq 0 ]; then
- rm "$TESTLOG"
+ # $TESTLOG is in $STATEDIR, so clean it up only on success
+ [[ -n "$STATEDIR" ]] && rm -vfr "$STATEDIR"
echo "[OK]"
else
echo "[FAILED]"
# systemd-networkd tests
# These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM,
-# simply run this file which can be found in the VM at /root/src/test/test-network/systemd-networkd-tests.py.
+# simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py.
import argparse
import errno
SYSUSERS="${1:-systemd-sysusers}"
-[ -e "$(dirname $0)/../systemd-runtest.env" ] && . "$(dirname $0)/../systemd-runtest.env"
+# shellcheck disable=SC1090
+[ -e "$(dirname "$0")/../systemd-runtest.env" ] && . "$(dirname "$0")/../systemd-runtest.env"
SYSTEMD_TEST_DATA=${SYSTEMD_TEST_DATA:-@SYSTEMD_TEST_DATA@}
SOURCE=$SYSTEMD_TEST_DATA/test-sysusers
TESTDIR=$(mktemp --tmpdir --directory "test-sysusers.XXXXXXXXXX")
+# shellcheck disable=SC2064
trap "rm -rf '$TESTDIR'" EXIT INT QUIT PIPE
prepare_testdir() {
- mkdir -p $TESTDIR/etc/sysusers.d/
- mkdir -p $TESTDIR/usr/lib/sysusers.d/
- rm -f $TESTDIR/etc/*{passwd,group,shadow}
+ mkdir -p "$TESTDIR/etc/sysusers.d/"
+ mkdir -p "$TESTDIR/usr/lib/sysusers.d/"
+ rm -f "$TESTDIR"/etc/*{passwd,group,shadow}
for i in $1.initial-{passwd,group,shadow}; do
- test -f $i && cp $i $TESTDIR/etc/${i#*.initial-}
+ test -f "$i" && cp "$i" "$TESTDIR/etc/${i#*.initial-}"
done
return 0
}
+# shellcheck disable=SC2050
[ @SYSTEM_UID_MAX@ -lt @SYSTEM_GID_MAX@ ] && system_guid_max=@SYSTEM_UID_MAX@ || system_guid_max=@SYSTEM_GID_MAX@
preprocess() {
m=${2:-$system_guid_max}
+ # shellcheck disable=SC2140
sed -e "s/SYSTEM_UGID_MAX/$m/g;
s#NOLOGIN#@NOLOGIN@#g" "$1"
}
compare() {
- if ! diff -u $TESTDIR/etc/passwd <(preprocess $1.expected-passwd $3); then
+ if ! diff -u "$TESTDIR/etc/passwd" <(preprocess "$1.expected-passwd" "$3"); then
echo "**** Unexpected output for $f $2"
exit 1
fi
- if ! diff -u $TESTDIR/etc/group <(preprocess $1.expected-group $3); then
+ if ! diff -u "$TESTDIR/etc/group" <(preprocess "$1.expected-group" "$3"); then
echo "**** Unexpected output for $f $2"
exit 1
fi
}
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
# happy tests
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f"
- prepare_testdir ${f%.input}
- cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
- $SYSUSERS --root=$TESTDIR
+ prepare_testdir "${f%.input}"
+ cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+ $SYSUSERS --root="$TESTDIR"
- compare ${f%.*} ""
+ compare "${f%.*}" ""
done
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f on stdin"
- prepare_testdir ${f%.input}
- touch $TESTDIR/etc/sysusers.d/test.conf
- cat $f | $SYSUSERS --root=$TESTDIR -
+ prepare_testdir "${f%.input}"
+ touch "$TESTDIR/etc/sysusers.d/test.conf"
+ $SYSUSERS --root="$TESTDIR" - <"$f"
- compare ${f%.*} "on stdin"
+ compare "${f%.*}" "on stdin"
done
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f on stdin with --replace"
- prepare_testdir ${f%.input}
- touch $TESTDIR/etc/sysusers.d/test.conf
+ prepare_testdir "${f%.input}"
+ touch "$TESTDIR/etc/sysusers.d/test.conf"
# this overrides test.conf which is masked on disk
- cat $f | $SYSUSERS --root=$TESTDIR --replace=/etc/sysusers.d/test.conf -
+ $SYSUSERS --root="$TESTDIR" --replace=/etc/sysusers.d/test.conf - <"$f"
# this should be ignored
- cat $SOURCE/test-1.input | $SYSUSERS --root=$TESTDIR --replace=/usr/lib/sysusers.d/test.conf -
+ $SYSUSERS --root="$TESTDIR" --replace=/usr/lib/sysusers.d/test.conf - <"$SOURCE/test-1.input"
- compare ${f%.*} "on stdin with --replace"
+ compare "${f%.*}" "on stdin with --replace"
done
# test --inline
echo "*** Testing --inline"
-prepare_testdir $SOURCE/inline
+prepare_testdir "$SOURCE/inline"
# copy a random file to make sure it is ignored
-cp $f $TESTDIR/etc/sysusers.d/confuse.conf
-$SYSUSERS --root=$TESTDIR --inline \
+cp "$f" "$TESTDIR/etc/sysusers.d/confuse.conf"
+$SYSUSERS --root="$TESTDIR" --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
-compare $SOURCE/inline "(--inline)"
+compare "$SOURCE/inline" "(--inline)"
# test --replace
echo "*** Testing --inline with --replace"
-prepare_testdir $SOURCE/inline
+prepare_testdir "$SOURCE/inline"
# copy a random file to make sure it is ignored
-cp $f $TESTDIR/etc/sysusers.d/confuse.conf
-$SYSUSERS --root=$TESTDIR \
+cp "$f" "$TESTDIR/etc/sysusers.d/confuse.conf"
+$SYSUSERS --root="$TESTDIR" \
--inline \
--replace=/etc/sysusers.d/confuse.conf \
"u u1 222 - - /bin/zsh" \
"g g1 111"
-compare $SOURCE/inline "(--inline --replace=…)"
+compare "$SOURCE/inline" "(--inline --replace=…)"
echo "*** Testing --inline with no /etc"
-rm -rf $TESTDIR/etc
-$SYSUSERS --root=$TESTDIR --inline \
+rm -rf "${TESTDIR:?}/etc"
+$SYSUSERS --root="$TESTDIR" --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
-compare $SOURCE/inline "(--inline)"
+compare "$SOURCE/inline" "(--inline)"
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
-cat >$TESTDIR/etc/login.defs <<EOF
+cat >"$TESTDIR/etc/login.defs" <<EOF
SYS_UID_MIN abcd
SYS_UID_MAX abcd
SYS_GID_MIN abcd
SYS_GID_MAX999
EOF
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f (with login.defs)"
- prepare_testdir ${f%.input}
- cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
- $SYSUSERS --root=$TESTDIR
+ prepare_testdir "${f%.input}"
+ cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+ $SYSUSERS --root="$TESTDIR"
+ # shellcheck disable=SC2050
[ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
- compare ${f%.*} "(with login.defs)" $bound
+ compare "${f%.*}" "(with login.defs)" $bound
done
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
-mv $TESTDIR/etc/login.defs $TESTDIR/etc/login.defs.moved
-ln -s ../../../../../etc/login.defs.moved $TESTDIR/etc/login.defs
+mv "$TESTDIR/etc/login.defs" "$TESTDIR/etc/login.defs.moved"
+ln -s ../../../../../etc/login.defs.moved "$TESTDIR/etc/login.defs"
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f (with login.defs symlinked)"
- prepare_testdir ${f%.input}
- cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
- $SYSUSERS --root=$TESTDIR
+ prepare_testdir "${f%.input}"
+ cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+ $SYSUSERS --root="$TESTDIR"
+ # shellcheck disable=SC2050
[ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
- compare ${f%.*} "(with login.defs symlinked)" $bound
+ compare "${f%.*}" "(with login.defs symlinked)" $bound
done
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
# tests for error conditions
-for f in $(ls -1 $SOURCE/unhappy-*.input | sort -V); do
+for f in $(find "$SOURCE"/unhappy-*.input | sort -V); do
echo "*** Running test $f"
- prepare_testdir ${f%.input}
- cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
- $SYSUSERS --root=$TESTDIR 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >$TESTDIR/err
- if ! diff -u $TESTDIR/err ${f%.*}.expected-err; then
+ prepare_testdir "${f%.input}"
+ cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+ $SYSUSERS --root="$TESTDIR" 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >"$TESTDIR/err"
+ if ! diff -u "$TESTDIR/err" "${f%.*}.expected-err"; then
echo "**** Unexpected error output for $f"
- cat $TESTDIR/err
+ cat "$TESTDIR/err"
exit 1
fi
done
set -eux
set -o pipefail
-systemd-notify --status="Test starts, waiting for 5 seconds"
-sleep 5
+sync_in() {
+ read -r x < /tmp/syncfifo2
+ test "$x" = "$1"
+}
+sync_out() {
+ echo "$1" > /tmp/syncfifo1
+}
+
+export SYSTEMD_LOG_LEVEL=debug
+
+echo "toplevel PID: $BASHPID"
+
+systemd-notify --status="Test starts"
+sync_out a
+sync_in b
(
- systemd-notify --pid=auto
+ echo "subshell PID: $BASHPID"
+
+ # Make us main process
+ systemd-notify --pid="$BASHPID"
+
+ # Lock down access to just us
systemd-notify "NOTIFYACCESS=main"
- systemd-notify --status="Sending READY=1 in an unpriviledged process"
- (
- sleep 0.1
- systemd-notify --ready
- )
- sleep 10
+ # This should still work
+ systemd-notify --status="Sending READY=1 in an unprivileged process"
+
+ # Send as subprocess of the subshell, this should not work
+ systemd-notify --ready --pid=self --status "BOGUS1"
- systemd-notify "MAINPID=$$"
+ sync_out c
+ sync_in d
+
+ # Move main process back to toplevel
+ systemd-notify --pid=parent "MAINPID=$$"
+
+ # Should be dropped again
+ systemd-notify --status="BOGUS2" --pid=parent
+
+ # Apparently, bash will automatically invoke the last command in a subshell
+ # via a simple execve() rather than fork()ing first. But we want that the
+ # previous command uses the subshell's PID, hence let's insert a final,
+ # bogus redundant command as last command to run in the subshell, so that
+ # bash can't optimize things like that.
+ echo "bye"
)
+echo "toplevel again: $BASHPID"
+
systemd-notify --ready --status="OK"
systemd-notify "NOTIFYACCESS=none"
-sleep infinity
+systemd-notify --status="BOGUS3"
+
+sync_out e
+
+exec sleep infinity
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+link_endswith() {
+ [[ -h "${1:?}" && "$(readlink "${1:?}")" =~ ${2:?}$ ]]
+}
+
+link_eq() {
+ [[ -h "${1:?}" && "$(readlink "${1:?}")" == "${2:?}" ]]
+}
+
+# Get the value from a 'key=value' assignment
+opt_get_arg() {
+ local arg
+
+ IFS="=" read -r _ arg <<< "${1:?}"
+ test -n "$arg"
+ echo "$arg"
+}
+
+in_initrd() {
+ [[ "${SYSTEMD_IN_INITRD:-0}" -ne 0 ]]
+}
+
+# Check if we're parsing host's fstab in initrd
+in_initrd_host() {
+ in_initrd && [[ "${SYSTEMD_SYSROOT_FSTAB:-/dev/null}" != /dev/null ]]
+}
+
+in_container() {
+ systemd-detect-virt -qc
+}
+
+# Filter out "unwanted" options, i.e. options that the fstab-generator doesn't
+# propagate to the final mount unit
+opt_filter_consumed() {(
+ set +x
+ local opt split_options filtered_options
+
+ IFS="," read -ra split_options <<< "${1:?}"
+ for opt in "${split_options[@]}"; do
+ if [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+ continue
+ fi
+
+ filtered_options+=("$opt")
+ done
+
+ IFS=","; printf "%s" "${filtered_options[*]}"
+)}
+
+# Run the given generator $1 with target directory $2 - clean the target
+# directory beforehand
+run_and_list() {
+ local generator="${1:?}"
+ local out_dir="${2:?}"
+
+ rm -fr "${out_dir:?}"/*
+ "$generator" "$out_dir"
+ ls -lR "$out_dir"
+}
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-01-BASIC
-After=multi-user.target
+# Order the test unit after systemd-update-utmp-runlevel.service, since
+# the service doesn't play well with daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27167
+After=multi-user.target systemd-update-utmp-runlevel.service
Wants=systemd-resolved.service systemd-networkd.service
[Service]
ExecStartPre=rm -f /failed /testok
-ExecStart=sh -e -x -c 'systemctl --state=failed --no-legend --no-pager >/failed ; systemctl daemon-reload ; echo OK >/testok'
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+STTY_ORIGINAL="$(stty --file=/dev/console --save)"
+
+at_exit() {
+ set +e
+ stty --file=/dev/console "${STTY_ORIGINAL:?}"
+}
+
+trap at_exit EXIT
+
+# Do one reexec beforehand to get /dev/console into some predictable state
+systemctl daemon-reexec
+
+# Check if we do skip the early setup when doing daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27106
+#
+# Change a couple of console settings, do a reexec, and then check if our
+# changes persisted, since we reset the terminal stuff only on "full" reexec
+#
+# Relevant function: reset_terminal_fd() from terminal-util.cs
+stty --file=/dev/console brkint igncr inlcr istrip iuclc -icrnl -imaxbel -iutf8 \
+ kill ^K quit ^I
+STTY_NEW="$(stty --file=/dev/console --save)"
+systemctl daemon-reexec
+diff <(echo "$STTY_NEW") <(stty --file=/dev/console --save)
+
+if ! systemd-detect-virt -qc; then
+ # We also disable coredumps when doing a "full" reexec, so check for that too
+ sysctl -w kernel.core_pattern=dont-overwrite-me
+ systemctl daemon-reexec
+ diff <(echo dont-overwrite-me) <(sysctl --values kernel.core_pattern)
+fi
+
+# Collect failed units & do one daemon-reload to a basic sanity check
+systemctl --state=failed --no-legend --no-pager | tee /failed
+systemctl daemon-reload
+
+echo OK >/testok
rm /tmp/lb1
+# https://bugzilla.redhat.com/show_bug.cgi?id=2183546
+mkdir /run/systemd/system/systemd-journald.service.d
+MID=$(cat /etc/machine-id)
+for c in "NONE" "XZ" "LZ4" "ZSTD"; do
+ cat >/run/systemd/system/systemd-journald.service.d/compress.conf <<EOF
+[Service]
+Environment=SYSTEMD_JOURNAL_COMPRESS=${c}
+EOF
+ systemctl daemon-reload
+ systemctl restart systemd-journald.service
+ journalctl --rotate
+
+ ID=$(systemd-id128 new)
+ systemd-cat -t "$ID" /bin/bash -c "for ((i=0;i<100;i++)); do echo -n hoge with ${c}; done; echo"
+ journalctl --sync
+ timeout 10 bash -c "while ! SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /var/log/journal/$MID/system.journal 2>&1 | grep -q -F 'compress=${c}'; do sleep .5; done"
+
+ # $SYSTEMD_JOURNAL_COMPRESS= also works for journal-remote
+ if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then
+ for cc in "NONE" "XZ" "LZ4" "ZSTD"; do
+ rm -f /tmp/foo.journal
+ SYSTEMD_JOURNAL_COMPRESS="${cc}" /usr/lib/systemd/systemd-journal-remote --split-mode=none -o /tmp/foo.journal --getter="journalctl -b -o export -t $ID"
+ SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -q -F "compress=${cc}"
+ journalctl -t "$ID" -o cat --file /tmp/foo.journal | grep -q -F "hoge with ${c}"
+ done
+ fi
+done
+rm /run/systemd/system/systemd-journald.service.d/compress.conf
+systemctl daemon-reload
+systemctl restart systemd-journald.service
+journalctl --rotate
+
touch /testok
# Failed to parse rules file .: Is a directory
cp "${workdir}/default_output_1_fail" "${exo}"
assert_1 .
+# Failed to parse rules file ./nosuchfile: No such file or directory
+assert_1 ./nosuchfile
# Failed to parse rules file .: Is a directory
cat >"${exo}" <<EOF
test_syntax_error 'ACTION=="a",, NAME="b"' 'More than one comma between tokens.'
test_syntax_error 'ACTION=="a" , NAME="b"' 'Stray whitespace before comma.'
test_syntax_error 'ACTION=="a",NAME="b"' 'Whitespace after comma is expected.'
+test_syntax_error 'RESULT=="a", PROGRAM="b"' 'Reordering RESULT check after PROGRAM assignment.'
+test_syntax_error 'RESULT=="a*", PROGRAM="b", RESULT=="*c", PROGRAM="d"' \
+ 'Reordering RESULT check after PROGRAM assignment.'
cat >"${rules}" <<'EOF'
KERNEL=="a|b", KERNEL=="a|c", NAME="d"
KERNEL=="|a", KERNEL=="|b", NAME="c"
KERNEL=="*", KERNEL=="a*", NAME="b"
KERNEL=="a*", KERNEL=="c*|ab*", NAME="d"
+PROGRAM="a", RESULT=="b"
EOF
assert_0 "${rules}"
useradd test ||:
trap "userdel -r test" RETURN
- systemd-run --uid=test -p User=test -p Delegate=yes --slice workload.slice --unit workload0.scope --scope \
- test -w /sys/fs/cgroup/workload.slice/workload0.scope -a \
- -w /sys/fs/cgroup/workload.slice/workload0.scope/cgroup.procs -a \
- -w /sys/fs/cgroup/workload.slice/workload0.scope/cgroup.subtree_control
+ systemd-run --uid=test -p User=test -p Delegate=yes --slice workload.slice --unit test-workload0.scope --scope \
+ test -w /sys/fs/cgroup/workload.slice/test-workload0.scope -a \
+ -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.procs -a \
+ -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control
}
if grep -q cgroup2 /proc/filesystems ; then
- systemd-run --wait --unit=test0.service -p "DynamicUser=1" -p "Delegate=" \
- test -w /sys/fs/cgroup/system.slice/test0.service/ -a \
- -w /sys/fs/cgroup/system.slice/test0.service/cgroup.procs -a \
- -w /sys/fs/cgroup/system.slice/test0.service/cgroup.subtree_control
+ systemd-run --wait --unit=test-0.service -p "DynamicUser=1" -p "Delegate=" \
+ test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \
+ -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.procs -a \
+ -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.subtree_control
- systemd-run --wait --unit=test1.service -p "DynamicUser=1" -p "Delegate=memory pids" \
- grep -q memory /sys/fs/cgroup/system.slice/test1.service/cgroup.controllers
+ systemd-run --wait --unit=test-1.service -p "DynamicUser=1" -p "Delegate=memory pids" \
+ grep -q memory /sys/fs/cgroup/system.slice/test-1.service/cgroup.controllers
- systemd-run --wait --unit=test2.service -p "DynamicUser=1" -p "Delegate=memory pids" \
- grep -q pids /sys/fs/cgroup/system.slice/test2.service/cgroup.controllers
+ systemd-run --wait --unit=test-2.service -p "DynamicUser=1" -p "Delegate=memory pids" \
+ grep -q pids /sys/fs/cgroup/system.slice/test-2.service/cgroup.controllers
# "io" is not among the controllers enabled by default for all units, verify that
grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
# Run a service with "io" enabled, and verify it works
- systemd-run --wait --unit=test3.service -p "IOAccounting=yes" -p "Slice=system-foo-bar-baz.slice" \
- grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test3.service/cgroup.controllers
+ systemd-run --wait --unit=test-3.service -p "IOAccounting=yes" -p "Slice=system-foo-bar-baz.slice" \
+ grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test-3.service/cgroup.controllers
# We want to check if "io" is removed again from the controllers
# list. However, PID 1 (rightfully) does this asynchronously. In order
# to force synchronization on this, let's start a short-lived service
# which requires PID 1 to refresh the cgroup tree, so that we can
# verify that this all works.
- systemd-run --wait --unit=test4.service true
+ systemd-run --wait --unit=test-4.service true
# And now check again, "io" should have vanished
grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
disown
# Start a test process outside of our own cgroup
-systemd-run -p DynamicUser=1 --unit=test20-sleep.service /bin/sleep infinity
-EXTERNALPID="$(systemctl show -P MainPID test20-sleep.service)"
+systemd-run -p DynamicUser=1 --unit=test-sleep.service /bin/sleep infinity
+EXTERNALPID="$(systemctl show -P MainPID test-sleep.service)"
# Update our own main PID to the external test PID, this should work
systemd-notify MAINPID="$EXTERNALPID"
systemd-notify --uid=1000 MAINPID=$$
test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
-cat >/tmp/test20-mainpid.sh <<EOF
+cat >/tmp/test-mainpid.sh <<EOF
#!/usr/bin/env bash
set -eux
echo \$MAINPID >/run/mainpidsh/pid
EOF
-chmod +x /tmp/test20-mainpid.sh
+chmod +x /tmp/test-mainpid.sh
-systemd-run --unit=test20-mainpidsh.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh -p PIDFile=/run/mainpidsh/pid /tmp/test20-mainpid.sh
-test "$(systemctl show -P MainPID test20-mainpidsh.service)" -eq "$(cat /run/mainpidsh/pid)"
+systemd-run --unit=test-mainpidsh.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh -p PIDFile=/run/mainpidsh/pid /tmp/test-mainpid.sh
+test "$(systemctl show -P MainPID test-mainpidsh.service)" -eq "$(cat /run/mainpidsh/pid)"
-cat >/tmp/test20-mainpid2.sh <<EOF
+cat >/tmp/test-mainpid2.sh <<EOF
#!/usr/bin/env bash
set -eux
echo \$MAINPID >/run/mainpidsh2/pid
chown 1001:1001 /run/mainpidsh2/pid
EOF
-chmod +x /tmp/test20-mainpid2.sh
+chmod +x /tmp/test-mainpid2.sh
-systemd-run --unit=test20-mainpidsh2.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh2 -p PIDFile=/run/mainpidsh2/pid /tmp/test20-mainpid2.sh
-test "$(systemctl show -P MainPID test20-mainpidsh2.service)" -eq "$(cat /run/mainpidsh2/pid)"
+systemd-run --unit=test-mainpidsh2.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh2 -p PIDFile=/run/mainpidsh2/pid /tmp/test-mainpid2.sh
+test "$(systemctl show -P MainPID test-mainpidsh2.service)" -eq "$(cat /run/mainpidsh2/pid)"
-cat >/dev/shm/test20-mainpid3.sh <<EOF
+cat >/dev/shm/test-mainpid3.sh <<EOF
#!/usr/bin/env bash
set -eux
# Quick assertion that the link isn't dead
test -f /run/mainpidsh3/pid
EOF
-chmod 755 /dev/shm/test20-mainpid3.sh
+chmod 755 /dev/shm/test-mainpid3.sh
# This has to fail, as we shouldn't accept the dangerous PID file, and then
# inotify-wait on it to be corrected which we never do.
-systemd-run --unit=test20-mainpidsh3.service \
+systemd-run --unit=test-mainpidsh3.service \
-p StandardOutput=tty \
-p StandardError=tty \
-p Type=forking \
-p PIDFile=/run/mainpidsh3/pid \
-p DynamicUser=1 \
-p TimeoutStartSec=2s \
- /dev/shm/test20-mainpid3.sh \
+ /dev/shm/test-mainpid3.sh \
&& { echo 'unexpected success'; exit 1; }
# Test that this failed due to timeout, and not some other error
-test "$(systemctl show -P Result test20-mainpidsh3.service)" = timeout
+test "$(systemctl show -P Result test-mainpidsh3.service)" = timeout
# Test that scope units work
-systemd-run --scope --unit test20-true.scope /bin/true
-test "$(systemctl show -P Result test20-true.scope)" = success
+systemd-run --scope --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
# Test that user scope units work as well
runas() {
declare userid=$1
shift
- # shellcheck disable=SC2016
- su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+ XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
}
systemctl start user@4711.service
-runas testuser systemd-run --scope --user --unit test20-true.scope /bin/true
-test "$(systemctl show -P Result test20-true.scope)" = success
+runas testuser systemd-run --scope --user --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
systemd-analyze log-level info
set -eux
set -o pipefail
-cat >/etc/systemd/system/testservice.service <<EOF
+cat >/etc/systemd/system/test-service.service <<EOF
[Service]
-ConfigurationDirectory=testservice
-RuntimeDirectory=testservice
-StateDirectory=testservice
-CacheDirectory=testservice
-LogsDirectory=testservice
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
RuntimeDirectoryPreserve=yes
ExecStart=/bin/sleep infinity
Type=exec
systemctl daemon-reload
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
-systemctl start testservice
+systemctl start test-service
-test -d /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
-systemctl clean testservice && { echo 'unexpected success'; exit 1; }
+systemctl clean test-service && { echo 'unexpected success'; exit 1; }
-systemctl stop testservice
+systemctl stop test-service
-test -d /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
-systemctl clean testservice --what=configuration
+systemctl clean test-service --what=configuration
-test ! -e /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test ! -e /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
-systemctl clean testservice
+systemctl clean test-service
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test -d /var/lib/testservice
-test ! -e /var/cache/testservice
-test -d /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test -d /var/log/test-service
-systemctl clean testservice --what=logs
+systemctl clean test-service --what=logs
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test -d /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
-systemctl clean testservice --what=all
+systemctl clean test-service --what=all
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
-cat >/etc/systemd/system/testservice.service <<EOF
+cat >/etc/systemd/system/test-service.service <<EOF
[Service]
DynamicUser=yes
-ConfigurationDirectory=testservice
-RuntimeDirectory=testservice
-StateDirectory=testservice
-CacheDirectory=testservice
-LogsDirectory=testservice
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
RuntimeDirectoryPreserve=yes
ExecStart=/bin/sleep infinity
Type=exec
systemctl daemon-reload
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
-
-systemctl restart testservice
-
-test -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice && { echo 'unexpected success'; exit 1; }
-
-systemctl stop testservice
-
-test -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice --what=configuration
-
-test ! -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test ! -L /run/testservice
-test -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice --what=logs
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test ! -d /var/log/private/testservice
-test ! -L /run/testservice
-test -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test ! -L /var/log/testservice
-
-systemctl clean testservice --what=all
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test ! -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test ! -d /var/log/private/testservice
-test ! -L /run/testservice
-test ! -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test ! -L /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+systemctl restart test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service && { echo 'unexpected success'; exit 1; }
+
+systemctl stop test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=configuration
+
+test ! -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=logs
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
+
+systemctl clean test-service --what=all
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test ! -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test ! -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
cat >/etc/systemd/system/tmp-hoge.mount <<EOF
[Mount]
test ! -d /var/cache/hoge
test ! -d /var/log/hoge
-cat >/etc/systemd/system/testservice.socket <<EOF
+cat >/etc/systemd/system/test-service.socket <<EOF
[Socket]
-ListenSequentialPacket=/run/testservice.socket
+ListenSequentialPacket=/run/test-service.socket
RemoveOnStop=yes
ExecStartPre=true
-ConfigurationDirectory=testsocket
-RuntimeDirectory=testsocket
-StateDirectory=testsocket
-CacheDirectory=testsocket
-LogsDirectory=testsocket
+ConfigurationDirectory=test-socket
+RuntimeDirectory=test-socket
+StateDirectory=test-socket
+CacheDirectory=test-socket
+LogsDirectory=test-socket
EOF
systemctl daemon-reload
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test ! -e /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
-systemctl start testservice.socket
+systemctl start test-service.socket
-test -d /etc/testsocket
-test -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test -d /etc/test-socket
+test -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
-systemctl clean testservice.socket && { echo 'unexpected success'; exit 1; }
+systemctl clean test-service.socket && { echo 'unexpected success'; exit 1; }
-systemctl stop testservice.socket
+systemctl stop test-service.socket
-test -d /etc/testsocket
-test ! -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test -d /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
-systemctl clean testservice.socket --what=configuration
+systemctl clean test-service.socket --what=configuration
-test ! -e /etc/testsocket
-test ! -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
-systemctl clean testservice.socket
+systemctl clean test-service.socket
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test -d /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test -d /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test -d /var/log/test-socket
-systemctl clean testservice.socket --what=logs
+systemctl clean test-service.socket --what=logs
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test -d /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
-systemctl clean testservice.socket --what=all
+systemctl clean test-service.socket --what=all
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test ! -e /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
echo OK >/testok
# CAP_CHOWN | CAP_KILL
MASK=$(((1 << 0) | (1 << 5)))
- if [ $(("$BND" & "$MASK")) -ne "$MASK" ] ; then
+ if [ $((BND & MASK)) -ne "$MASK" ] ; then
echo "CAP_CHOWN or CAP_KILL not available in bounding set, skipping test." >&2
return
fi
runas() {
declare userid=$1
shift
- # shellcheck disable=SC2016
- su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+ XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
}
runas testuser systemd-run --wait --user --unit=test-private-users \
test ! -e /usr/lib/systemd/system/some_file
systemd-sysext unmerge
rmdir /etc/extensions/app-nodistro
+
+# Check that extensions cannot contain os-release
+mkdir -p /run/extensions/app-reject/usr/lib/{extension-release.d/,systemd/system}
+echo "ID=_any" >/run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject
+echo "ID=_any" >/run/extensions/app-reject/usr/lib/os-release
+touch /run/extensions/app-reject/usr/lib/systemd/system/other_file
+systemd-sysext merge && { echo 'unexpected success'; exit 1; }
+test ! -e /usr/lib/systemd/system/some_file
+test ! -e /usr/lib/systemd/system/other_file
+systemd-sysext unmerge
+rm -rf /run/extensions/app-reject
rm /var/lib/extensions/app-nodistro.raw
mkdir -p /run/machines /run/portables /run/extensions
systemd-dissect --detach "${image}.raw"
(! systemd-dissect --detach "${image}.raw")
+# check for confext functionality
+mkdir -p /run/confexts/test/etc/extension-release.d
+echo "ID=_any" >/run/confexts/test/etc/extension-release.d/extension-release.test
+echo "ARCHITECTURE=_any" >>/run/confexts/test/etc/extension-release.d/extension-release.test
+echo "MARKER_CONFEXT_123" >/run/confexts/test/etc/testfile
+systemd-confext merge
+grep -q -F "MARKER_CONFEXT_123" /etc/testfile
+systemd-confext status
+systemd-confext unmerge
+rm -rf /run/confexts/
+
echo OK >/testok
exit 0
-p LoadCredential=shadow:/etc/shadow \
-p SetCredential=dog:wuff \
-p DynamicUser=1 \
+ --unit=test-54-unpriv.service \
--wait \
--pipe \
cat '${CREDENTIALS_DIRECTORY}/passwd' '${CREDENTIALS_DIRECTORY}/shadow' '${CREDENTIALS_DIRECTORY}/dog' >/tmp/ts54-concat
# Verify that the creds are immutable
systemd-run -p LoadCredential=passwd:/etc/passwd \
-p DynamicUser=1 \
+ --unit=test-54-immutable-touch.service \
--wait \
touch '${CREDENTIALS_DIRECTORY}/passwd' \
&& { echo 'unexpected success'; exit 1; }
systemd-run -p LoadCredential=passwd:/etc/passwd \
-p DynamicUser=1 \
+ --unit=test-54-immutable-rm.service \
--wait \
rm '${CREDENTIALS_DIRECTORY}/passwd' \
&& { echo 'unexpected success'; exit 1; }
echo -n d >/tmp/ts54-creds/sub/qux
systemd-run -p LoadCredential=cred:/tmp/ts54-creds \
-p DynamicUser=1 \
+ --unit=test-54-dir.service \
--wait \
--pipe \
cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
if [ -f /tmp/testsuite-57.counter ] ; then
read -r counter < /tmp/testsuite-57.counter
- counter=$(("$counter" + 1))
+ counter=$((counter + 1))
else
counter=0
fi
runas() {
declare userid=$1
shift
- # shellcheck disable=SC2016
- su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+ XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
}
if ! command -v systemd-repart &>/dev/null; then
function reload() {
systemd-notify --reloading --status="Adding 11 to exit status"
- EXIT_STATUS=\$((\$EXIT_STATUS + 11))
+ EXIT_STATUS=\$((EXIT_STATUS + 11))
systemd-notify --ready --status="Back running"
}
function leave() {
systemd-notify --stopping --status="Adding 7 to exit status"
- EXIT_STATUS=\$((\$EXIT_STATUS + 7))
+ EXIT_STATUS=\$((EXIT_STATUS + 7))
LEAVE=1
return 0
}
done
systemd-notify --status="Adding 3 to exit status"
-EXIT_STATUS=\$((\$EXIT_STATUS + 3))
+EXIT_STATUS=\$((EXIT_STATUS + 3))
exit \$EXIT_STATUS
EOF
systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata
rm /tmp/testdata
+# negative tests for cryptenroll
+
+# Prepare a new disk image
+img_2="/var/tmp/file_enroll.txt"
+truncate -s 20M $img_2
+echo -n password >/tmp/password
+cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img_2 /tmp/password
+
+#boolean_arguments
+systemd-cryptenroll --fido2-with-client-pin=false && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-client-pin=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-client-pin=false $img_2
+
+systemd-cryptenroll --fido2-with-user-presence=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-user-presence=false $img_2
+
+systemd-cryptenroll --fido2-with-user-verification=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --tpm2-with-pin=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-user-verification=false $img_2
+
+#arg_enroll_type
+systemd-cryptenroll --recovery-key --password $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --recovery-key $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --tpm2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+#arg_unlock_type
+systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2 && { echo 'unexpected success'; exit 1; }
+
+#fido2_cred_algorithm
+systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2 && { echo 'unexpected success'; exit 1; }
+
+#tpm2_errors
+systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --tpm2-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; }
+
+#wipe_slots
+systemd-cryptenroll --wipe-slot $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --wipe-slot=10240000 $img_2 && { echo 'unexpected success'; exit 1; }
+
+#fido2_multiple_auto
+systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
echo OK >/testok
exit 0
: >/failed
+# Make sure the content of kbd-model-map is the one that the tests expect
+# regardless of the version intalled on the distro where the testsuite is
+# running on.
+export SYSTEMD_KBD_MODEL_MAP=/usr/lib/systemd/tests/testdata/test-keymap-util/kbd-model-map
+
enable_debug
test_locale
test_vc_keymap
grep -q "^root:$ROOT_HASHED_PASSWORD2:" "$ROOT/etc/shadow"
grep -q "hello.world=0" "$ROOT/etc/kernel/cmdline"
+# Test that --reset removes all files configured by firstboot.
+systemd-firstboot --root="$ROOT" --reset
+[[ ! -e "$ROOT/etc/locale.conf" ]]
+[[ ! -e "$ROOT/etc/vconsole.conf" ]]
+[[ ! -e "$ROOT/etc/localtime" ]]
+[[ ! -e "$ROOT/etc/hostname" ]]
+[[ ! -e "$ROOT/etc/machine-id" ]]
+[[ ! -e "$ROOT/etc/kernel/cmdline" ]]
+
# --copy-* options
rm -fr "$ROOT"
mkdir "$ROOT"
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+MODULES_LOAD_BIN="/usr/lib/systemd/systemd-modules-load"
+CONFIG_FILE="/run/modules-load.d/99-test.conf"
+
+at_exit() {
+ rm -rfv "${CONFIG_FILE:?}"
+}
+
+trap at_exit EXIT
+
+if systemd-detect-virt -cq; then
+ echo "Running in a container, skipping the systemd-modules-load test..."
+ exit 0
+fi
+
+# Check if we have required kernel modules
+modprobe --all --resolve-alias loop dummy
+
+mkdir -p /run/modules-load.d/
+
+"$MODULES_LOAD_BIN"
+"$MODULES_LOAD_BIN" --help
+"$MODULES_LOAD_BIN" --version
+
+# Explicit config file
+modprobe -v --all --remove loop dummy
+printf "loop\ndummy" >"$CONFIG_FILE"
+"$MODULES_LOAD_BIN" "$CONFIG_FILE" |& tee /tmp/out.log
+grep -E "Inserted module .*loop" /tmp/out.log
+grep -E "Inserted module .*dummy" /tmp/out.log
+
+# Implicit config file
+modprobe -v --all --remove loop dummy
+printf "loop\ndummy" >"$CONFIG_FILE"
+"$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "Inserted module .*loop" /tmp/out.log
+grep -E "Inserted module .*dummy" /tmp/out.log
+
+# Valid & invalid data mixed together
+modprobe -v --all --remove loop dummy
+cat >"$CONFIG_FILE" <<EOF
+
+loop
+loop
+loop
+ loop
+dummy
+ \\n\n\n\\\\\\
+
+loo!@@123##2455
+# This is a comment
+$(printf "%.0sx" {0..4096})
+dummy
+loop
+foo-bar-baz
+1
+"
+'
+EOF
+"$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "^Inserted module .*loop" /tmp/out.log
+grep -E "^Inserted module .*dummy" /tmp/out.log
+grep -E "^Failed to find module .*foo-bar-baz" /tmp/out.log
+(! grep -E "This is a comment" /tmp/out.log)
+# Each module should be loaded only once, even if specified multiple times
+[[ "$(grep -Ec "^Inserted module" /tmp/out.log)" -eq 2 ]]
+[[ "$(grep -Ec "^Failed to find module" /tmp/out.log)" -eq 7 ]]
+
+# Command line arguments
+modprobe -v --all --remove loop dummy
+# Make sure we have no config files left over that might interfere with
+# following tests
+rm -fv "$CONFIG_FILE"
+[[ -z "$(systemd-analyze cat-config modules-load.d)" ]]
+CMDLINE="ro root= modules_load= modules_load=, / = modules_load=foo-bar-baz,dummy modules_load=loop,loop,loop"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" "$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "^Inserted module .*loop" /tmp/out.log
+grep -E "^Inserted module .*dummy" /tmp/out.log
+grep -E "^Failed to find module .*foo-bar-baz" /tmp/out.log
+# Each module should be loaded only once, even if specified multiple times
+[[ "$(grep -Ec "^Inserted module" /tmp/out.log)" -eq 2 ]]
+
+(! "$MODULES_LOAD_BIN" --nope)
+(! "$MODULES_LOAD_BIN" /foo/bar/baz)
losetup -d "$LOOP"
unset LOOP
# The automount unit should disappear once the underlying blockdev is gone
-timeout 10s bash -c "while systemctl status '$(systemd-escape --path "$WORK_DIR/mnt").automount)'; do sleep .2; done"
+timeout 10s bash -c "while systemctl status '$(systemd-escape --path "$WORK_DIR/mnt".automount)'; do sleep .2; done"
# Mount a disk image
systemd-mount --discover "$WORK_DIR/simple.img"
test -e /run/media/system/simple.img/foo.bar
# systemd-mount --list and systemd-umount require the loopback block device is initialized by udevd.
udevadm settle --timeout 30
-assert_in "/dev/loop.* ext4 sd-mount-test" "$(systemd-mount --list --full)"
+assert_in "/dev/loop.* ext4 +sd-mount-test" "$(systemd-mount --list --full)"
systemd-umount "$WORK_DIR/simple.img"
# --owner + vfat
: >/failed
+mkfifo /tmp/syncfifo1 /tmp/syncfifo2
+
+sync_in() {
+ read -r x < /tmp/syncfifo1
+ test "$x" = "$1"
+}
+
+sync_out() {
+ echo "$1" > /tmp/syncfifo2
+}
+
+export SYSTEMD_LOG_LEVEL=debug
+
systemctl --no-block start notify.service
-sleep 2
-assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts, waiting for 5 seconds"
+sync_in a
+
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
-sleep 5
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts"
+
+sync_out b
+sync_in c
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "main"
-assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unpriviledged process"
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unprivileged process"
assert_rc 3 systemctl --quiet is-active notify.service
-sleep 10
+
+sync_out d
+sync_in e
systemctl --quiet is-active notify.service
assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK"
systemctl stop notify.service
assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
+rm /tmp/syncfifo1 /tmp/syncfifo2
+
+MYSCRIPT="/tmp/myscript$RANDOM.sh"
+cat >> "$MYSCRIPT" <<'EOF'
+#!/usr/bin/env bash
+set -eux
+set -o pipefail
+N="/tmp/$RANDOM"
+echo $RANDOM > "$N"
+systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N"
+rm "$N"
+systemd-notify --ready
+exec sleep infinity
+EOF
+
+chmod +x "$MYSCRIPT"
+
+MYUNIT="myunit$RANDOM.service"
+systemd-run -u "$MYUNIT" -p Type=notify -p StandardOutput=journal+console -p StandardError=journal+console -p FileDescriptorStoreMax=7 "$MYSCRIPT"
+
+test "$(systemd-analyze fdstore "$MYUNIT" | wc -l)" -eq 2
+systemd-analyze fdstore "$MYUNIT" --json=short
+systemd-analyze fdstore "$MYUNIT" --json=short | grep -P -q '\[{"fdname":"quux","type":.*,"devno":\[.*\],"inode":.*,"rdevno":null,"path":"/tmp/.*","flags":"ro"}\]'
+
+systemctl stop "$MYUNIT"
+rm "$MYSCRIPT"
+
touch /testok
rm /failed
+
+exit 0
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-debug-generator"
+OUT_DIR="$(mktemp -d /tmp/debug-generator.XXX)"
+
+at_exit() {
+ rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+# Potential FIXME:
+# - debug-generator should gracefully handle duplicated mask/wants
+# - also, handle gracefully empty mask/wants
+ARGS=(
+ "systemd.mask=masked-no-suffix"
+ "systemd.mask=masked.service"
+ "systemd.mask=masked.socket"
+ "systemd.wants=wanted-no-suffix"
+ "systemd.wants=wanted.service"
+ "systemd.wants=wanted.mount"
+ "rd.systemd.mask=masked-initrd.service"
+ "rd.systemd.wants=wanted-initrd.service"
+)
+
+# Regular (non-initrd) scenario
+#
+: "debug-shell: regular"
+CMDLINE="ro root=/ ${ARGS[*]} rd.systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/masked.service" /dev/null
+link_eq "$OUT_DIR/masked.socket" /dev/null
+link_endswith "$OUT_DIR/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/default.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/default.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+# Following stuff should be ignored, as it's prefixed with rd.
+test ! -h "$OUT_DIR/masked-initrd.service"
+test ! -h "$OUT_DIR/default.target.wants/wants-initrd.service"
+test ! -h "$OUT_DIR/default.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/initrd.target.wants"
+
+# Let's re-run the generator with systemd.debug_shell that should be honored
+: "debug-shell: regular + systemd.debug_shell"
+CMDLINE="$CMDLINE systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Same thing, but with custom tty
+: "debug-shell: regular + systemd.debug_shell=/dev/tty666"
+CMDLINE="$CMDLINE systemd.debug_shell=/dev/tty666"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+grep -F "/dev/tty666" "$OUT_DIR/debug-shell.service.d/50-tty.conf"
+
+# Now override the default target via systemd.unit=
+: "debug-shell: regular + systemd.unit="
+CMDLINE="$CMDLINE systemd.unit=my-fancy.target"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/masked.service" /dev/null
+link_eq "$OUT_DIR/masked.socket" /dev/null
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+link_endswith "$OUT_DIR/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+test ! -d "$OUT_DIR/default.target.wants"
+
+
+# Initrd scenario
+: "debug-shell: initrd"
+CMDLINE="ro root=/ ${ARGS[*]} systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+# The non-initrd stuff (i.e. without the rd. suffix) should be ignored in
+# this case
+test ! -h "$OUT_DIR/masked-no-suffix.service"
+test ! -h "$OUT_DIR/masked.service"
+test ! -h "$OUT_DIR/masked.socket"
+test ! -h "$OUT_DIR/initrd.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/default.target.wants"
+
+# Again, but with rd.systemd.debug_shell
+: "debug-shell: initrd + rd.systemd.debug_shell"
+CMDLINE="$CMDLINE rd.systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/initrd.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Override the default target
+: "debug-shell: initrd + rd.systemd.unit"
+CMDLINE="$CMDLINE rd.systemd.unit=my-fancy-initrd.target"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+test ! -d "$OUT_DIR/initrd.target.wants"
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator"
+CONFIG_FILE="/run/environment.d/99-test.conf"
+OUT_FILE="$(mktemp)"
+
+at_exit() {
+ set +e
+ rm -frv "${CONFIG_FILE:?}" "${OUT_FILE:?}"
+ systemctl -M testuser@.host --user daemon-reload
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+mkdir -p /run/environment.d/
+
+cat >"$CONFIG_FILE" <<EOF
+
+\t\n\t
+3
+=
+ =
+INVALID
+ALSO_INVALID=
+EMPTY_INVALID=""
+3_INVALID=foo
+xxxx xx xxxxxx
+# This is a comment
+$(printf "%.0sx" {0..4096})=
+SIMPLE=foo
+REF=\$SIMPLE
+ALSO_REF=\${SIMPLE}
+DEFAULT="\${NONEXISTENT:-default value}"
+ALTERNATE="\${SIMPLE:+alternate value}"
+LIST=foo,bar,baz
+SIMPLE=redefined
+UNASSIGNED=\$FOO_BAR_BAZ
+VERY_LONG="very $(printf "%.0sx" {0..4096})= long string"
+EOF
+
+# Source env assignments from a file and check them - do this in a subshell
+# to not pollute the test environment
+check_environment() {(
+ # shellcheck source=/dev/null
+ source "${1:?}"
+
+ [[ "$SIMPLE" == "redefined" ]]
+ [[ "$REF" == "foo" ]]
+ [[ "$ALSO_REF" == "foo" ]]
+ [[ "$DEFAULT" == "default value" ]]
+ [[ "$ALTERNATE" == "alternate value" ]]
+ [[ "$LIST" == "foo,bar,baz" ]]
+ [[ "$VERY_LONG" =~ ^very\ ]]
+ [[ "$VERY_LONG" =~ \ long\ string$ ]]
+ [[ -z "$UNASSIGNED" ]]
+ [[ ! -v INVALID ]]
+ [[ ! -v ALSO_INVALID ]]
+ [[ ! -v EMPTY_INVALID ]]
+ [[ ! -v 3_INVALID ]]
+)}
+
+# Check the output by directly calling the generator
+"$GENERATOR_BIN" | tee "$OUT_FILE"
+check_environment "$OUT_FILE"
+: >"$OUT_FILE"
+
+# Check if the generator is correctly called in a user session
+systemctl -M testuser@.host --user daemon-reload
+systemctl -M testuser@.host --user show-environment | tee "$OUT_FILE"
+check_environment "$OUT_FILE"
+
+(! "$GENERATOR_BIN" foo)
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235,SC2233
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+export SYSTEMD_LOG_LEVEL=debug
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-fstab-generator"
+NETWORK_FS_RX="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$"
+OUT_DIR="$(mktemp -d /tmp/fstab-generator.XXX)"
+FSTAB="$(mktemp)"
+
+at_exit() {
+ rm -fr "${OUT_DIR:?}" "${FSTAB:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+FSTAB_GENERAL=(
+ # Valid entries
+ "/dev/test2 /nofail ext4 nofail 0 0"
+ "/dev/test3 /regular btrfs defaults 0 0"
+ "/dev/test4 /x-systemd.requires xfs x-systemd.requires=foo.service 0 0"
+ "/dev/test5 /x-systemd.before-after xfs x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0"
+ "/dev/test6 /x-systemd.wanted-required-by xfs x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0"
+ "/dev/test7 /x-systemd.requires-mounts-for xfs x-systemd.requires-mounts-for=/foo/bar/baz 0 0"
+ "/dev/test8 /x-systemd.automount-idle-timeout vfat x-systemd.automount,x-systemd.idle-timeout=50s 0 0"
+ "/dev/test9 /x-systemd.makefs xfs x-systemd.makefs 0 0"
+ "/dev/test10 /x-systemd.growfs xfs x-systemd.growfs 0 0"
+ "/dev/test11 /_netdev ext4 defaults,_netdev 0 0"
+ "/dev/test12 /_rwonly ext4 x-systemd.rw-only 0 0"
+ "/dev/test13 /chaos1 zfs x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0"
+ "/dev/test14 /chaos2 zfs x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0"
+ "/dev/test15 /fstype/auto auto defaults 0 0"
+ "/dev/test16 /fsck/me ext4 defaults 0 1"
+ "/dev/test17 /also/fsck/me ext4 defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99"
+ "/dev/test18 /swap swap defaults 0 0"
+ "/dev/test19 /swap/makefs swap defaults,x-systemd.makefs 0 0"
+ "/dev/test20 /var xfs defaults,x-systemd.device-timeout=1h 0 0"
+ "/dev/test21 /usr ext4 defaults 0 1"
+ "/dev/test22 /initrd/mount ext2 defaults,x-systemd.rw-only,x-initrd.mount 0 1"
+ "/dev/test23 /initrd/mount/nofail ext3 defaults,nofail,x-initrd.mount 0 1"
+ "/dev/test24 /initrd/mount/deps ext4 x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1"
+
+ # Incomplete, but valid entries
+ "/dev/incomplete1 /incomplete1"
+ "/dev/incomplete2 /incomplete2 ext4"
+ "/dev/incomplete3 /incomplete3 ext4 defaults"
+ "/dev/incomplete4 /incomplete4 ext4 defaults 0"
+
+ # Remote filesystems
+ "/dev/remote1 /nfs nfs bg 0 0"
+ "/dev/remote2 /nfs4 nfs4 bg 0 0"
+ "bar.tld:/store /remote/storage nfs ro,x-systemd.wanted-by=store.service 0 0"
+ "user@host.tld:/remote/dir /remote/top-secret sshfs rw,x-systemd.before=naughty.service 0 0"
+ "foo.tld:/hello /hello/world ceph defaults 0 0"
+ "//192.168.0.1/storage /cifs-storage cifs automount,nofail 0 0"
+)
+
+FSTAB_GENERAL_ROOT=(
+ # rootfs with bunch of options we should ignore and fsck enabled
+ "/dev/test1 / ext4 noauto,nofail,automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1"
+ "${FSTAB_GENERAL[@]}"
+)
+
+FSTAB_MINIMAL=(
+ "/dev/loop1 /foo/bar ext3 defaults 0 0"
+)
+
+FSTAB_DUPLICATE=(
+ "/dev/dup1 / ext4 defaults 0 1"
+ "/dev/dup2 / ext4 defaults,x-systemd.requires=foo.mount 0 2"
+)
+
+FSTAB_INVALID=(
+ # Ignored entries
+ "/dev/ignored1 /sys/fs/cgroup/foo ext4 defaults 0 0"
+ "/dev/ignored2 /sys/fs/selinux ext4 defaults 0 0"
+ "/dev/ignored3 /dev/console ext4 defaults 0 0"
+ "/dev/ignored4 /proc/kmsg ext4 defaults 0 0"
+ "/dev/ignored5 /proc/sys ext4 defaults 0 0"
+ "/dev/ignored6 /proc/sys/kernel/random/boot_id ext4 defaults 0 0"
+ "/dev/ignored7 /run/host ext4 defaults 0 0"
+ "/dev/ignored8 /run/host/foo ext4 defaults 0 0"
+ "/dev/ignored9 /autofs autofs defaults 0 0"
+ "/dev/invalid1 not-a-path ext4 defaults 0 0"
+ ""
+ "/dev/invalid1"
+ " "
+ "\\"
+ "$"
+)
+
+check_fstab_mount_units() {
+ local what where fstype opts passno unit
+ local item opt split_options filtered_options supp service device arg
+ local array_name="${1:?}"
+ local out_dir="${2:?}"
+ # Get a reference to the array from its name
+ local -n fstab_entries="$array_name"
+
+ # Running the checks in a container is pretty much useless, since we don't
+ # generate any mounts, but don't skip the whole test to test the "skip"
+ # paths as well
+ in_container && return 0
+
+ for item in "${fstab_entries[@]}"; do
+ # Don't use a pipe here, as it would make the variables out of scope
+ read -r what where fstype opts _ passno <<< "$item"
+
+ # Skip non-initrd mounts in initrd
+ if in_initrd_host && ! [[ "$opts" =~ x-initrd.mount ]]; then
+ continue
+ fi
+
+ if [[ "$fstype" == swap ]]; then
+ unit="$(systemd-escape --suffix=swap --path "${what:?}")"
+ cat "$out_dir/$unit"
+
+ grep -qE "^What=$what$" "$out_dir/$unit"
+ if [[ "$opts" != defaults ]]; then
+ grep -qE "^Options=$opts$" "$out_dir/$unit"
+ fi
+
+ if [[ "$opts" =~ x-systemd.makefs ]]; then
+ service="$(systemd-escape --template=systemd-mkswap@.service --path "$what")"
+ test -e "$out_dir/$service"
+ fi
+
+ continue
+ fi
+
+ # If we're parsing host's fstab in initrd, prefix all mount targets
+ # with /sysroot
+ in_initrd_host && where="/sysroot${where:?}"
+ unit="$(systemd-escape --suffix=mount --path "${where:?}")"
+ cat "$out_dir/$unit"
+
+ # Check the general stuff
+ grep -qE "^What=$what$" "$out_dir/$unit"
+ grep -qE "^Where=$where$" "$out_dir/$unit"
+ if [[ -n "$fstype" ]] && [[ "$fstype" != auto ]]; then
+ grep -qE "^Type=$fstype$" "$out_dir/$unit"
+ fi
+ if [[ -n "$opts" ]] && [[ "$opts" != defaults ]]; then
+ # Some options are not propagated to the generated unit
+ filtered_options="$(opt_filter_consumed "$opts")"
+ if [[ "${filtered_options[*]}" != defaults ]]; then
+ grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit"
+ fi
+ fi
+
+ if ! [[ "$opts" =~ (noauto|x-systemd.(wanted-by=|required-by=|automount)) ]]; then
+ # We don't create the Requires=/Wants= symlinks for noauto/automount mounts
+ # and for mounts that use x-systemd.wanted-by=/required-by=
+ if in_initrd_host; then
+ if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+ link_eq "$out_dir/initrd-fs.target.requires/$unit" "../$unit"
+ else
+ link_eq "$out_dir/initrd-fs.target.wants/$unit" "../$unit"
+ fi
+ elif [[ "$fstype" =~ $NETWORK_FS_RX || "$opts" =~ _netdev ]]; then
+ # Units with network filesystems should have a Requires= dependency
+ # on the remote-fs.target, unless they use nofail or are an nfs "bg"
+ # mounts, in which case the dependency is downgraded to Wants=
+ if [[ "$opts" =~ nofail ]] || [[ "$fstype" =~ ^(nfs|nfs4) && "$opts" =~ bg ]]; then
+ link_eq "$out_dir/remote-fs.target.wants/$unit" "../$unit"
+ else
+ link_eq "$out_dir/remote-fs.target.requires/$unit" "../$unit"
+ fi
+ else
+ # Similarly, local filesystems should have a Requires= dependency on
+ # the local-fs.target, unless they use nofail, in which case the
+ # dependency is downgraded to Wants=. Rootfs is a special case,
+ # since we always ignore nofail there
+ if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+ link_eq "$out_dir/local-fs.target.requires/$unit" "../$unit"
+ else
+ link_eq "$out_dir/local-fs.target.wants/$unit" "../$unit"
+ fi
+ fi
+ fi
+
+ if [[ "${passno:=0}" -ne 0 ]]; then
+ # Generate systemd-fsck@.service dependencies, if applicable
+ if in_initrd && [[ "$where" == / || "$where" == /usr ]]; then
+ continue
+ fi
+
+ if [[ "$where" == / ]]; then
+ link_endswith "$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service"
+ else
+ service="$(systemd-escape --template=systemd-fsck@.service --path "$what")"
+ grep -qE "^After=$service$" "$out_dir/$unit"
+ if [[ "$where" == /usr ]]; then
+ grep -qE "^Wants=$service$" "$out_dir/$unit"
+ else
+ grep -qE "^Requires=$service$" "$out_dir/$unit"
+ fi
+ fi
+ fi
+
+ # Check various x-systemd options
+ #
+ # First, split them into an array to make splitting them even further
+ # easier
+ IFS="," read -ra split_options <<< "$opts"
+ # and process them one by one.
+ #
+ # Note: the "machinery" below might (and probably does) miss some
+ # combinations of supported options, so tread carefully
+ for opt in "${split_options[@]}"; do
+ if [[ "$opt" =~ ^x-systemd.requires= ]]; then
+ service="$(opt_get_arg "$opt")"
+ grep -qE "^Requires=$service$" "$out_dir/$unit"
+ grep -qE "^After=$service$" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd.before= ]]; then
+ service="$(opt_get_arg "$opt")"
+ grep -qE "^Before=$service$" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd.after= ]]; then
+ service="$(opt_get_arg "$opt")"
+ grep -qE "^After=$service$" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd.wanted-by= ]]; then
+ service="$(opt_get_arg "$opt")"
+ if [[ "$where" == / ]]; then
+ # This option is ignored for rootfs mounts
+ (! link_eq "$out_dir/$service.wants/$unit" "../$unit")
+ else
+ link_eq "$out_dir/$service.wants/$unit" "../$unit"
+ fi
+ elif [[ "$opt" =~ ^x-systemd.required-by= ]]; then
+ service="$(opt_get_arg "$opt")"
+ if [[ "$where" == / ]]; then
+ # This option is ignored for rootfs mounts
+ (! link_eq "$out_dir/$service.requires/$unit" "../$unit")
+ else
+ link_eq "$out_dir/$service.requires/$unit" "../$unit"
+ fi
+ elif [[ "$opt" =~ ^x-systemd.requires-mounts-for= ]]; then
+ arg="$(opt_get_arg "$opt")"
+ grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit"
+ elif [[ "$opt" == x-systemd.device-bound ]]; then
+ # This is implied for fstab mounts
+ :
+ elif [[ "$opt" == x-systemd.automount ]]; then
+ # The $unit should have an accompanying automount unit
+ supp="$(systemd-escape --suffix=automount --path "$where")"
+ test -e "$out_dir/$supp"
+ link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp"
+ elif [[ "$opt" =~ ^x-systemd.idle-timeout= ]]; then
+ # The timeout applies to the automount unit, not the original
+ # mount one
+ arg="$(opt_get_arg "$opt")"
+ supp="$(systemd-escape --suffix=automount --path "$where")"
+ grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp"
+ elif [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+ arg="$(opt_get_arg "$opt")"
+ device="$(systemd-escape --suffix=device --path "$what")"
+ grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf"
+ elif [[ "$opt" == x-systemd.makefs ]]; then
+ service="$(systemd-escape --template=systemd-makefs@.service --path "$what")"
+ test -e "$out_dir/$service"
+ link_eq "$out_dir/${unit}.requires/$service" "../$service"
+ elif [[ "$opt" == x-systemd.rw-only ]]; then
+ grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit"
+ elif [[ "$opt" == x-systemd.growfs ]]; then
+ service="$(systemd-escape --template=systemd-growfs@.service --path "$where")"
+ link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service"
+ elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then
+ # We "convert" nfs bg mounts to fg, so we can do the job-control
+ # ourselves
+ grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit"
+ grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit"
+ elif [[ "$opt" =~ ^x-systemd\. ]]; then
+ echo >&2 "Unhandled mount option: $opt"
+ exit 1
+ fi
+ done
+ done
+}
+
+# TODO
+# - kernel arguments
+
+: "fstab-generator: regular"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Skip the rest when running in a container, as it makes little sense to check
+# initrd-related stuff there and fstab-generator might have a bit strange
+# behavior during certain tests, like https://github.com/systemd/systemd/issues/27156
+if in_container; then
+ echo "Running in a container, skipping the rest of the fstab-generator tests..."
+ exit 0
+fi
+
+# In this mode we treat the entries as "regular" ones
+: "fstab-generator: initrd - initrd fstab"
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null check_fstab_mount_units FSTAB_GENERAL "$OUT_DIR"
+
+# In this mode we prefix the mount target with /sysroot and ignore all mounts
+# that don't have the x-initrd.mount flag
+: "fstab-generator: initrd - host fstab"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Check the default stuff that we (almost) always create in initrd
+: "fstab-generator: initrd default"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+test -e "$OUT_DIR/sysroot.mount"
+test -e "$OUT_DIR/systemd-fsck-root.service"
+link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+
+: "fstab-generator: run as systemd-sysroot-fstab-check in initrd"
+ln -svf "$GENERATOR_BIN" /tmp/systemd-sysroot-fstab-check
+(! /tmp/systemd-sysroot-fstab-check foo)
+(! SYSTEMD_IN_INITRD=0 /tmp/systemd-sysroot-fstab-check)
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB="$FSTAB" /tmp/systemd-sysroot-fstab-check
+
+: "fstab-generator: duplicate"
+printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB"
+cat "$FSTAB"
+(! SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR")
+
+: "fstab-generator: invalid"
+printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB"
+cat "$FSTAB"
+# Don't care about the exit code here
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" || :
+# No mounts should get created here
+[[ "$(find "$OUT_DIR" -name "*.mount" | wc -l)" -eq 0 ]]
+
+: "fstab-generator: kernel args - fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+
+: "fstab-generator: kernel args - rd.fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-81-GENERATORS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+systemctl log-level debug
+
+for script in "${0%.sh}".*.sh; do
+ echo "Running $script"
+ "./$script"
+done
+
+touch /testok
+rm /failed
['systemd-reboot.service', ''],
['systemd-rfkill.socket', 'ENABLE_RFKILL'],
['systemd-sysext.service', 'ENABLE_SYSEXT'],
+ ['systemd-confext.service', 'ENABLE_SYSEXT'],
['systemd-sysupdate.timer', 'ENABLE_SYSUPDATE'],
['systemd-sysupdate-reboot.timer', 'ENABLE_SYSUPDATE'],
['systemd-sysusers.service', 'ENABLE_SYSUSERS',
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Merge System Configuration Images into /etc/
+Documentation=man:systemd-confext.service(8)
+
+ConditionCapability=CAP_SYS_ADMIN
+ConditionDirectoryNotEmpty=|/run/confexts
+ConditionDirectoryNotEmpty=|/var/lib/confexts
+ConditionDirectoryNotEmpty=|/usr/local/lib/confexts
+ConditionDirectoryNotEmpty=|/usr/lib/confexts
+
+DefaultDependencies=no
+After=local-fs.target
+Before=sysinit.target systemd-tmpfiles-setup.service
+Conflicts=shutdown.target initrd-switch-root.target
+Before=shutdown.target initrd-switch-root.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=systemd-confext refresh
+ExecReload=systemd-confext refresh
+ExecStop=systemd-confext unmerge
+
+[Install]
+WantedBy=sysinit.target
Description=Process Core Dump Socket
Documentation=man:systemd-coredump(8)
DefaultDependencies=no
-Before=shutdown.target
+Before=shutdown.target systemd-sysctl.service
Conflicts=shutdown.target
[Socket]
ConditionDirectoryNotEmpty=|/etc/extensions
ConditionDirectoryNotEmpty=|/run/extensions
ConditionDirectoryNotEmpty=|/var/lib/extensions
-ConditionDirectoryNotEmpty=|/usr/local/lib/extensions
-ConditionDirectoryNotEmpty=|/usr/lib/extensions
DefaultDependencies=no
After=local-fs.target
[Service]
Type=oneshot
RemainAfterExit=yes
-ExecStart=systemd-sysext merge
+ExecStart=systemd-sysext refresh
+ExecReload=systemd-sysext refresh
ExecStop=systemd-sysext unmerge
[Install]