- hwdb.d/**/*
journal:
- src/journal/*
+ - src/libsystemd/sd-journal/*
journal-remote:
- src/journal-remote/*
meson:
"--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
name: Differential ShellCheck
on:
+ push:
+ branches:
+ - main
pull_request:
branches:
- main
permissions:
security-events: write
- pull-requests: write
steps:
- name: Repository checkout
fetch-depth: 0
- name: Differential ShellCheck
- uses: redhat-plumbers-in-action/differential-shellcheck@f3cd08fcf12680861615270b29494d2b87c3e1cc
+ uses: redhat-plumbers-in-action/differential-shellcheck@d24099b9f39ddee81dea31eb0e135e0a623cb2b8
with:
token: ${{ secrets.GITHUB_TOKEN }}
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*"
+
+permissions:
+ contents: read
+
+jobs:
+ release:
+ if: github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable'
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: write
+
+ steps:
+ - name: Release
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
+ 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:
- distro: ubuntu
release: jammy
- distro: fedora
- release: "37"
+ release: "38"
- distro: fedora
release: rawhide
- distro: opensuse
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - uses: systemd/mkosi@c1f1e9ab2fe89f21ebdb4984b676f9a489081a64
+ - uses: systemd/mkosi@54f80aa8f687ed4d1759f0a7329c64c3f0ff70ad
- name: Configure
run: |
- tee mkosi.default <<- EOF
+ tee mkosi.conf <<- EOF
[Distribution]
Distribution=${{ matrix.distro }}
Release=${{ matrix.release }}
systemd.journald.max_level_console=debug
# udev's debug log output is very verbose, so up it to info in CI.
udev.log_level=info
+
+ [Host]
+ ExtraSearchPaths=!*
EOF
- name: Generate secure boot key
run: mkosi genkey
- - name: Build ${{ matrix.distro }}
- run: mkosi
-
- name: Show ${{ matrix.distro }} image summary
run: mkosi summary
+ - name: Build ${{ matrix.distro }}
+ run: mkosi
+
- name: Boot ${{ matrix.distro }} systemd-nspawn
run: sudo mkosi boot
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/
[![CentOS CI - Arch (sanitizers)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/buildStatus/icon?subject=CentOS%20CI%20-%20Arch%20(sanitizers)&job=upstream-vagrant-archlinux-sanitizers)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/job/upstream-vagrant-archlinux-sanitizers/)<br/>
[![CentOS CI - Rawhide (SELinux)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/buildStatus/icon?subject=CentOS%20CI%20-%20Rawhide%20(SELinux)&job=upstream-vagrant-rawhide-selinux)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/view/Upstream/job/upstream-vagrant-rawhide-selinux/)<br/>
[![Fossies codespell report](https://fossies.org/linux/test/systemd-main.tar.gz/codespell.svg)](https://fossies.org/linux/test/systemd-main.tar.gz/codespell.html)</br>
+[![Weblate](https://translate.fedoraproject.org/widgets/systemd/-/master/svg-badge.svg)](https://translate.fedoraproject.org/engage/systemd/)</br>
[![Coverage Status](https://coveralls.io/repos/github/systemd/systemd/badge.svg?branch=main)](https://coveralls.io/github/systemd/systemd?branch=main)</br>
[![Packaging status](https://repology.org/badge/tiny-repos/systemd.svg)](https://repology.org/project/systemd/versions)</br>
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/systemd/systemd/badge)](https://api.securityscorecards.dev/projects/github.com/systemd/systemd)
* rework mount.c and swap.c to follow proper state enumeration/deserialization
semantics, like we do for device.c now
-* get rid of prefix_roota() and similar, only use chase_symlinks() and related
+* get rid of prefix_roota() and similar, only use chase() and related
calls instead.
* get rid of basename() and replace by path_extract_filename()
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 patterns can match arbitrary fields with expressions like
+ "*:foobar:*", to wildcard match both the start and the end of the string.
+ This only works safely for later extensions of the string if the strings
+ always end in a colon. This requires updating our udev rules, as well as
+ checking if the various hwdb files are fine with that.
+
* 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
+ way no one 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
* journald: also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive
"corrected" CLOCK_REALTIME information on display from that and the timestamp
- info of the newest entry of the specificy boot (as identified by the boot
+ info of the newest entry of the specific boot (as identified by the boot
ID). This way, if a system comes up without a valid clock but acquires a
better clock later, we can "fix" older entry timestamps on display, by
calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does
userspace to allow ordering boots (for example in journalctl). The counter
would be monotonically increased on every boot.
-* systemd-sysext: for sysext DDIs picked up via EFI stub, set much stricter
- image policy by default
-
* pam_systemd_home: add module parameter to control whether to only accept
only password or only pcks11/fido2 auth, and then use this to hook nicely
into two of the three PAM stacks gdm provides.
* 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
* Add support for extra verity configuration options to systemd-repart (FEC,
hash type, etc)
-* chase_symlinks(): take inspiration from path_extract_filename() and return
+* chase(): take inspiration from path_extract_filename() and return
O_DIRECTORY if input path contains trailing slash.
-* chase_symlinks(): refuse resolution if trailing slash is specified on input,
+* chase(): refuse resolution if trailing slash is specified on input,
but final node is not a directory
-* chase_symlinks(): 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_symlinks() 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
virtio-fs.
* for vendor-built signed initrds:
- - make sysext run in the initrd
- - sysext should pick up sysext images from /.extra/ in the initrd, and insist
- on verification if in secureboot mode
- kernel-install should be able to install pre-built unified kernel images in
type #2 drop-in dir in the ESP.
- kernel-install should be able install encrypted creds automatically for
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.
CapabilityQuintet we already have. (This likely allows us to drop libcap
dep in the base OS image)
-* sysext: automatically activate sysext images dropped in via new sd-stub
- sysext pickup logic. (must insist on verity + signature on those though)
-
* add concept for "exitrd" as inverse of "initrd", that we can transition to at
shutdown, and has similar security semantics. This should then take the place
of dracut's shutdown logic. Should probably support sysexts too. Care needs
keys of /etc/crypttab. That way people can store/provide the roothash
externally and provide to us on demand only.
-* add high-level lockdown level for GPT dissection logic: e.g. an enum that can
- be ANY (to mount anything), TRUSTED (to require that /usr is on signed
- verity, but rest doesn't matter), LOCKEDDOWN (to require that everything is
- on signed verity, except for ESP), SUPERLOCKDOWN (like LOCKEDDOWN but ESP not
- allowed). And then maybe some flavours of that that declare what is expected
- from home/srv/var… Then, add a new cmdline flag to all tools that parse such
- images, to configure this. Also, add a kernel cmdline option for this, to be
- honoured by the gpt auto generator.
-
- Alternative idea: add "systemd.gpt_auto_policy=rhvs" to allow gpt-auto to
- only mount root dir, /home/ dir, /var/ and /srv/, but nothing else. And then
- minor extension to this, insisting on encryption, for example
- "systemd.gpt_auto_policy=r+v+h" to require encryption for root and var but not
- for /home/, and similar. Similar add --image-dissect-policy= to tools that
- take --image= that take the same short string.
-
* we probably should extend the root verity hash of the root fs into some PCR
on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
it into PCR 12); Similar: we probably should extend the LUKS volume key of
(i.e. sysext, root verity) from those inherently local (i.e. encryption key),
which is useful if they shall be signed separately.
-* add a "policy" to the dissection logic. i.e. a bit mask what is OK to mount,
- what must be read-only, what requires encryption, and what requires
- authentication.
-
* in uefi stub: query firmware regarding which PCR banks are being used, store
that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify
that the selected PCRs actually are used by firmware.
* 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
documented in the pivot_root(2) man page, so that we can drop the /oldroot
temporary dir.
-* special case some calls of chase_symlinks() to use openat2() internally, so
+* special case some calls of chase() to use openat2() internally, so
that the kernel does what we otherwise do.
-* add a new flag to chase_symlinks() that stops chasing once the first missing
+* add a new flag to chase() that stops chasing once the first missing
component is found and then allows the caller to create the rest.
* make use of new glibc 2.32 APIs sigabbrev_np() and strerrorname_np().
* 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
* maybe rework get_user_creds() to query the user database if $SHELL is used
for root, but only then.
-* be stricter with fds we receive for the fdstore: close them asynchronously
-
* calenderspec: add support for week numbers and day numbers within a
year. This would allow us to define "bi-weekly" triggers safely.
* 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_symlinks() 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.
effect on the regular file. If in doubt consider turning off `O_NONBLOCK`
again after opening.
+- These days we generally prefer `openat()`-style file APIs, i.e. APIs that
+ accept a combination of file descriptor and path string, and where the path
+ (if not absolute) is considered relative to the specified file
+ descriptor. When implementing library calls in similar style, please make
+ sure to imply `AT_EMPTY_PATH` if an empty or `NULL` path argument is
+ specified (and convert that latter to an empty string). This differs from the
+ underlying kernel semantics, where `AT_EMPTY_PATH` must always be specified
+ explicitly, and `NULL` is not acepted as path.
+
## Command Line
- If you parse a command line, and want to store the parsed parameters in
--- /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
+receives 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
+```
encrypt and decrypt/authenticate credentials. Example:
```sh
-systemd-creds encrypt plaintext.txt ciphertext.cred
+systemd-creds encrypt --name=foobar plaintext.txt ciphertext.cred
shred -u plaintext.txt
systemd-run -P --wait -p LoadCredentialEncrypted=foobar:$(pwd)/ciphertext.cred systemd-creds cat foobar
```
* `$SYSTEMD_OS_RELEASE` — if set, use this path instead of `/etc/os-release` or
`/usr/lib/os-release`. When operating under some root (e.g. `systemctl
- --root=…`), the path is taken relative to the outside root. Only useful for
- debugging.
+ --root=…`), the path is prefixed with the root. Only useful for debugging.
* `$SYSTEMD_FSTAB` — if set, use this path instead of `/etc/fstab`. Only useful
for debugging.
* `$SYSTEMD_UTF8=` — takes a boolean value, and overrides whether to generate
non-ASCII special glyphs at various places (i.e. "→" instead of
- "->"). Usually this is deterined automatically, based on $LC_CTYPE, but in
+ "->"). Usually this is determined automatically, based on `$LC_CTYPE`, but in
scenarios where locale definitions are not installed it might make sense to
override this check explicitly.
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`:
`systemd-sysusers`
-* `SOURCE_DATE_EPOCH` — if unset, the field of the date of last password change
+* `$SOURCE_DATE_EPOCH` — if unset, the field of the date of last password change
in `/etc/shadow` will be the number of days from Jan 1, 1970 00:00 UTC until
- today. If SOURCE_DATE_EPOCH is set to a valid UNIX epoch value in seconds,
+ today. If `$SOURCE_DATE_EPOCH` is set to a valid UNIX epoch value in seconds,
then the field will be the number of days until that time instead. This is to
support creating bit-by-bit reproducible system images by choosing a
reproducible value for the field of the date of last password change in
specified defaults to something like: `ext4:btrfs:xfs:vfat:erofs:squashfs`
* `$SYSTEMD_LOOP_DIRECT_IO` – takes a boolean, which controls whether to enable
- LO_FLAGS_DIRECT_IO (i.e. direct IO + asynchronous IO) on loopback block
+ `LO_FLAGS_DIRECT_IO` (i.e. direct IO + asynchronous IO) on loopback block
devices when opening them. Defaults to on, set this to "0" to disable this
feature.
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
$ cd systemd
$ git checkout -b <BRANCH> # where BRANCH is the name of the branch
$ vim src/core/main.c # or wherever you'd like to make your changes
-$ meson build # configure the build
+$ meson setup build -Danalyze=true -Drepart=true -Defi=true -Dbootloader=true -Dukify=true # configure the build
$ ninja -C build # build it locally, see if everything compiles fine
$ meson test -C build # run some simple regression tests
$ cd ..
## Debugging systemd with mkosi + vscode
To simplify debugging systemd when testing changes using mkosi, we're going to show how to attach
-[VSCode](https://code.visualstudio.com/)'s debugger to an instance of systemd running in a mkosi image
-(either using QEMU or systemd-nspawn).
+[VSCode](https://code.visualstudio.com/)'s debugger to an instance of systemd running in a mkosi image using
+QEMU.
To allow VSCode's debugger to attach to systemd running in a mkosi image, we have to make sure it can access
-the container/virtual machine spawned by mkosi where systemd is running. mkosi makes this possible via a
-handy SSH option that makes the generated image accessible via SSH when booted. Thus you must build
-the image with `mkosi --ssh`. The easiest way to set the
-option is to create a file 20-local.conf in mkosi.default.d/ (in the directory you ran mkosi in) and add
-the following contents:
+the virtual machine spawned by mkosi where systemd is running. mkosi makes this possible via a handy SSH
+option that makes the generated image accessible via SSH when booted. Thus you must build the image with
+`mkosi --ssh`. The easiest way to set the option is to create a file 20-local.conf in mkosi.conf.d/ (in the
+directory you ran mkosi in) and add the following contents:
```
[Host]
Ssh=yes
```
-Next, make sure systemd-networkd is running on the host system so that it can configure the network interface
-connecting the host system to the container/VM spawned by mkosi. Once systemd-networkd is running, you should
-be able to connect to a running mkosi image by executing `mkosi ssh` in the systemd repo directory.
+Also make sure that the SSH agent is running on your system and that you've added your SSH key to it with
+`ssh-add`.
+
+After rebuilding the image and booting it with `mkosi qemu`, you should now be able to connect to it by
+running `mkosi ssh` from the same directory in another terminal window.
Now we need to configure VSCode. First, make sure the C/C++ extension is installed. If you're already using
a different extension for code completion and other IDE features for C in VSCode, make sure to disable the
},
"MIMode": "gdb",
"sourceFileMap": {
- "/root/build/../src": {
+ "/work/build/../src": {
"editorPath": "${workspaceFolder}",
"useForBreakpoints": false
},
- "/root/build/*": {
+ "/work/build/*": {
"editorPath": "${workspaceFolder}/mkosi.builddir",
"useForBreakpoints": false
}
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)
back to the kernel as it happens, relieving the memory pressure before it
becomes too critical.
-The effects of memory pressure during runtime generaly are growing latencies
+The effects of memory pressure during runtime generally are growing latencies
during operation: when a program requires memory but the system is busy writing
out memory to (relatively slow) disks in order make some available, this
generally surfaces in scheduling latencies, and applications and services will
where each of them shall be able to release resources on memory pressure.
The `POLLPRI`/`POLLIN` conditions will be triggered every time memory pressure
-is detected, but not continously. It is thus safe to keep `poll()`-ing on the
-same file descriptor continously, and executing resource release operations
+is detected, but not continuously. It is thus safe to keep `poll()`-ing on the
+same file descriptor continuously, and executing resource release operations
whenever the file descriptor triggers without having to expect overloading the
process.
the unit files to enable such behaviour and add a local data directory to the
services copied onto the host.
+## Logging
+
+Several fields are autotmatically added to log messages generated by a portable
+service (or about a portable service, e.g.: start/stop logs from systemd).
+The `PORTABLE=` field will refer to the name of the portable image where the unit
+was loaded from. In case extensions are used, additionally there will be a
+`PORTABLE_ROOT=` field, referring to the name of image used as the base layer
+(i.e.: `RootImage=` or `RootDirectory=`), and one `PORTABLE_EXTENSION=` field per
+each extension image used.
+
+The `os-release` file from the portable image will be parsed and added as structured
+metadata to the journal log entries. The parsed fields will be the first ID field which
+is set from the set of `IMAGE_ID` and `ID` in this order of preference, and the first
+version field which is set from a set of `IMAGE_VERSION`, `VERSION_ID`, and `BUILD_ID`
+in this order of preference. The ID and version, if any, are concatenated with an
+underscore (`_`) as separator. If only either one is found, it will be used by itself.
+The field will be named `PORTABLE_NAME_AND_VERSION=`.
+
+In case extensions are used, the same fields in the same order are, but prefixed by
+`SYSEXT_`, are parsed from each `extension-release` file, and are appended to the
+journal as log entries, using `PORTABLE_EXTENSION_NAME_AND_VERSION=` as the field
+name. The base layer's field will be named `PORTABLE_ROOT_NAME_AND_VERSION=` instead
+of `PORTABLE_NAME_AND_VERSION=` in this case.
+
+For example, a portable service `app0` using two extensions `app0.raw` and
+`app1.raw` (with `SYSEXT_ID=app`, and `SYSEXT_VERSION_ID=` `0` and `1` in their
+respective extension-releases), and a base layer `base.raw` (with `VERSION_ID=10` and
+`ID=debian` in `os-release`), will create log entries with the following fields:
+
+```
+PORTABLE=app0.raw
+PORTABLE_ROOT=base.raw
+PORTABLE_ROOT_NAME_AND_VERSION=debian_10
+PORTABLE_EXTENSION=app0.raw
+PORTABLE_EXTENSION_NAME_AND_VERSION=app_0
+PORTABLE_EXTENSION=app1.raw
+PORTABLE_EXTENSION_NAME_AND_VERSION=app_1
+```
+
## Links
[`portablectl(1)`](https://www.freedesktop.org/software/systemd/man/portablectl.html)<br>
and directories stored in `/tmp/` and `/var/tmp/`. This means that files that
have neither been changed nor read within a specific time frame are
automatically removed in regular intervals. (This concept is not new to
-`systemd-tmpfiles` btw, it's inherited from previous subsystems such as
+`systemd-tmpfiles`, it's inherited from previous subsystems such as
`tmpwatch`.) By default files in `/tmp/` are cleaned up after 10 days, and
those in `/var/tmp` after 30 days.
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:
```
`sshAuthorizedKeys` → An array of strings, each listing an SSH public key that
is authorized to access the account. The strings should follow the same format
-as the lines in the traditional `~/.ssh/authorized_key` file.
+as the lines in the traditional `~/.ssh/authorized_keys` file.
`pkcs11EncryptedKey` → An array of objects. Each element of the array should be
an object consisting of three string fields: `uri` shall contain a PKCS#11
EVDEV_ABS_35=::12
EVDEV_ABS_36=::11
+# Lenovo Thinkpad L14 Gen1 (AMD)
+evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*pvrThinkPadL14Gen1**
+ EVDEV_ABS_00=::44
+ EVDEV_ABS_01=::50
+ EVDEV_ABS_35=::44
+ EVDEV_ABS_36=::50
+
# Lenovo T460
evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO*:pn*ThinkPad*T460:*
EVDEV_ABS_00=1266:5677:44
KEYBOARD_KEY_090010=f20 # Microphone mute button; should be micmute
# Lenovo 3000
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:pvr*
KEYBOARD_KEY_8b=switchvideomode # Fn+F7 video
KEYBOARD_KEY_96=wlan # Fn+F5 wireless
KEYBOARD_KEY_97=sleep # Fn+F4 suspend
KEYBOARD_KEY_b4=prog1
# Lenovo IdeaPad
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:pvr*
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pnS10-*:*
KEYBOARD_KEY_81=rfkill # does nothing in BIOS
KEYBOARD_KEY_83=display_off # BIOS toggles screen state
KEYBOARD_KEY_81=insert
# Thinkpad X200_Tablet
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:rvn*
KEYBOARD_KEY_5d=menu
KEYBOARD_KEY_63=fn
KEYBOARD_KEY_66=screenlock
KEYBOARD_KEY_6c=direction # rotate screen
# ThinkPad X6 Tablet
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X6*Tablet*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X6*Tablet*:rvn*
KEYBOARD_KEY_6c=direction # rotate
KEYBOARD_KEY_68=leftmeta # toolbox
KEYBOARD_KEY_6b=esc # escape
KEYBOARD_KEY_42=f23
KEYBOARD_KEY_43=f22
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*Y550*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*Y550*:pvr*
KEYBOARD_KEY_95=media
KEYBOARD_KEY_a3=play
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*U300s*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*U300s*:pvr*
KEYBOARD_KEY_f1=f21
KEYBOARD_KEY_ce=f20 # micmute
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:pvr*
KEYBOARD_KEY_a0=!mute
KEYBOARD_KEY_ae=!volumedown
KEYBOARD_KEY_b0=!volumeup
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPadFlex5*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPadFlex5*:pvr*
KEYBOARD_KEY_a0=!mute
KEYBOARD_KEY_ae=!volumedown
KEYBOARD_KEY_b0=!volumeup
KEYBOARD_KEY_b0=!volumeup
# Lenovo Y50-70
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:pvr*
KEYBOARD_KEY_f3=f21 # Fn+F6 (toggle touchpad)
# V480
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:pvr*
KEYBOARD_KEY_f1=f21
# Lenovo ThinkCentre M800z/M820z/M920z AIO machines
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pn*:*
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*:*
+ KEYBOARD_KEY_76=f21 # Toggle touchpad, sends meta+ctrl+toggle
KEYBOARD_KEY_91=config # MSIControl Center
KEYBOARD_KEY_a0=mute # Fn+F9
KEYBOARD_KEY_ae=volumedown # Fn+F7
# Keymaps MSI Prestige And MSI Modern FnKeys and Special keys
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*Prestige*:*
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*Modern*:*
- KEYBOARD_KEY_f1=f20 # Fn+F5 Micmute
- KEYBOARD_KEY_76=f21 # Fn+F4 Toggle touchpad, sends meta+ctrl+toggle
KEYBOARD_KEY_91=prog1 # Fn+F7 Creation Center, sometime F7
KEYBOARD_KEY_f2=prog2 # Fn+F12 Screen rotation
KEYBOARD_KEY_8d=prog3 # Fn+A Change True Color selections
sensor:modalias:acpi:BOSC0200*:dmi:*svn*ASUSTeK*:*pn*TP412UA:*
ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
+sensor:modalias:acpi:BOSC0200*:dmi:*svn*ASUSTeK*:pn*BR1100FKA:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, -1
+
#########################################
# Axxo
#########################################
sensor:modalias:acpi:BMI0160*:dmi:*:svnAYANEO:pn*NEXT*:*
ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
+#########################################
+# BMAX
+#########################################
+
+# BMAX Y13
+sensor:modalias:acpi:KIOX010A:*:dmi:*:svnAMI:*:skuH2M6:*
+ ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, -1, 0; 0, 0, -1
+ ACCEL_LOCATION=display
+
#########################################
# Chuwi
#########################################
ACCEL_MOUNT_MATRIX=1, 0, 0; 0, 1, 0; 0, 0, -1
ACCEL_LOCATION=base
+# Yoga Tablet 2 851F/L
+sensor:modalias:acpi:ACCL0001*:dmi:*:svnLENOVO:pn60072:pvr851*:*
+ ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
+
# IdeaPad Duet 3 10IGL5 (82AT)
sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82AT:*
ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
sensor:modalias:acpi:BMI0160*:dmi:*:rnONEXPLAYER:rvrV01:*
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, -1
+#########################################
+# Passion
+#########################################
+
+# Passion P612F
+sensor:modalias:acpi:MXC6655*:dmi:*:svnDefaultstring*:pnP612F:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
#########################################
# Peaq
#########################################
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
#########################################
# The lookup keys are composed in:
# 70-joystick.rules
#
-# Note: The format of the "joystick:" prefix match key is a
-# contract between the rules file and the hardware data, it might
-# change in later revisions to support more or better matches, it
-# is not necessarily expected to be a stable ABI.
-#
# Match string format:
# joystick:<bustype>:v<vid>p<pid>:name:<name>:
#
mouse:bluetooth:v05acp030d:name:*:*
MOUSE_DPI=1300@1000
+##########################################
+# Cherry
+##########################################
+
+# Cherry MW 2310
+mouse:usb:v1A81p1701:name:G-Tech Wireless Dongle Mouse:*
+ KEYBOARD_KEY_90005=back
+ KEYBOARD_KEY_90004=forward
+
##########################################
# Chicony
##########################################
# Logitech G502 X (Wired)
mouse:usb:v046dpc098:name:Logitech, Inc. G502 X LIGHTSPEED:*
# Logitech G502 X (Wireless)
-mouse:usb:v046dpc547:name:Logitech USB Receiver:*
+# The USB receiver is also used by other mice. See #27118.
+# If you want to enable the entry, please copy below to your custom hwdb file.
+#mouse:usb:v046dpc547:name:Logitech USB Receiver:*
MOUSE_DPI=1200@1000 *2400@1000 3200@1000 6400@1000
# Logitech G700 Laser Mouse (Wired)
# The lookup keys are composed in:
# 70-touchpad.rules
#
-# Note: The format of the "touchpad:" prefix match key is a
-# contract between the rules file and the hardware data, it might
-# change in later revisions to support more or better matches, it
-# is not necessarily expected to be a stable ABI.
-#
# Match string format:
# touchpad:<subsystem>:v<vid>p<pid>:name:<name>:
#
switch of the same name.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--install-source=</option></term>
<listitem><para>When installing binaries with <option>--root=</option> or
</listitem>
</varlistentry>
+ <varlistentry id='log-ratelimit-kmsg'>
+ <term><varname>$SYSTEMD_LOG_RATELIMIT_KMSG</varname></term>
+
+ <listitem><para id='log-ratelimit-kmsg-body'> Whether to ratelimit kmsg or not. Takes a boolean.
+ Defaults to <literal>true</literal>. If disabled, systemd will not ratelimit messages written to kmsg.
+ </para></listitem>
+ </varlistentry>
+
<varlistentry id='pager'>
<term><varname>$SYSTEMD_PAGER</varname></term>
switch of the same name.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>-q</option></term>
<term><option>--quiet</option></term>
<!ENTITY DEFAULT_DNS_OVER_TLS_MODE "{{DEFAULT_DNS_OVER_TLS_MODE_STR}}">
<!ENTITY DEFAULT_TIMEOUT "{{DEFAULT_TIMEOUT_SEC}} s">
<!ENTITY DEFAULT_USER_TIMEOUT "{{DEFAULT_USER_TIMEOUT_SEC}} s">
-<!ENTITY fedora_latest_version "36">
-<!ENTITY fedora_cloud_release "1.5">
+<!ENTITY fedora_latest_version "37">
+<!ENTITY fedora_cloud_release "1.7">
</row>
<row>
<entry><filename>/var/log/<replaceable>package</replaceable>/</filename></entry>
- <entry>Persistent log data of the package. As above, the package should make sure to create this directory if necessary, possibly using <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> or <varname>LogsDirectory=</varname> (see <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>), as it might be missing.</entry>
+ <entry>Persistent log data of the package. As above, the package should make sure to create this directory if necessary, possibly using <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> or <varname>LogsDirectory=</varname> (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>), as it might be missing.</entry>
</row>
<row>
<entry><filename>/var/spool/<replaceable>package</replaceable>/</filename></entry>
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"
--- /dev/null
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="iocost.conf" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>iocost.conf</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>iocost.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>iocost.conf</refname>
+ <refpurpose>Configuration files for the iocost solution manager</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para>
+ <filename>/etc/systemd/iocost.conf</filename>
+ <filename>/etc/systemd/iocost.conf.d/*.conf</filename>
+ </para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>This file configures the behavior of <literal>iocost</literal>, a tool mostly used by
+ <citerefentry><refentrytitle>systemd-udevd</refentrytitle><manvolnum>8</manvolnum></citerefentry> rules
+ to automatically apply I/O cost solutions to <filename>/sys/fs/cgroup/io.cost.*</filename>.</para>
+
+ <para>The qos and model values are calculated based on benchmarks collected on the
+ <ulink url="https://github.com/iocost-benchmark/iocost-benchmarks">iocost-benchmark</ulink>
+ project and turned into a set of solutions that go from most to least isolated.
+ Isolation allows the system to remain responsive in face of high I/O load.
+ Which solutions are available for a device can be queried from the udev metadata attached to it. By
+ default the naive solution is used, which provides the most bandwidth.</para>
+ </refsect1>
+
+ <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>All options are configured in the [IOCost] section:</para>
+
+ <variablelist class='config-directives'>
+
+ <varlistentry>
+ <term><varname>TargetSolution=</varname></term>
+
+ <listitem><para>Chooses which I/O cost solution (identified by named string) should be used
+ for the devices in this system. The known solutions can be queried from the udev metadata
+ attached to the devices. If a device does not have the specified solution, the first one
+ listed in <varname>IOCOST_SOLUTIONS</varname> is used instead.</para>
+
+ <para>E.g. <literal>TargetSolution=isolated-bandwidth</literal>.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <ulink url="https://github.com/iocost-benchmark/iocost-benchmarks">The iocost-benchmarks github project</ulink>,
+ <ulink url="https://github.com/facebookexperimental/resctl-demo/tree/main/resctl-bench/doc">The resctl-bench
+ documentation details how the values are obtained</ulink>
+ </para>
+ </refsect1>
+
+</refentry>
switch of the same name.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--namespace=<replaceable>NAMESPACE</replaceable></option></term>
<term><varname>systemd.log_level=</varname></term>
<term><varname>systemd.log_location=</varname></term>
<term><varname>systemd.log_color</varname></term>
+ <term><varname>systemd.log_ratelimit_kmsg</varname></term>
<term><varname>systemd.default_standard_output=</varname></term>
<term><varname>systemd.default_standard_error=</varname></term>
<term><varname>systemd.setenv=</varname></term>
<term><varname>rd.systemd.gpt_auto=</varname></term>
<listitem>
- <para>Configures whether GPT based partition auto-discovery
- shall be attempted. For details, see
+ <para>Configures whether GPT-based partition auto-discovery shall be attempted. For details, see
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>systemd.image_policy=</varname></term>
+ <term><varname>rd.systemd.image_policy=</varname></term>
+
+ <listitem><para>When GPT-based partition auto-discovery is used, configures the image dissection
+ policy string to apply, as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. For
+ details see
+ <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>systemd.default_timeout_start_sec=</varname></term>
is set in <filename>/etc/hostname</filename>. Note that this does not bar later runtime changes to
the hostname, it simply controls the initial hostname set during early boot.</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>systemd.tty.term.<replaceable>tty</replaceable>=</varname></term>
+ <term><varname>systemd.tty.rows.<replaceable>tty</replaceable>=</varname></term>
+ <term><varname>systemd.tty.columns.<replaceable>tty</replaceable>=</varname></term>
+
+ <listitem><para>These arguments allow configuring default values for <varname>$TERM</varname>,
+ <varname>TTYRows=</varname>, and <varname>TTYColumns=</varname> for tty
+ <replaceable>tty</replaceable>. The tty name should be specified without the
+ <filename>/dev/</filename> prefix (e.g. <literal>systemd.tty.rows.ttyS0=80</literal>).
+ </para></listitem>
+ </varlistentry>
</variablelist>
</refsect1>
</varlistentry>
</variablelist>
- <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> is set for plugins to select the initrd
- generator. This may be configured as <varname>initrd_generator=</varname> in
- <filename>install.conf</filename>, see below.</para>
+ <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.
Plugins may drop files in that directory, and they will be installed as part of the loader entry, based
<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>
--- /dev/null
+/* SPDX-License-Identifier: MIT-0 */
+
+/* Implements the LogControl1 interface as per specification:
+ * https://www.freedesktop.org/software/systemd/man/org.freedesktop.LogControl1.html
+ *
+ * Compile with 'cc logcontrol-example.c $(pkg-config --libs --cflags libsystemd)'
+ *
+ * To get and set properties via busctl:
+ *
+ * $ busctl --user get-property org.freedesktop.Example \
+ * /org/freedesktop/LogControl1 \
+ * org.freedesktop.LogControl1 \
+ * SyslogIdentifier
+ * s "example"
+ * $ busctl --user get-property org.freedesktop.Example \
+ * /org/freedesktop/LogControl1 \
+ * org.freedesktop.LogControl1 \
+ * LogTarget
+ * s "journal"
+ * $ busctl --user get-property org.freedesktop.Example \
+ * /org/freedesktop/LogControl1 \
+ * org.freedesktop.LogControl1 \
+ * LogLevel
+ * s "info"
+ * $ busctl --user set-property org.freedesktop.Example \
+ * /org/freedesktop/LogControl1 \
+ * org.freedesktop.LogControl1 \
+ * LogLevel \
+ * "s" debug
+ * $ busctl --user get-property org.freedesktop.Example \
+ * /org/freedesktop/LogControl1 \
+ * org.freedesktop.LogControl1 \
+ * LogLevel
+ * s "debug"
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-journal.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
+
+#define check(log_level, x) ({ \
+ int _r = (x); \
+ errno = _r < 0 ? -_r : 0; \
+ sd_journal_print((log_level), #x ": %m"); \
+ if (_r < 0) \
+ return EXIT_FAILURE; \
+ })
+
+typedef enum LogTarget {
+ LOG_TARGET_JOURNAL,
+ LOG_TARGET_KMSG,
+ LOG_TARGET_SYSLOG,
+ LOG_TARGET_CONSOLE,
+ _LOG_TARGET_MAX,
+} LogTarget;
+
+static const char* const log_target_table[_LOG_TARGET_MAX] = {
+ [LOG_TARGET_JOURNAL] = "journal",
+ [LOG_TARGET_KMSG] = "kmsg",
+ [LOG_TARGET_SYSLOG] = "syslog",
+ [LOG_TARGET_CONSOLE] = "console",
+};
+
+static const char* const log_level_table[LOG_DEBUG + 1] = {
+ [LOG_EMERG] = "emerg",
+ [LOG_ALERT] = "alert",
+ [LOG_CRIT] = "crit",
+ [LOG_ERR] = "err",
+ [LOG_WARNING] = "warning",
+ [LOG_NOTICE] = "notice",
+ [LOG_INFO] = "info",
+ [LOG_DEBUG] = "debug",
+};
+
+typedef struct object {
+ const char *syslog_identifier;
+ LogTarget log_target;
+ int log_level;
+} object;
+
+static int property_get(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ object *o = userdata;
+
+ if (strcmp(property, "LogLevel") == 0)
+ return sd_bus_message_append(reply, "s", log_level_table[o->log_level]);
+
+ if (strcmp(property, "LogTarget") == 0)
+ return sd_bus_message_append(reply, "s", log_target_table[o->log_target]);
+
+ if (strcmp(property, "SyslogIdentifier") == 0)
+ return sd_bus_message_append(reply, "s", o->syslog_identifier);
+
+ return sd_bus_error_setf(error,
+ SD_BUS_ERROR_UNKNOWN_PROPERTY,
+ "Unknown property '%s'",
+ property);
+}
+
+static int property_set(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ object *o = userdata;
+ const char *value;
+ int r;
+
+ r = sd_bus_message_read(message, "s", &value);
+ if (r < 0)
+ return r;
+
+ if (strcmp(property, "LogLevel") == 0) {
+ for (int i = 0; i < LOG_DEBUG + 1; i++)
+ if (strcmp(value, log_level_table[i]) == 0) {
+ o->log_level = i;
+ return 0;
+ }
+
+ return sd_bus_error_setf(error,
+ SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid value for LogLevel: '%s'",
+ value);
+ }
+
+ if (strcmp(property, "LogTarget") == 0) {
+ for (LogTarget i = 0; i < _LOG_TARGET_MAX; i++)
+ if (strcmp(value, log_target_table[i]) == 0) {
+ o->log_target = i;
+ return 0;
+ }
+
+ return sd_bus_error_setf(error,
+ SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid value for LogTarget: '%s'",
+ value);
+ }
+
+ return sd_bus_error_setf(error,
+ SD_BUS_ERROR_UNKNOWN_PROPERTY,
+ "Unknown property '%s'",
+ property);
+}
+
+/* https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html
+ */
+static const sd_bus_vtable vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_WRITABLE_PROPERTY(
+ "LogLevel", "s",
+ property_get, property_set,
+ 0,
+ 0),
+ SD_BUS_WRITABLE_PROPERTY(
+ "LogTarget", "s",
+ property_get, property_set,
+ 0,
+ 0),
+ SD_BUS_PROPERTY(
+ "SyslogIdentifier", "s",
+ property_get,
+ 0,
+ SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_VTABLE_END
+};
+
+int main(int argc, char **argv) {
+ /* The bus should be relinquished before the program terminates. The cleanup
+ * attribute allows us to do it nicely and cleanly whenever we exit the
+ * block.
+ */
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+ object o = {
+ .log_level = LOG_INFO,
+ .log_target = LOG_TARGET_JOURNAL,
+ .syslog_identifier = "example",
+ };
+
+ /* Acquire a connection to the bus, letting the library work out the details.
+ * https://www.freedesktop.org/software/systemd/man/sd_bus_default.html
+ */
+ check(o.log_level, sd_bus_default(&bus));
+
+ /* Publish an interface on the bus, specifying our well-known object access
+ * path and public interface name.
+ * https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html
+ * https://dbus.freedesktop.org/doc/dbus-tutorial.html
+ */
+ check(o.log_level, sd_bus_add_object_vtable(bus, NULL,
+ "/org/freedesktop/LogControl1",
+ "org.freedesktop.LogControl1",
+ vtable,
+ &o));
+
+ /* By default the service is assigned an ephemeral name. Also add a fixed
+ * one, so that clients know whom to call.
+ * https://www.freedesktop.org/software/systemd/man/sd_bus_request_name.html
+ */
+ check(o.log_level, sd_bus_request_name(bus, "org.freedesktop.Example", 0));
+
+ for (;;) {
+ /* https://www.freedesktop.org/software/systemd/man/sd_bus_wait.html
+ */
+ check(o.log_level, sd_bus_wait(bus, UINT64_MAX));
+ /* https://www.freedesktop.org/software/systemd/man/sd_bus_process.html
+ */
+ check(o.log_level, sd_bus_process(bus, NULL));
+ }
+
+ /* https://www.freedesktop.org/software/systemd/man/sd_bus_release_name.html
+ */
+ check(o.log_level, sd_bus_release_name(bus, "org.freedesktop.Example"));
+
+ return 0;
+}
<refnamediv>
<refname>networkctl</refname>
- <refpurpose>Query the status of network links</refpurpose>
+ <refpurpose>Query or modify the status of network links</refpurpose>
</refnamediv>
<refsynopsisdiv>
<refsect1>
<title>Description</title>
- <para><command>networkctl</command> may be used to introspect the
+ <para><command>networkctl</command> may be used to query or modify the
state of the network links as seen by
<command>systemd-networkd</command>. Please refer to
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details about <varname>BusName=</varname>.)</para>
</refsect1>
+ <refsect1>
+ <title>Example</title>
+
+ <example>
+ <title>Create a simple listener on the bus that implements LogControl1</title>
+
+ <programlisting><xi:include href="logcontrol-example.c" parse="text"/></programlisting>
+
+ <para>This creates a simple server on the bus. It implements the LogControl1 interface by providing
+ the required properties and allowing to set the writable ones. It logs at the configured log level using
+ <citerefentry><refentrytitle>sd_journal_print</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
+ </example>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
<para>
ReleaseControl();
SetType(in s type);
SetDisplay(in s display);
+ SetTTY(in h tty_fd);
TakeDevice(in u major,
in u minor,
out h fd,
<variablelist class="dbus-method" generated="True" extra-ref="SetDisplay()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="SetTTY()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="TakeDevice()"/>
<variablelist class="dbus-method" generated="True" extra-ref="ReleaseDevice()"/>
controller. If <function>TakeControl()</function> has not been called, this method will fail. The only argument
<varname>display</varname> is the new display name.</para>
+ <para><function>SetTTY()</function> allows the device name of the session to be changed. This is
+ useful if the tty device is only known after authentication. It can only be called by session's
+ current controller. If <function>TakeControl()</function> has not been called, this method will fail.
+ The only argument <varname>tty_fd</varname> is a file handle to the new tty device.</para>
+
<para><function>TakeDevice()</function> allows a session controller to get a file descriptor for a
specific device. Pass in the major and minor numbers of the character device and
<filename>systemd-logind</filename> will return a file descriptor for the device. Only a limited set of
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>
condition is a trigger condition, whether the condition is reversed, the right hand side of the
condition (e.g. the path in case of <varname>ConditionPathExists</varname>), and the status. The status
can be 0, in which case the condition hasn't been checked yet, a positive value, in which case the
- condition passed, or a negative value, in which case the condition failed. Currently only 0, +1, and -1
+ condition passed, or a negative value, in which case the condition is not met. Currently only 0, +1, and -1
are used, but additional values may be used in the future, retaining the meaning of
zero/positive/negative values.</para>
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);
readonly s Restart = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PIDFile = '...';
- @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NotifyAccess = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t RestartUSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly u RestartSteps = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly t RestartUSecMax = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly t RestartUSecNext = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t TimeoutStartUSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t TimeoutStopUSec = ...;
readonly u FileDescriptorStoreMax = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly u NFileDescriptorStore = ...;
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s FileDescriptorStorePreserve = '...';
readonly s StatusText = '...';
readonly i StatusErrno = ...;
readonly s Result = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s RootImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s MountImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s ExtensionImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
<!--property RestartUSec is not documented!-->
+ <!--property RestartSteps is not documented!-->
+
+ <!--property RestartUSecMax is not documented!-->
+
+ <!--property RestartUSecNext is not documented!-->
+
<!--property TimeoutStartFailureMode is not documented!-->
<!--property TimeoutStopFailureMode is not documented!-->
<!--property NFileDescriptorStore is not documented!-->
+ <!--property FileDescriptorStorePreserve is not documented!-->
+
<!--property StatusErrno is not documented!-->
<!--property ReloadResult is not documented!-->
<!--property IPCNamespacePath is not documented!-->
+ <!--property RootImagePolicy is not documented!-->
+
+ <!--property MountImagePolicy is not documented!-->
+
+ <!--property ExtensionImagePolicy is not documented!-->
+
<!--property KillMode is not documented!-->
<!--property KillSignal 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="RestartUSec"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RestartSteps"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecMax"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecNext"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="TimeoutStartUSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="TimeoutStopUSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="NFileDescriptorStore"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="FileDescriptorStorePreserve"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="StatusText"/>
<variablelist class="dbus-property" generated="True" extra-ref="StatusErrno"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
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>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s RootImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s MountImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s ExtensionImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
<!--property IPCNamespacePath is not documented!-->
+ <!--property RootImagePolicy is not documented!-->
+
+ <!--property MountImagePolicy is not documented!-->
+
+ <!--property ExtensionImagePolicy is not documented!-->
+
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s RootImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s MountImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s ExtensionImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
<!--property IPCNamespacePath is not documented!-->
+ <!--property RootImagePolicy is not documented!-->
+
+ <!--property MountImagePolicy is not documented!-->
+
+ <!--property ExtensionImagePolicy is not documented!-->
+
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s RootImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s MountImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s ExtensionImagePolicy = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
<!--property IPCNamespacePath is not documented!-->
+ <!--property RootImagePolicy is not documented!-->
+
+ <!--property MountImagePolicy is not documented!-->
+
+ <!--property ExtensionImagePolicy is not documented!-->
+
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
<varname>VERSION_ID=</varname> exists and matches. This ensures ABI/API compatibility between the
layers and prevents merging of an incompatible image in an overlay.</para>
+ <para>In order to identify the extension image itself, the same fields defined below can be added to the
+ <filename>extension-release</filename> file with a <varname>SYSEXT_</varname> prefix (to disambiguate
+ from fields used to match on the base image). E.g.: <varname>SYSEXT_ID=myext</varname>,
+ <varname>SYSEXT_VERSION_ID=1.2.3</varname>.</para>
+
<para>In the <filename>extension-release.<replaceable>IMAGE</replaceable></filename> filename, the
<replaceable>IMAGE</replaceable> part must exactly match the file name of the containing image with the
suffix removed. In case it is not possible to guarantee that an image file name is stable and doesn't
</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);
-}
['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
['hwdb', '7', [], 'ENABLE_HWDB'],
['integritytab', '5', [], 'HAVE_LIBCRYPTSETUP'],
+ ['iocost.conf', '5', [], ''],
['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'],
['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'],
['journalctl', '1', [], ''],
'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',
'sd_notifyf',
'sd_pid_notify',
'sd_pid_notify_with_fds',
- 'sd_pid_notifyf'],
+ 'sd_pid_notifyf',
+ 'sd_pid_notifyf_with_fds'],
''],
['sd_path_lookup', '3', ['sd_path_lookup_strv'], ''],
['sd_pid_get_owner_uid',
'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',
['systemd.environment-generator', '7', [], 'ENABLE_ENVIRONMENT_D'],
['systemd.exec', '5', [], ''],
['systemd.generator', '7', [], ''],
+ ['systemd.image-policy', '7', [], ''],
['systemd.journal-fields', '7', [], ''],
['systemd.kill', '5', [], ''],
['systemd.link', '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>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<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>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para>
</listitem>
</varlistentry>
</variablelist>
<title>Description</title>
<para><function>sd_bus_default()</function> acquires a bus
- connection object to the user bus when invoked in user context, or
- to the system bus otherwise. The connection object is associated
- with the calling thread. Each time the function is invoked from
- the same thread, the same object is returned, but its reference
- count is increased by one, as long as at least one reference is
- kept. When the last reference to the connection is dropped (using
- the
+ connection object to the user bus when invoked from within a user
+ slice (any session under <literal>user-*.slice</literal>, e.g.:
+ <literal>user@1000.service</literal>), or to the system bus
+ otherwise. The connection object is associated with the calling
+ thread. Each time the function is invoked from the same thread,
+ the same object is returned, but its reference count is increased
+ by one, as long as at least one reference is kept. When the last
+ reference to the connection is dropped (using the
<citerefentry><refentrytitle>sd_bus_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>
call), the connection is terminated. Note that the connection is
not automatically terminated when the associated thread ends. It
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection was created in a different process.</para></listitem>
+ <listitem><para>The bus connection was created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
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>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus of <parameter>m</parameter> was created in a different process.
+ <listitem><para>The bus of <parameter>m</parameter> was created in a different process, library or module instance.
</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection was created in a different process.</para>
+ <listitem><para>The bus connection was created in a different process, library or module instance.</para>
</listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus was created in a different process.</para></listitem>
+ <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection was created in a different process.</para></listitem>
+ <listitem><para>The bus connection was created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The bus connection has been created in a different process.</para></listitem>
+ <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop was created in a different process.</para></listitem>
+ <listitem><para>The event loop was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop object was created in a different process.</para></listitem>
+ <listitem><para>The event loop object was created in a different process, library or module instance.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The event loop has been created in a different process.</para></listitem>
+ <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry id='ECHILD'>
<term><constant>-ECHILD</constant></term>
- <listitem><para>The journal object was created in a different process.</para></listitem>
+ <listitem><para>The journal object was created in a different process, library or module instance.</para></listitem>
</varlistentry>
<varlistentry id='EADDRNOTAVAIL'>
<refname>sd_pid_notify</refname>
<refname>sd_pid_notifyf</refname>
<refname>sd_pid_notify_with_fds</refname>
+ <refname>sd_pid_notifyf_with_fds</refname>
<refname>sd_notify_barrier</refname>
<refpurpose>Notify service manager about start-up completion and other service status changes</refpurpose>
</refnamediv>
<paramdef>unsigned <parameter>n_fds</parameter></paramdef>
</funcprototype>
+ <funcprototype>
+ <funcdef>int <function>sd_pid_notifyf_with_fds</function></funcdef>
+ <paramdef>pid_t <parameter>pid</parameter></paramdef>
+ <paramdef>int <parameter>unset_environment</parameter></paramdef>
+ <paramdef>const int *<parameter>fds</parameter></paramdef>
+ <paramdef>size_t <parameter>n_fds</parameter></paramdef>
+ <paramdef>const char *<parameter>format</parameter></paramdef>
+ <paramdef>…</paramdef>
+ </funcprototype>
+
<funcprototype>
<funcdef>int <function>sd_notify_barrier</function></funcdef>
<paramdef>int <parameter>unset_environment</parameter></paramdef>
<refsect1>
<title>Description</title>
- <para><function>sd_notify()</function> may be called by a service
- to notify the service manager about state changes. It can be used
- to send arbitrary information, encoded in an
- environment-block-like string. Most importantly, it can be used for
- start-up completion notification.</para>
-
- <para>If the <parameter>unset_environment</parameter> parameter is
- non-zero, <function>sd_notify()</function> will unset the
- <varname>$NOTIFY_SOCKET</varname> environment variable before
- returning (regardless of whether the function call itself
- succeeded or not). Further calls to
- <function>sd_notify()</function> will then fail, but the variable
- is no longer inherited by child processes.</para>
-
- <para>The <parameter>state</parameter> parameter should contain a
- newline-separated list of variable assignments, similar in style
- to an environment block. A trailing newline is implied if none is
- specified. The string may contain any kind of variable
- assignments, but the following shall be considered
+
+ <para><function>sd_notify()</function> may be called by a service to notify the service manager about
+ state changes. It can be used to send arbitrary information, encoded in an environment-block-like
+ string. Most importantly, it can be used for start-up completion notification.</para>
+
+ <para>If the <parameter>unset_environment</parameter> parameter is non-zero,
+ <function>sd_notify()</function> will unset the <varname>$NOTIFY_SOCKET</varname> environment variable
+ before returning (regardless of whether the function call itself succeeded or not). Further calls to
+ <function>sd_notify()</function> will then fail, but the variable is no longer inherited by child
+ processes.</para>
+
+ <para>The <parameter>state</parameter> parameter should contain a newline-separated list of variable
+ assignments, similar in style to an environment block. A trailing newline is implied if none is
+ specified. The string may contain any kind of variable assignments, but the following shall be considered
well-known:</para>
<variablelist>
<varlistentry>
<term>STOPPING=1</term>
- <listitem><para>Tells the service manager that the service is
- beginning its shutdown. This is useful to allow the service
- manager to track the service's internal state, and present it
- to the user.</para></listitem>
+ <listitem><para>Tells the service manager that the service is beginning its shutdown. This is useful
+ to allow the service manager to track the service's internal state, and present it to the
+ user.</para></listitem>
</varlistentry>
<varlistentry>
<term>STATUS=…</term>
- <listitem><para>Passes a single-line UTF-8 status string back
- to the service manager that describes the service state. This
- is free-form and can be used for various purposes: general
- state feedback, fsck-like programs could pass completion
- percentages and failing programs could pass a human-readable
- error message. Example: <literal>STATUS=Completed 66% of file
- system check…</literal></para></listitem>
+ <listitem><para>Passes a single-line UTF-8 status string back to the service manager that describes
+ the service state. This is free-form and can be used for various purposes: general state feedback,
+ fsck-like programs could pass completion percentages and failing programs could pass a human-readable
+ error message. Example: <literal>STATUS=Completed 66% of file system
+ check…</literal></para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>NOTIFYACCESS=…</term>
+
+ <listitem><para>Reset the access to the service status notification socket during runtime, overriding
+ <varname>NotifyAccess=</varname> setting in the service unit file. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details, specifically <literal>NotifyAccess=</literal> for a list of accepted
+ values.</para></listitem>
</varlistentry>
<varlistentry>
<term>ERRNO=…</term>
- <listitem><para>If a service fails, the errno-style error
- code, formatted as string. Example: <literal>ERRNO=2</literal>
- for ENOENT.</para></listitem>
+ <listitem><para>If a service fails, the errno-style error code, formatted as string. Example:
+ <literal>ERRNO=2</literal> for ENOENT.</para></listitem>
</varlistentry>
<varlistentry>
<term>BUSERROR=…</term>
- <listitem><para>If a service fails, the D-Bus error-style
- error code. Example:
+ <listitem><para>If a service fails, the D-Bus error-style error code. Example:
<literal>BUSERROR=org.freedesktop.DBus.Error.TimedOut</literal></para></listitem>
</varlistentry>
+ <varlistentry>
+ <term>EXIT_STATUS=…</term>
+
+ <listitem><para>If a service exits, the return value of its <function>main()</function> function.
+ </para></listitem>
+ </varlistentry>
+
<varlistentry>
<term>MAINPID=…</term>
- <listitem><para>The main process ID (PID) of the service, in
- case the service manager did not fork off the process itself.
- Example: <literal>MAINPID=4711</literal></para></listitem>
+ <listitem><para>The main process ID (PID) of the service, in case the service manager did not fork
+ off the process itself. Example: <literal>MAINPID=4711</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG=1</term>
- <listitem><para>Tells the service manager to update the
- watchdog timestamp. This is the keep-alive ping that services
- need to issue in regular intervals if
- <varname>WatchdogSec=</varname> is enabled for it. See
+ <listitem><para>Tells the service manager to update the watchdog timestamp. This is the keep-alive
+ ping that services need to issue in regular intervals if <varname>WatchdogSec=</varname> is enabled
+ for it. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for information how to enable this functionality and
<citerefentry><refentrytitle>sd_watchdog_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- for the details of how the service can check whether the
- watchdog is enabled. </para></listitem>
+ for the details of how the service can check whether the watchdog is enabled. </para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG=trigger</term>
- <listitem><para>Tells the service manager that the service detected an internal error that should be handled by
- the configured watchdog options. This will trigger the same behaviour as if <varname>WatchdogSec=</varname> is
- enabled and the service did not send <literal>WATCHDOG=1</literal> in time. Note that
- <varname>WatchdogSec=</varname> does not need to be enabled for <literal>WATCHDOG=trigger</literal> to trigger
- the watchdog action. See
- <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
- information about the watchdog behavior. </para></listitem>
+ <listitem><para>Tells the service manager that the service detected an internal error that should be
+ handled by the configured watchdog options. This will trigger the same behaviour as if
+ <varname>WatchdogSec=</varname> is enabled and the service did not send <literal>WATCHDOG=1</literal>
+ in time. Note that <varname>WatchdogSec=</varname> does not need to be enabled for
+ <literal>WATCHDOG=trigger</literal> to trigger the watchdog action. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for information about the watchdog behavior. </para></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG_USEC=…</term>
- <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.
- Notice that this is not available when using <function>sd_event_set_watchdog()</function>
- or <function>sd_watchdog_enabled()</function>.
- Example : <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
+ <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime. Notice that this is not
+ available when using <function>sd_event_set_watchdog()</function> or
+ <function>sd_watchdog_enabled()</function>. Example :
+ <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>EXTEND_TIMEOUT_USEC=…</term>
<listitem><para>Tells the service manager to extend the startup, runtime or shutdown service timeout
- corresponding the current state. The value specified is a time in microseconds during which the service must
- send a new message. A service timeout will occur if the message isn't received, but only if the runtime of the
- current state is beyond the original maximum times of <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>,
- and <varname>TimeoutStopSec=</varname>.
- See <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ corresponding the current state. The value specified is a time in microseconds during which the
+ service must send a new message. A service timeout will occur if the message isn't received, but only
+ if the runtime of the current state is beyond the original maximum times of
+ <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>, and
+ <varname>TimeoutStopSec=</varname>. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for effects on the service timeouts.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDSTORE=1</term>
- <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this way will
- be maintained per-service by the service manager and will later be handed back using the usual file descriptor
- passing logic at the next invocation of the service (e.g. when it is restarted), see
- <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>. This is
- useful for implementing services that can restart after an explicit request or a crash without losing
- state. Any open sockets and other file descriptors which should not be closed during the restart may be stored
- this way. Application state can either be serialized to a file in <filename>/run/</filename>, or better, stored
- in a <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry> memory
- file descriptor. Note that the service manager will accept messages for a service only if its
+ <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this
+ way will be maintained per-service by the service manager and will later be handed back using the
+ usual file descriptor passing logic at the next invocation of the service (e.g. when it is
+ restarted), see
+ <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+ This is useful for implementing services that can restart after an explicit request or a crash
+ without losing state. Any open sockets and other file descriptors which should not be closed during
+ the restart may be stored this way. Application state can either be serialized to a file in
+ <filename>/run/</filename>, or better, stored in a
+ <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+ memory file descriptor. Note that the service manager will accept messages for a service only if its
<varname>FileDescriptorStoreMax=</varname> setting is non-zero (defaults to zero, see
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>). If
<varname>FDPOLL=0</varname> is not set and the file descriptors sent are pollable (see
- <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then any
- <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in their
- automatic removal from the store. Multiple arrays of file descriptors may be sent in separate messages, in
- which case the arrays are combined. Note that the service manager removes duplicate (pointing to the same
- object) file descriptors before passing them to the service. When a service is stopped, its file descriptor
- store is discarded and all file descriptors in it are closed. Use <function>sd_pid_notify_with_fds()</function>
- to send messages with <literal>FDSTORE=1</literal>, see below.</para></listitem>
+ <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then
+ any <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in
+ their automatic removal from the store. Multiple arrays of file descriptors may be sent in separate
+ messages, in which case the arrays are combined. Note that the service manager removes duplicate
+ (pointing to the same object) file descriptors before passing them to the service. When a service is
+ stopped, its file descriptor store is discarded and all file descriptors in it are closed. Use
+ <function>sd_pid_notify_with_fds()</function> to send messages with <literal>FDSTORE=1</literal>, see
+ below. The service manager will set the <varname>$FDSTORE</varname> environment variable for services
+ that have the file descriptor store enabled.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDSTOREREMOVE=1</term>
- <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be combined with
- <varname>FDNAME=</varname> to specify the name of the file descriptors to remove.</para></listitem>
+ <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be
+ combined with <varname>FDNAME=</varname> to specify the name of the file descriptors to
+ remove.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDNAME=…</term>
- <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the submitted
- file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for the file
- descriptors to remove. This name is passed to the service during activation, and may be queried using
+ <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the
+ submitted file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for
+ the file descriptors to remove. This name is passed to the service during activation, and may be
+ queried using
<citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
descriptors submitted without this field set, will implicitly get the name <literal>stored</literal>
- assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be assigned to
- all of them. In order to assign different names to submitted file descriptors, submit them in separate
- invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist of arbitrary ASCII
- characters except control characters or <literal>:</literal>. It may not be longer than 255 characters. If a
- submitted name does not follow these restrictions, it is ignored.</para></listitem>
+ assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be
+ assigned to all of them. In order to assign different names to submitted file descriptors, submit
+ them in separate invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist
+ of arbitrary ASCII characters except control characters or <literal>:</literal>. It may not be longer
+ than 255 characters. If a submitted name does not follow these restrictions, it is
+ ignored.</para></listitem>
</varlistentry>
<varlistentry>
<term>FDPOLL=0</term>
- <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the stored
- file descriptors regardless of whether or not they are pollable. As this option disables automatic cleanup
- of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to ensure proper manual cleanup.
- Use of this option is not generally recommended except for when automatic cleanup has unwanted behavior such
- as prematurely discarding file descriptors from the store.</para></listitem>
+ <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the
+ stored file descriptors regardless of whether or not they are pollable. As this option disables
+ automatic cleanup of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to
+ ensure proper manual cleanup. Use of this option is not generally recommended except for when
+ automatic cleanup has unwanted behavior such as prematurely discarding file descriptors from the
+ store.</para></listitem>
</varlistentry>
<varlistentry>
</varlistentry>
</variablelist>
- <para>It is recommended to prefix variable names that are not
- listed above with <varname>X_</varname> to avoid namespace
- clashes.</para>
-
- <para>Note that systemd will accept status data sent from a
- service only if the <varname>NotifyAccess=</varname> option is
- correctly set in the service definition file. See
- <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
- for details.</para>
-
- <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only if either
- the sending process is still around at the time PID 1 processes the message, or if the sending process is
- explicitly runtime-tracked by the service manager. The latter is the case if the service manager originally forked
- off the process, i.e. on all processes that match <varname>NotifyAccess=</varname><option>main</option> or
- <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit sends an
- <function>sd_notify()</function> message and immediately exits, the service manager might not be able to properly
- attribute the message to the unit, and thus will ignore it, even if
+ <para>It is recommended to prefix variable names that are not listed above with <varname>X_</varname> to
+ avoid namespace clashes.</para>
+
+ <para>Note that systemd will accept status data sent from a service only if the
+ <varname>NotifyAccess=</varname> option is correctly set in the service definition file. See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para>
+
+ <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only
+ if either the sending process is still around at the time PID 1 processes the message, or if the sending
+ process is explicitly runtime-tracked by the service manager. The latter is the case if the service
+ manager originally forked off the process, i.e. on all processes that match
+ <varname>NotifyAccess=</varname><option>main</option> or
+ <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit
+ sends an <function>sd_notify()</function> message and immediately exits, the service manager might not be
+ able to properly attribute the message to the unit, and thus will ignore it, even if
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
- <para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
- to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as a synchronization point
- and ensures all notifications sent before this call have been picked up by the service manager when it returns
- successfully. Use of <function>sd_notify_barrier()</function> is needed for clients which are not invoked by the
- service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
- unit.</para>
-
- <para><function>sd_notifyf()</function> is similar to
- <function>sd_notify()</function> but takes a
- <function>printf()</function>-like format string plus
- arguments.</para>
-
- <para><function>sd_pid_notify()</function> and
- <function>sd_pid_notifyf()</function> are similar to
- <function>sd_notify()</function> and
- <function>sd_notifyf()</function> but take a process ID (PID) to
- use as originating PID for the message as first argument. This is
- useful to send notification messages on behalf of other processes,
- provided the appropriate privileges are available. If the PID
- argument is specified as 0, the process ID of the calling process
- is used, in which case the calls are fully equivalent to
- <function>sd_notify()</function> and
- <function>sd_notifyf()</function>.</para>
-
- <para><function>sd_pid_notify_with_fds()</function> is similar to
- <function>sd_pid_notify()</function> but takes an additional array
- of file descriptors. These file descriptors are sent along the
- notification message to the service manager. This is particularly
- useful for sending <literal>FDSTORE=1</literal> messages, as
- described above. The additional arguments are a pointer to the
- file descriptor array plus the number of file descriptors in the
- array. If the number of file descriptors is passed as 0, the call
- is fully equivalent to <function>sd_pid_notify()</function>, i.e.
- no file descriptors are passed. Note that sending file descriptors
- to the service manager on messages that do not expect them (i.e.
- without <literal>FDSTORE=1</literal>) they are immediately closed
- on reception.</para>
-
- <para><function>sd_notify_barrier()</function> allows the caller to
- synchronize against reception of previously sent notification messages
- and uses the <varname>BARRIER=1</varname> command. It takes a relative
- <varname>timeout</varname> value in microseconds which is passed to
+ <para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of
+ notifications to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as
+ a synchronization point and ensures all notifications sent before this call have been picked up by the
+ service manager when it returns successfully. Use of <function>sd_notify_barrier()</function> is needed
+ for clients which are not invoked by the service manager, otherwise this synchronization mechanism is
+ unnecessary for attribution of notifications to the unit.</para>
+
+ <para><function>sd_notifyf()</function> is similar to <function>sd_notify()</function> but takes a
+ <function>printf()</function>-like format string plus arguments.</para>
+
+ <para><function>sd_pid_notify()</function> and <function>sd_pid_notifyf()</function> are similar to
+ <function>sd_notify()</function> and <function>sd_notifyf()</function> but take a process ID (PID) to use
+ as originating PID for the message as first argument. This is useful to send notification messages on
+ behalf of other processes, provided the appropriate privileges are available. If the PID argument is
+ specified as 0, the process ID of the calling process is used, in which case the calls are fully
+ equivalent to <function>sd_notify()</function> and <function>sd_notifyf()</function>.</para>
+
+ <para><function>sd_pid_notify_with_fds()</function> is similar to <function>sd_pid_notify()</function>
+ but takes an additional array of file descriptors. These file descriptors are sent along the notification
+ message to the service manager. This is particularly useful for sending <literal>FDSTORE=1</literal>
+ messages, as described above. The additional arguments are a pointer to the file descriptor array plus
+ the number of file descriptors in the array. If the number of file descriptors is passed as 0, the call
+ is fully equivalent to <function>sd_pid_notify()</function>, i.e. no file descriptors are passed. Note
+ that file descriptors sent to the service manager on a message without <literal>FDSTORE=1</literal> are
+ immediately closed on reception.</para>
+
+ <para><function>sd_pid_notifyf_with_fds()</function> is a combination of
+ <function>sd_pid_notify_with_fds()</function> and <function>sd_notifyf()</function>, i.e. it accepts both
+ a PID and a set of file descriptors as input, and processes a format string to generate the state
+ string.</para>
+
+ <para><function>sd_notify_barrier()</function> allows the caller to synchronize against reception of
+ previously sent notification messages and uses the <varname>BARRIER=1</varname> command. It takes a
+ relative <varname>timeout</varname> value in microseconds which is passed to
<citerefentry><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum>
</citerefentry>. A value of UINT64_MAX is interpreted as infinite timeout.
</para>
<refsect1>
<title>Return Value</title>
- <para>On failure, these calls return a negative errno-style error code. If <varname>$NOTIFY_SOCKET</varname> was
- not set and hence no status message could be sent, 0 is returned. If the status was sent, these functions return a
- positive value. In order to support both service managers that implement this scheme and those which do not, it is
- generally recommended to ignore the return value of this call. Note that the return value simply indicates whether
- the notification message was enqueued properly, it does not reflect whether the message could be processed
+ <para>On failure, these calls return a negative errno-style error code. If
+ <varname>$NOTIFY_SOCKET</varname> was not set and hence no status message could be sent, 0 is
+ returned. If the status was sent, these functions return a positive value. In order to support both
+ service managers that implement this scheme and those which do not, it is generally recommended to ignore
+ the return value of this call. Note that the return value simply indicates whether the notification
+ message was enqueued properly, it does not reflect whether the message could be processed
successfully. Specifically, no error is returned when a file descriptor is attempted to be stored using
- <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file descriptors (see
- above).</para>
+ <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file
+ descriptors (see above).</para>
</refsect1>
<refsect1>
<xi:include href="libsystemd-pkgconfig.xml" xpointer="pkgconfig-text"/>
<xi:include href="threads-aware.xml" xpointer="getenv"/>
- <para>These functions send a single datagram with the
- state string as payload to the socket referenced in the
- <varname>$NOTIFY_SOCKET</varname> environment variable. If the
- first character of <varname>$NOTIFY_SOCKET</varname> is
- <literal>/</literal> or <literal>@</literal>, the string is understood
- as an <constant>AF_UNIX</constant> or Linux abstract namespace socket
- (respectively), and in both cases the datagram is accompanied by the
- process credentials of the sending service, using SCM_CREDENTIALS. If
- the string starts with <literal>vsock:</literal> then the string is
- understood as an <constant>AF_VSOCK</constant> address, which is useful
- for hypervisors/VMMs or other processes on the host to receive a
- notification when a virtual machine has finished booting. Note that in
- case the hypervisor does not support <constant>SOCK_DGRAM</constant>
- over <constant>AF_VSOCK</constant>, <constant>SOCK_SEQPACKET</constant>
- will be used instead. The address should be in the form:
- <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock,
- the CID is mandatory and cannot be <literal>VMADDR_CID_ANY</literal>.
- Note that PID1 will send the VSOCK packets from a privileged port
- (i.e.: lower than 1024), as an attempt to address concerns that unprivileged
- processes in the guest might try to send malicious notifications to the
- host, driving it to make destructive decisions based on them.</para>
+ <para>These functions send a single datagram with the state string as payload to the socket referenced in
+ the <varname>$NOTIFY_SOCKET</varname> environment variable. If the first character of
+ <varname>$NOTIFY_SOCKET</varname> is <literal>/</literal> or <literal>@</literal>, the string is
+ understood as an <constant>AF_UNIX</constant> or Linux abstract namespace socket (respectively), and in
+ both cases the datagram is accompanied by the process credentials of the sending service, using
+ SCM_CREDENTIALS. If the string starts with <literal>vsock:</literal> then the string is understood as an
+ <constant>AF_VSOCK</constant> address, which is useful for hypervisors/VMMs or other processes on the
+ host to receive a notification when a virtual machine has finished booting. Note that in case the
+ hypervisor does not support <constant>SOCK_DGRAM</constant> over <constant>AF_VSOCK</constant>,
+ <constant>SOCK_SEQPACKET</constant> will be used instead. The address should be in the form:
+ <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock, the CID is mandatory and cannot
+ be <literal>VMADDR_CID_ANY</literal>. Note that PID1 will send the VSOCK packets from a privileged port
+ (i.e.: lower than 1024), as an attempt to address concerns that unprivileged processes in the guest might
+ try to send malicious notifications to the host, driving it to make destructive decisions based on
+ them.</para>
</refsect1>
<refsect1>
<varlistentry>
<term><varname>$NOTIFY_SOCKET</varname></term>
- <listitem><para>Set by the service manager for supervised
- processes for status and start-up completion notification.
- This environment variable specifies the socket
- <function>sd_notify()</function> talks to. See above for
- details.</para></listitem>
+ <listitem><para>Set by the service manager for supervised processes for status and start-up
+ completion notification. This environment variable specifies the socket
+ <function>sd_notify()</function> talks to. See above for details.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<example>
<title>Start-up Notification</title>
- <para>When a service finished starting up, it might issue the
- following call to notify the service manager:</para>
+ <para>When a service finished starting up, it might issue the following call to notify the service
+ manager:</para>
<programlisting>sd_notify(0, "READY=1");</programlisting>
</example>
<example>
<title>Extended Start-up Notification</title>
- <para>A service could send the following after completing
- initialization:</para>
+ <para>A service could send the following after completing initialization:</para>
<programlisting>
sd_notifyf(0, "READY=1\n"
<example>
<title>Store a File Descriptor in the Service Manager</title>
- <para>To store an open file descriptor in the service manager,
- in order to continue operation after a service restart without
- losing state, use <literal>FDSTORE=1</literal>:</para>
+ <para>To store an open file descriptor in the service manager, in order to continue operation after a
+ service restart without losing state, use <literal>FDSTORE=1</literal>:</para>
<programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &fd, 1);</programlisting>
</example>
<example>
<title>Eliminating race conditions</title>
- <para>When the client sending the notifications is not spawned
- by the service manager, it may exit too quickly and the service
- manager may fail to attribute them correctly to the unit. To
- prevent such races, use <function>sd_notify_barrier()</function>
- to synchronize against reception of all notifications sent before
- this call is made.</para>
+ <para>When the client sending the notifications is not spawned by the service manager, it may exit too
+ quickly and the service manager may fail to attribute them correctly to the unit. To prevent such
+ races, use <function>sd_notify_barrier()</function> to synchronize against reception of all
+ notifications sent before this call is made.</para>
<programlisting>
sd_notify(0, "READY=1");
numerical signal numbers and the program will exit immediately.</para>
</listitem>
</varlistentry>
+
+ <varlistentry id='image-policy-open'>
+ <term><option>--image-policy=<replaceable>policy</replaceable></option></term>
+
+ <listitem><para>Takes an image policy string as argument, as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+ policy is enforced when operating on the disk image specified via <option>--image=</option>, see
+ above. If not specified defaults to the <literal>*</literal> policy, i.e. all recognized file systems
+ in the image are used.</para></listitem>
+ </varlistentry>
+
</variablelist>
<refsect1>
<para id="singular">This option is only available for system services, or for services running in per-user
- instances of the service manager when <varname>PrivateUsers=</varname> is enabled.</para>
+ instances of the service manager in which case <varname>PrivateUsers=</varname> is implicitly enabled
+ (requires unprivileged user namespaces support to be enabled in the kernel via the
+ <literal>kernel.unprivileged_userns_clone=</literal> sysctl).</para>
<para id="plural">These options are only available for system services, or for services running in per-user
- instances of the service manager when <varname>PrivateUsers=</varname> is enabled.</para>
+ instances of the service manager in which case <varname>PrivateUsers=</varname> is implicitly enabled
+ (requires unprivileged user namespaces support to be enabled in the kernel via the
+ <literal>kernel.unprivileged_userns_clone=</literal> sysctl).</para>
</refsect1>
<varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>,
<varname>LogsDirectory=</varname> and <varname>RuntimeDirectory=</varname>, see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
- for details. For timer units this may be used to clear out the persistent timestamp data if
+ for details. It may also be used to clear the file descriptor store as enabled via
+ <varname>FileDescriptorStoreMax=</varname>, see
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details. For timer units this may be used to clear out the persistent timestamp data if
<varname>Persistent=</varname> is used and <option>--what=state</option> is selected, see
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This
command only applies to units that use either of these settings. If <option>--what=</option> is
- not specified, both the cache and runtime data are removed (as these two types of data are
- generally redundant and reproducible on the next invocation of the unit).</para>
+ not specified, the cache and runtime data as well as the file descriptor store are removed (as
+ these three types of resources are generally redundant and reproducible on the next invocation of
+ the unit). Note that the specified units must be stopped to invoke this operation.</para>
</listitem>
</varlistentry>
<varlistentry>
<listitem>
<para>Select what type of per-unit resources to remove when the <command>clean</command> command is
- invoked, see below. Takes one of <constant>configuration</constant>, <constant>state</constant>,
- <constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant> to select the
- type of resource. This option may be specified more than once, in which case all specified resource
- types are removed. Also accepts the special value <constant>all</constant> as a shortcut for
- specifying all five resource types. If this option is not specified defaults to the combination of
- <constant>cache</constant> and <constant>runtime</constant>, i.e. the two kinds of resources that
- are generally considered to be redundant and can be reconstructed on next invocation.</para>
+ invoked, see above. Takes one of <constant>configuration</constant>, <constant>state</constant>,
+ <constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant>,
+ <constant>fdstore</constant> to select the type of resource. This option may be specified more than
+ once, in which case all specified resource types are removed. Also accepts the special value
+ <constant>all</constant> as a shortcut for specifying all six resource types. If this option is not
+ specified defaults to the combination of <constant>cache</constant>, <constant>runtime</constant>
+ and <constant>fdstore</constant>, i.e. the three kinds of resources that are generally considered
+ to be redundant and can be reconstructed on next invocation. Note that the explicit removal of the
+ <constant>fdstore</constant> resource type is only useful if the
+ <varname>FileDescriptorStorePreserve=</varname> option is enabled, since the file descriptor store
+ is otherwise cleaned automatically when the unit is stopped.</para>
</listitem>
</varlistentry>
switch of the same name.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--runtime</option></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>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">image-policy</arg>
+ <arg choice="plain" rep="repeat"><replaceable>POLICY</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>
+
+ <refsect2>
+ <title><command>systemd-analyze image-policy <optional><replaceable>POLICY</replaceable>…</optional></command></title>
+
+ <para>This command analyzes the specified image policy string, as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+ policy is normalized and simplified. For each currently defined partition identifier (as per the <ulink
+ url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
+ Partitions Specification</ulink> the effect of the image policy string is shown in tabular form.</para>
+
+ <example>
+ <title>Example Output</title>
+
+ <programlisting>$ systemd-analyze image-policy swap=encrypted:usr=read-only-on+verity:root=encrypted
+Analyzing policy: root=encrypted:usr=verity+read-only-on:swap=encrypted
+ Long form: root=encrypted:usr=verity+read-only-on:swap=encrypted:=unused+absent
+
+PARTITION MODE READ-ONLY GROWFS
+root encrypted - -
+usr verity yes -
+home ignore - -
+srv ignore - -
+esp ignore - -
+xbootldr ignore - -
+swap encrypted - -
+root-verity ignore - -
+usr-verity unprotected yes -
+root-verity-sig ignore - -
+usr-verity-sig ignore - -
+tmp ignore - -
+var ignore - -
+default ignore - -</programlisting>
+ </example>
</refsect2>
</refsect1>
operate on files inside the specified image path <replaceable>PATH</replaceable>.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--offline=<replaceable>BOOL</replaceable></option></term>
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>
<listitem><para>When specified with <command>encrypt</command> controls whether to show the encrypted
credential as <varname>SetCredentialEncrypted=</varname> setting that may be pasted directly into a
- unit file.</para></listitem>
+ unit file. Has effect only when used together with <option>--name=</option> and <literal>-</literal>
+ as the output file.</para></listitem>
</varlistentry>
<varlistentry>
<para>The tool supports only LUKS2 volumes, as it stores token meta-information in the LUKS2 JSON token
area, which is not available in other encryption formats.</para>
+
+ <refsect2>
+ <title>TPM2 PCRs and policies</title>
+
+ <para>PCRs allow binding of the encryption of secrets to specific software versions and system state,
+ so that the enrolled key is only accessible (may be "unsealed") if specific trusted software and/or
+ configuration is used. Such bindings may be created with the option <option>--tpm2-pcrs=</option>
+ described below.</para>
+
+ <para>Secrets may also be bound indirectly: a signed policy for a state of some combination of PCR
+ values is provided, and the secret is bound to the public part of the key used to sign this policy.
+ This means that the owner of a key can generate a sequence of signed policies, for specific software
+ versions and system states, and the secret can be decrypted as long as the machine state matches one of
+ those policies. For example, a vendor may provide such a policy for each kernel+initrd update, allowing
+ users to encrypt secrets so that they can be decrypted when running any kernel+initrd signed by the
+ vendor. Such bindings may be created with the options <option>--tpm2-public-key=</option>,
+ <option>--tpm2-public-key-pcrs=</option>, <option>--tpm2-signature=</option> described below.
+ </para>
+
+ <para>See <ulink url="https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/">Linux TPM
+ PCR Registry</ulink> for an authoritative list of PCRs and how they are updated. The table below
+ contains a quick reference, describing in particular the PCRs modified by systemd.</para>
+
+ <table>
+ <title>Well-known PCR Definitions</title>
+
+ <!-- See: https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/ -->
+ <!-- See: https://github.com/rhboot/shim/blob/main/README.tpm -->
+ <!-- See: https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html -->
+ <!-- See: https://sourceforge.net/p/linux-ima/wiki/Home/ -->
+ <!-- See: https://github.com/tianocore-docs/edk2-TrustedBootChain/blob/main/4_Other_Trusted_Boot_Chains.md -->
+ <!-- See: https://wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers -->
+
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="pcr" />
+ <colspec colname="name" />
+ <colspec colname="definition" />
+
+ <thead>
+ <row>
+ <entry>PCR</entry>
+ <entry>name</entry>
+ <entry>Explanation</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>0</entry>
+ <entry>platform-code</entry>
+ <entry>Core system firmware executable code; changes on firmware updates</entry>
+ </row>
+
+ <row>
+ <entry>1</entry>
+ <entry>platform-config</entry>
+ <entry>Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements</entry>
+ </row>
+
+ <row>
+ <entry>2</entry>
+ <entry>external-code</entry>
+ <entry>Extended or pluggable executable code; includes option ROMs on pluggable hardware</entry>
+ </row>
+
+ <row>
+ <entry>3</entry>
+ <entry>external-config</entry>
+ <entry>Extended or pluggable firmware data; includes information about pluggable hardware</entry>
+ </row>
+
+ <row>
+ <entry>4</entry>
+ <entry>boot-loader-code</entry>
+ <entry>Boot loader and additional drivers, PE binaries invoked by the boot loader; changes on boot loader updates. <citerefentry><refentrytitle>sd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures system extension images read from the ESP here too (see <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>).</entry>
+ </row>
+
+ <row>
+ <entry>5</entry>
+ <entry>boot-loader-config</entry>
+ <entry>GPT/Partition table; changes when the partitions are added, modified, or removed</entry>
+ </row>
+
+ <row>
+ <entry>7</entry>
+ <entry>secure-boot-policy</entry>
+ <entry>Secure Boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes.</entry>
+ </row>
+
+ <row>
+ <entry>9</entry>
+ <entry>kernel-initrd</entry>
+ <entry>The Linux kernel measures all initrds it receives into this PCR.</entry>
+ <!-- Strictly speaking only Linux >= 5.17 using the LOAD_FILE2 protocol, see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f046fff8bc4c4d8f8a478022e76e40b818f692df -->
+ </row>
+
+ <row>
+ <entry>10</entry>
+ <entry>ima</entry>
+ <entry>The IMA project measures its runtime state into this PCR.</entry>
+ </row>
+
+ <row>
+ <entry>11</entry>
+ <entry>kernel-boot</entry>
+ <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. <citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures boot phase strings into this PCR at various milestones of the boot process.</entry>
+ </row>
+
+ <row>
+ <entry>12</entry>
+ <entry>kernel-config</entry>
+ <entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR.</entry>
+ </row>
+
+ <row>
+ <entry>13</entry>
+ <entry>sysexts</entry>
+ <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> images it passes to the booted kernel into this PCR.</entry>
+ </row>
+
+ <row>
+ <entry>14</entry>
+ <entry>shim-policy</entry>
+ <entry>The shim project measures its "MOK" certificates and hashes into this PCR.</entry>
+ </row>
+
+ <row>
+ <entry>15</entry>
+ <entry>system-identity</entry>
+ <entry><citerefentry><refentrytitle>systemd-cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> optionally measures the volume key of activated LUKS volumes into this PCR. <citerefentry><refentrytitle>systemd-pcrmachine.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures the <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> into this PCR. <citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures mount points, file system UUIDs, labels, partition UUIDs of the root and <filename>/var/</filename> filesystems into this PCR.</entry>
+ </row>
+
+ <row>
+ <entry>16</entry>
+ <entry>debug</entry>
+ <entry>Debug</entry>
+ </row>
+
+ <row>
+ <entry>23</entry>
+ <entry>application-support</entry>
+ <entry>Application Support</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>In general, encrypted volumes would be bound to some combination of PCRs 7, 11, and 14 (if
+ shim/MOK is used). In order to allow firmware and OS version updates, it is typically not advisable to
+ use PCRs such as 0 and 2, since the program code they cover should already be covered indirectly
+ through the certificates measured into PCR 7. Validation through certificates hashes is typically
+ preferable over validation through direct measurements as it is less brittle in context of OS/firmware
+ updates: the measurements will change on every update, but signatures should remain unchanged. See the
+ <ulink url="https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/">Linux TPM PCR
+ Registry</ulink> for more discussion.</para>
+ </refsect2>
</refsect1>
<refsect1>
<varlistentry>
<term><option>--tpm2-pcrs=</option><arg rep="repeat">PCR</arg></term>
- <listitem><para>Configures the TPM2 PCRs (Platform Configuration Registers) to bind the enrollment
- requested via <option>--tpm2-device=</option> to. Takes a <literal>+</literal> separated list of
- numeric PCR indexes in the range 0…23. If not used, defaults to PCR 7 only. If an empty string is
- specified, binds the enrollment to no PCRs at all. PCRs allow binding the enrollment to specific
- software versions and system state, so that the enrolled unlocking key is only accessible (may be
- "unsealed") if specific trusted software and/or configuration is used.</para>
-
- <table>
- <title>Well-known PCR Definitions</title>
-
- <!-- See: https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/ -->
- <!-- See: https://github.com/rhboot/shim/blob/main/README.tpm -->
- <!-- See: https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html -->
- <!-- See: https://sourceforge.net/p/linux-ima/wiki/Home/ -->
- <!-- See: https://github.com/tianocore-docs/edk2-TrustedBootChain/blob/main/4_Other_Trusted_Boot_Chains.md -->
- <!-- See: https://wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers -->
-
- <tgroup cols='2' align='left' colsep='1' rowsep='1'>
- <colspec colname="pcr" />
- <colspec colname="definition" />
-
- <thead>
- <row>
- <entry>PCR</entry>
- <entry>Explanation</entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>0</entry>
- <entry>Core system firmware executable code; changes on firmware updates</entry>
- </row>
-
- <row>
- <entry>1</entry>
- <entry>Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements</entry>
- </row>
-
- <row>
- <entry>2</entry>
- <entry>Extended or pluggable executable code; includes option ROMs on pluggable hardware</entry>
- </row>
-
- <row>
- <entry>3</entry>
- <entry>Extended or pluggable firmware data; includes information about pluggable hardware</entry>
- </row>
-
- <row>
- <entry>4</entry>
- <entry>Boot loader and additional drivers; changes on boot loader updates. The shim project will measure the PE binary it chain loads into this PCR. If the Linux kernel is invoked as UEFI PE binary, it is measured here, too. <citerefentry><refentrytitle>sd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures system extension images read from the ESP here too (see <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>).</entry>
- </row>
-
- <row>
- <entry>5</entry>
- <entry>GPT/Partition table; changes when the partitions are added, modified or removed</entry>
- </row>
-
- <row>
- <entry>6</entry>
- <entry>Power state events; changes on system suspend/sleep</entry>
- </row>
-
- <row>
- <entry>7</entry>
- <entry>Secure Boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. The shim project will measure most of its (non-MOK) certificates and SBAT data into this PCR.</entry>
- </row>
-
- <!-- Grub measures all its commands and the kernel command line into PCR 8… -->
- <!-- Grub measures all files it reads (including kernel image, initrd, …) into PCR 9… -->
-
- <row>
- <entry>9</entry>
- <entry>The Linux kernel measures all initrds it receives into this PCR.</entry>
- <!-- Strictly speaking only Linux >= 5.17 using the LOAD_FILE2 protocol, see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f046fff8bc4c4d8f8a478022e76e40b818f692df -->
- </row>
-
- <row>
- <entry>10</entry>
- <entry>The IMA project measures its runtime state into this PCR.</entry>
- </row>
-
- <row>
- <entry>11</entry>
- <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initrd. <citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures boot phase strings into this PCR at various milestones of the boot process.</entry>
- </row>
-
- <row>
- <entry>12</entry>
- <entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if <command>systemd-boot</command> and <command>systemd-stub</command> are used in combination the command line might be measured twice!)</entry>
- </row>
-
- <row>
- <entry>13</entry>
- <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> images it loads and passed to the booted kernel into this PCR.</entry>
- </row>
-
- <row>
- <entry>14</entry>
- <entry>The shim project measures its "MOK" certificates and hashes into this PCR.</entry>
- </row>
-
- <row>
- <entry>15</entry>
- <entry><citerefentry><refentrytitle>systemd-cryptsetup</refentrytitle><manvolnum>7</manvolnum></citerefentry> optionally measures the volume key of activated LUKS volumes into this PCR.</entry>
- </row>
- </tbody>
- </tgroup>
- </table>
-
- <para>For most applications it should be sufficient to bind against PCR 7 (and possibly PCR 14, if
- shim/MOK is desired), as this includes measurements of the trusted certificates (and possibly hashes)
- that are used to validate all components of the boot process up to and including the OS kernel. In
- order to simplify firmware and OS version updates it's typically not advisable to include PCRs such
- as 0 and 2 in the binding of the enrollment, since the program code they cover should already be
- protected indirectly through the certificates measured into PCR 7. Validation through these
- certificates is typically preferable over validation through direct measurements as it is less
- brittle in context of OS/firmware updates: the measurements will change on every update, but code
- signatures likely will validate against pre-existing certificates.</para></listitem>
+ <listitem><para>Configures the TPM2 PCRs (Platform Configuration Registers) to bind to when
+ enrollment is requested via <option>--tpm2-device=</option>. Takes a list of PCR names or numeric
+ indices in the range 0…23. Multiple PCR indexes are separated by <literal>+</literal>. If not
+ specified, the default is to use PCR 7 only. If an empty string is specified, binds the enrollment to
+ no PCRs at all. See the table above for a list of available PCRs.</para>
+
+ <para>Example: <option>--tpm2-pcrs=boot-loader-code+platform-config+boot-loader-config</option>
+ specifies that PCR registers 4, 1, and 5 should be used.</para>
+ </listitem>
</varlistentry>
<varlistentry>
<option>--tpm2-public-key-pcrs=</option>: the former binds decryption to the current, specific PCR
values; the latter binds decryption to any set of PCR values for which a signature by the specified
public key can be provided. The latter is hence more useful in scenarios where software updates shell
- be possible without losing access to all previously encrypted LUKS2 volumes.</para>
+ be possible without losing access to all previously encrypted LUKS2 volumes. Like with
+ <option>--tpm2-pcrs=</option>, names defined in the table above can also be used to specify the
+ registers, for instance
+ <option>--tpm2-public-key-pcrs=boot-loader-code+system-identity</option>.</para>
- <para>The <option>--tpm2-signature=</option> option takes a path to a TPM2 PCR signature file
- as generated by the
+ <para>The <option>--tpm2-signature=</option> option takes a path to a TPM2 PCR signature file as
+ generated by the
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
- tool. If this is not specified explicitly a suitable signature file
+ tool. If this is not specified explicitly, a suitable signature file
<filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
- <filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and
- used. If a signature file is specified or found it is used to verify if the volume can be unlocked
- with it given the current PCR state, before the new slot is written to disk. This is intended as
- safety net to ensure that access to a volume is not lost if a public key is enrolled for which no
- valid signature for the current PCR state is available. If the supplied signature does not unlock the
+ <filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and used.
+ If a signature file is specified or found it is used to verify if the volume can be unlocked with it
+ given the current PCR state, before the new slot is written to disk. This is intended as safety net
+ to ensure that access to a volume is not lost if a public key is enrolled for which no valid
+ signature for the current PCR state is available. If the supplied signature does not unlock the
current PCR state and public key combination, no slot is enrolled and the operation will fail. If no
signature file is specified or found no such safety verification is done.</para></listitem>
</varlistentry>
<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>
+ <varlistentry>
+ <term><option>--validate</option></term>
+
+ <listitem><para>Validates the partition arrangement of a disk image (DDI), and ensures it matches the
+ image policy specified via <option>--image-policy=</option>, if one is specified. This parses the
+ partition table and probes the file systems in the image, but does not attempt to mount them (nor to
+ set up disk encryption/authentication via LUKS/Verity). It does this taking the configured image
+ dissection policy into account. Since this operation does not mount file systems, this command –
+ unlike all other commands implemented by this tool – requires no privileges other than the ability to
+ access the specified file. Prints "OK" and returns zero if the image appears to be in order and
+ matches the specified image dissection policy. Otherwise prints an error message and returns
+ non-zero.</para></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
<command>cfdisk /dev/loop/by-ref/quux</command>.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="json" />
<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>
<para><filename>systemd-fsck</filename> does not know any details
about specific filesystems, and simply executes file system
checkers specific to each filesystem type
- (<filename>/sbin/fsck.<replaceable>type</replaceable></filename>). These checkers will decide if
+ (<filename>fsck.<replaceable>type</replaceable></filename>). These checkers will decide if
the filesystem should actually be checked based on the time since
last check, number of mounts, unclean unmount, etc.</para>
<para><filename>systemd-fsck-root.service</filename> and <filename>systemd-fsck-usr.service</filename>
- will activate <filename>reboot.target</filename> if <filename>/sbin/fsck</filename> returns the "System
- should reboot" condition, or <filename>emergency.target</filename> if <filename>/sbin/fsck</filename>
+ will activate <filename>reboot.target</filename> if <filename>fsck</filename> returns the "System
+ should reboot" condition, or <filename>emergency.target</filename> if <filename>fsck</filename>
returns the "Filesystem errors left uncorrected" condition.</para>
<para><filename>systemd-fsck@.service</filename> will fail if
- <filename>/sbin/fsck</filename> returns with either "System should reboot"
+ <filename>fsck</filename> returns with either "System should reboot"
or "Filesystem errors left uncorrected" conditions. For filesystems
listed in <filename>/etc/fstab</filename> without <literal>nofail</literal>
or <literal>noauto</literal> options, <literal>local-fs.target</literal>
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>systemd.image_policy=</varname></term>
+ <term><varname>rd.systemd.image_policy=</varname></term>
+
+ <listitem><para>Takes an image dissection policy string as argument (as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>),
+ and allows enforcing a policy on dissection and use of the automatically discovered GPT partition
+ table entries.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>root=</varname></term>
<term><varname>rootfstype=</varname></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
tree.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--commit</option></term>
<listitem><para>Commit a transient machine ID to disk. This
<listitem><para>This option only has an effect in automount mode,
and controls whether the automount unit shall be bound to the backing device's lifetime. If set, the
- automount point will be removed automatically when the backing device vanishes. By default the automount point
+ automount unit will be stopped automatically when the backing device vanishes. By default the automount unit
stays around, and subsequent accesses will block until backing device is replugged. This option has no effect
in case of non-device mounts, such as network or virtual file system mounts.</para>
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>
together with <option>--directory=</option>, <option>--template=</option>.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--image-policy=<replaceable>policy</replaceable></option></term>
+
+ <listitem><para>Takes an image policy string as argument, as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+ policy is enforced when operating on the disk image specified via <option>--image=</option>, see
+ above. If not specified defaults to
+ <literal>root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent:home=encrypted+unprotected+absent:srv=encrypted+unprotected+absent:esp=unprotected+absent:xbootldr=unprotected+absent:tmp=encrypted+unprotected+absent:var=encrypted+unprotected+absent</literal>,
+ i.e. all recognized file systems in the image are used, but not the swap partition.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--oci-bundle=</option></term>
<varlistentry>
<term><option>--network-interface=</option></term>
- <listitem><para>Assign the specified network interface to the container. This will remove the
- specified interface from the calling namespace and place it in the container. When the container
- terminates, it is moved back to the calling namespace. Note that
- <option>--network-interface=</option> implies <option>--private-network</option>. This option may be
- used more than once to add multiple network interfaces to the container.</para>
+ <listitem><para>Assign the specified network interface to the container. Either takes a single
+ interface name, referencing the name on the host, or a colon-separated pair of interfaces, in which
+ case the first one references the name on the host, and the second one the name in the container.
+ When the container terminates, the interface is moved back to the calling namespace and renamed to
+ its original name. Note that <option>--network-interface=</option> implies
+ <option>--private-network</option>. This option may be used more than once to add multiple network
+ interfaces to the container.</para>
<para>Note that any network interface specified this way must already exist at the time the container
is started. If the container shall be started automatically at boot via a
<term><option>--network-macvlan=</option></term>
<listitem><para>Create a <literal>macvlan</literal> interface of the specified Ethernet network
- interface and add it to the container. A <literal>macvlan</literal> interface is a virtual interface
- that adds a second MAC address to an existing physical Ethernet link. The interface in the container
- will be named after the interface on the host, prefixed with <literal>mv-</literal>. Note that
+ interface and add it to the container. Either takes a single interface name, referencing the name
+ on the host, or a colon-separated pair of interfaces, in which case the first one references the name
+ on the host, and the second one the name in the container. A <literal>macvlan</literal> interface is
+ a virtual interface that adds a second MAC address to an existing physical Ethernet link. If the
+ container interface name is not defined, the interface in the container will be named after the
+ interface on the host, prefixed with <literal>mv-</literal>. Note that
<option>--network-macvlan=</option> implies <option>--private-network</option>. This option may be
used more than once to add multiple network interfaces to the container.</para>
<term><option>--network-ipvlan=</option></term>
<listitem><para>Create an <literal>ipvlan</literal> interface of the specified Ethernet network
- interface and add it to the container. An <literal>ipvlan</literal> interface is a virtual interface,
+ interface and add it to the container. Either takes a single interface name, referencing the name on
+ the host, or a colon-separated pair of interfaces, in which case the first one references the name
+ on the host, and the second one the name in the container. An <literal>ipvlan</literal> interface is
+ a virtual interface,
similar to a <literal>macvlan</literal> interface, which uses the same MAC address as the underlying
- interface. The interface in the container will be named after the interface on the host, prefixed
+ interface. If the container interface name is not defined, the interface in the container will be
+ named after the interface on the host, prefixed
with <literal>iv-</literal>. Note that <option>--network-ipvlan=</option> implies
<option>--private-network</option>. This option may be used more than once to add multiple network
interfaces to the container.</para>
<programlisting># dnf -y --releasever=&fedora_latest_version; --installroot=/var/lib/machines/f&fedora_latest_version; \
--repo=fedora --repo=updates --setopt=install_weak_deps=False install \
- passwd dnf fedora-release vim-minimal systemd systemd-networkd
+ passwd dnf fedora-release vim-minimal util-linux systemd systemd-networkd
# systemd-nspawn -bD /var/lib/machines/f&fedora_latest_version;</programlisting>
<para>This installs a minimal Fedora distribution into the
<refsect1>
<title>Description</title>
- <para><filename>systemd-poweroff.service</filename> is a system
- service that is pulled in by <filename>poweroff.target</filename> and
- is responsible for the actual system power-off operation. Similarly,
- <filename>systemd-halt.service</filename> is pulled in by
- <filename>halt.target</filename>,
- <filename>systemd-reboot.service</filename> by
- <filename>reboot.target</filename> and
- <filename>systemd-kexec.service</filename> by
- <filename>kexec.target</filename> to execute the respective
- actions.</para>
+ <para><filename>systemd-poweroff.service</filename> is a system service that is pulled in by
+ <filename>poweroff.target</filename> and is responsible for the actual system power-off
+ operation. Similarly, <filename>systemd-halt.service</filename> is pulled in by
+ <filename>halt.target</filename>, <filename>systemd-reboot.service</filename> by
+ <filename>reboot.target</filename> and <filename>systemd-kexec.service</filename> by
+ <filename>kexec.target</filename> to execute the respective actions.</para>
- <para>When these services are run, they ensure that PID 1 is
- replaced by the
- <filename>/usr/lib/systemd/systemd-shutdown</filename> tool which
- is then responsible for the actual shutdown. Before shutting down,
- this binary will try to unmount all remaining file systems,
- disable all remaining swap devices, detach all remaining storage
- devices and kill all remaining processes.</para>
+ <para>When these services are run, they ensure that PID 1 is replaced by the
+ <filename>/usr/lib/systemd/systemd-shutdown</filename> tool which is then responsible for the actual
+ shutdown. Before shutting down, this binary will try to unmount all remaining file systems (or at least
+ remount them read-only), disable all remaining swap devices, detach all remaining storage devices and
+ kill all remaining processes.</para>
- <para>It is necessary to have this code in a separate binary
- because otherwise rebooting after an upgrade might be broken — the
- running PID 1 could still depend on libraries which are not
- available any more, thus keeping the file system busy, which then
- cannot be re-mounted read-only.</para>
+ <para>It is necessary to have this code in a separate binary because otherwise rebooting after an upgrade
+ might be broken — the running PID 1 could still depend on libraries which are not available any more,
+ thus keeping the file system busy, which then cannot be re-mounted read-only.</para>
- <para>Immediately before executing the actual system
- power-off/halt/reboot/kexec <filename>systemd-shutdown</filename>
- will run all executables in
- <filename>/usr/lib/systemd/system-shutdown/</filename> and pass
- one arguments to them: either <literal>poweroff</literal>,
- <literal>halt</literal>, <literal>reboot</literal>, or
- <literal>kexec</literal>, depending on the chosen action. All
- executables in this directory are executed in parallel, and
- execution of the action is not continued before all executables
- finished.</para>
+ <para>Shortly before executing the actual system power-off/halt/reboot/kexec
+ <filename>systemd-shutdown</filename> will run all executables in
+ <filename>/usr/lib/systemd/system-shutdown/</filename> and pass one arguments to them: either
+ <literal>poweroff</literal>, <literal>halt</literal>, <literal>reboot</literal>, or
+ <literal>kexec</literal>, depending on the chosen action. All executables in this directory are executed
+ in parallel, and execution of the action is not continued before all executables finished. Note that
+ these executables are run <emphasis>after</emphasis> all services have been shut down, and after most
+ mounts have been detached (the root file system as well as <filename>/run/</filename> and various API
+ file systems are still around though). This means any programs dropped into this directory must be
+ prepared to run in such a limited execution environment and not rely on external services or hierarchies
+ such as <filename>/var/</filename> to be around (or writable).</para>
<para>Note that <filename>systemd-poweroff.service</filename> (and the related units) should never be
executed directly. Instead, trigger system shutdown with a command such as <literal>systemctl
<refsynopsisdiv>
<para><filename>systemd-random-seed.service</filename></para>
- <para><filename>/usr/lib/systemd/random-seed</filename></para>
+ <para><filename>/usr/lib/systemd/systemd-random-seed</filename></para>
</refsynopsisdiv>
<refsect1>
<option>--root=</option>, see above.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--seed=</option></term>
Consider using the <option>exec</option> service type (i.e. <option>--property=Type=exec</option>) to
ensure that <command>systemd-run</command> returns successfully only if the specified command line has
been successfully started.</para>
+
+ <para>After <command>systemd-run</command> passes the command to the service manager, the manager
+ performs variable expansion. This means that dollar characters (<literal>$</literal>) which should not be
+ expanded need to be escaped as <literal>$$</literal>. Expansion can also be disabled using
+ <varname>--expand-environment=no</varname>.</para>
</refsect1>
<refsect1>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--expand-environment=<replaceable>BOOL</replaceable></option></term>
+
+ <listitem><para>Expand environment variables in command arguments. If enabled (the default),
+ environment variables specified as <literal>${<replaceable>VARIABLE</replaceable>}</literal> will be
+ expanded in the same way as in commands specified via <varname>ExecStart=</varname> in units. With
+ <varname>--scope</varname>, this expansion is performed by <command>systemd-run</command> itself, and
+ in other cases by the service manager that spawns the command. Note that this is similar to, but not
+ the same as variable expansion in
+ <citerefentry project='man-pages'><refentrytitle>bash</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ and other shells.</para>
+
+ <para>See
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for a description of variable expansion. Disabling variable expansion is useful if the specified
+ command includes or may include a <literal>$</literal> sign.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-r</option></term>
<term><option>--remain-after-exit</option></term>
<programlisting>$ loginctl enable-linger</programlisting>
</example>
+ <example>
+ <title>Variable expansion by the manager</title>
+
+ <programlisting>$ systemd-run -t echo "<${INVOCATION_ID}>" '<${INVOCATION_ID}>'
+ <> <5d0149bfa2c34b79bccb13074001eb20>
+ </programlisting>
+
+ <para>The first argument is expanded by the shell (double quotes), but the second one is not expanded
+ by the shell (single quotes). <command>echo</command> is called with [<literal>/usr/bin/echo</literal>,
+ <literal>[]</literal>, <literal>[${INVOCATION_ID}]</literal>] as the argument array, and then
+ <command>systemd</command> generates <varname>${INVOCATION_ID}</varname> and substitutes it in the
+ command-line. This substitution could not be done on the client side, because the target ID that will
+ be set for the service isn't known before the call is made.</para>
+ </example>
+
+ <example>
+ <title>Variable expansion and output redirection using a shell</title>
+
+ <para>Variable expansion by <command>systemd</command> can be disabled with
+ <varname>--expand-environment=no</varname>.</para>
+
+ <para>Disabling variable expansion can be useful if the command to execute contains dollar characters
+ and escaping them would be inconvenient. For example, when a shell is used:</para>
+
+ <programlisting>$ systemd-run --expand-environment=no -t bash \
+ -c 'echo $SHELL $$ >/dev/stdout'
+/bin/bash 12345
+ </programlisting>
+
+ <para>The last argument is passed verbatim to the <command>bash</command> shell which is started by the
+ service unit. The shell expands <literal>$SHELL</literal> to the path of the shell, and
+ <literal>$$</literal> to its process number, and then those strings are passed to the
+ <command>echo</command> built-in and printed to standard output (which in this case is connected to the
+ calling terminal).</para>
+ </example>
+
<example>
<title>Return value</title>
<programlisting>$ systemd-run --user --wait true
$ systemd-run --user --wait -p SuccessExitStatus=11 bash -c 'exit 11'
-$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 bash -c 'kill -SIGUSR1 $$$$'</programlisting>
+$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no \
+ bash -c 'kill -SIGUSR1 $$'</programlisting>
<para>Those three invocations will succeed, i.e. terminate with an exit code of 0.</para>
</example>
<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
- <filename>.raw</filename> suffix are considered disk image based extension images.</para>
+ these search directories are considered directory based extension images; any files with the
+ <filename>.raw</filename> suffix are considered disk image based extension images. When invoked in the
+ initrd, the additional directory <filename>/.extra/sysext/</filename> is included in the directories that
+ are searched for extension images. Note however, that by default a tighter image policy applies to images
+ found there, though, see below. This directory is populated by
+ <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> with
+ extension images found in the system's EFI System Partition.</para>
<para>During boot OS extension images are activated automatically, if the
<filename>systemd-sysext.service</filename> is enabled. Note that this service runs only after 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. The merged hierarchy will be mounted with <literal>nosuid</literal> and
+ (if not disabled via <option>--noexec=false</option>) <literal>noexec</literal>.</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>
+
+ <varlistentry>
+ <term><option>--image-policy=<replaceable>policy</replaceable></option></term>
+
+ <listitem><para>Takes an image policy string as argument, as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+ policy is enforced when operating on system extension disk images. If not specified defaults to
+ <literal>root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent</literal>
+ for system extensions, i.e. only the root and <filename>/usr/</filename> file systems in the image
+ are used. For configuration extensions defaults to
+ <literal>root=verity+signed+encrypted+unprotected+absent</literal>. When run in the initrd and
+ operating on a system extension image stored in the <filename>/.extra/sysext/</filename> directory a
+ slightly stricter policy is used by default: <literal>root=signed+absent:usr=signed+absent</literal>,
+ see above for details.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--noexec=</option><replaceable>BOOL</replaceable></term>
+
+ <listitem><para>When merging configuration extensions into <filename>/etc/</filename> the
+ <literal>MS_NOEXEC</literal> mount flag is used by default. This option can be used to disable
+ it.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
- <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>
</para>
</refsect1>
<para><filename>systemd-system-update-generator</filename> is a
generator that automatically redirects the boot process to
<filename>system-update.target</filename>, if
- <filename>/system-update</filename> exists. This is required to
+ <filename>/system-update</filename> or <filename>/etc/system-update</filename> exists. This is required to
implement the logic explained in the
<citerefentry><refentrytitle>systemd.offline-updates</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
inside the specified disk image.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--instances-max=</option></term>
<term><option>-m</option></term>
switch of the same name.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--replace=<replaceable>PATH</replaceable></option></term>
<listitem><para>When this option is given, one or more positional arguments
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>
<para>Implies <option>-E</option>.</para></listitem>
</varlistentry>
+ <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
<varlistentry>
<term><option>--replace=<replaceable>PATH</replaceable></option></term>
<listitem><para>When this option is given, one or more positional arguments
records as acquired with APIs such as <citerefentry
project='man-pages'><refentrytitle>getpwnam</refentrytitle><manvolnum>1</manvolnum></citerefentry> to
JSON user/group records, thus hiding the differences between the services as much as
- possible. <constant>io.systemd.Dropin</constant> makes JSON user/group records from the aforementioned
+ possible. <constant>io.systemd.DropIn</constant> makes JSON user/group records from the aforementioned
drop-in directories available.</para>
</refsect1>
<term><varname>systemd.verity_root_options=</varname></term>
<listitem><para>Takes a comma-separated list of dm-verity options. Expects the following options
+ <option>superblock=<replaceable>BOOLEAN</replaceable></option>,
+ <option>format=<replaceable>NUMBER</replaceable></option>,
+ <option>data-block-size=<replaceable>BYTES</replaceable></option>,
+ <option>hash-block-size=<replaceable>BYTES</replaceable></option>,
+ <option>data-blocks=<replaceable>BLOCKS</replaceable></option>,
+ <option>hash-offset=<replaceable>BYTES</replaceable></option>,
+ <option>salt=<replaceable>HEX</replaceable></option>, <option>uuid=<replaceable>UUID</replaceable></option>,
<option>ignore-corruption</option>, <option>restart-on-corruption</option>, <option>ignore-zero-blocks</option>,
- <option>check-at-most-once</option>, <option>panic-on-corruption</option> and
+ <option>check-at-most-once</option>, <option>panic-on-corruption</option>,
+ <option>hash=<replaceable>HASH</replaceable></option>, <option>fec-device=<replaceable>PATH</replaceable></option>,
+ <option>fec-offset=<replaceable>BYTES</replaceable></option>, <option>fec-roots=<replaceable>NUM</replaceable></option> and
<option>root-hash-signature=<replaceable>PATH</replaceable>|base64:<replaceable>HEX</replaceable></option>. See
<citerefentry project='die-net'><refentrytitle>veritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> for more
details.</para></listitem>
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>RootImagePolicy=</varname></term>
+ <term><varname>MountImagePolicy=</varname></term>
+ <term><varname>ExtensionImagePolicy=</varname></term>
+
+ <listitem><para>Takes an image policy string as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ to use when mounting the disk images (DDI) specified in <varname>RootImage=</varname>,
+ <varname>MountImage=</varname>, <varname>ExtensionImage=</varname>, respectively. If not specified
+ the following policy string is the default for <varname>RootImagePolicy=</varname> and <varname>MountImagePolicy</varname>:</para>
+
+ <programlisting>root=verity+signed+encrypted+unprotected+absent: \
+ usr=verity+signed+encrypted+unprotected+absent: \
+ home=encrypted+unprotected+absent: \
+ srv=encrypted+unprotected+absent: \
+ tmp=encrypted+unprotected+absent: \
+ var=encrypted+unprotected+absent</programlisting>
+
+ <para>The default policy for <varname>ExtensionImagePolicy=</varname> is:</para>
+
+ <programlisting>root=verity+signed+encrypted+unprotected+absent: \
+ usr=verity+signed+encrypted+unprotected+absent</programlisting></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>MountAPIVFS=</varname></term>
<entry>@obsolete</entry>
<entry>Unusual, obsolete or unimplemented (<citerefentry project='man-pages'><refentrytitle>create_module</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>gtty</refentrytitle><manvolnum>2</manvolnum></citerefentry>, …)</entry>
</row>
+ <row>
+ <entry>@pkey</entry>
+ <entry>System calls that deal with memory protection keys (<citerefentry project='man-pages'><refentrytitle>pkeys</refentrytitle><manvolnum>7</manvolnum></citerefentry>)</entry>
+ </row>
<row>
<entry>@privileged</entry>
<entry>All system calls which need super-user capabilities (<citerefentry project='man-pages'><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>)</entry>
<entry>@resources</entry>
<entry>System calls for changing resource limits, memory and scheduling parameters (<citerefentry project='man-pages'><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setpriority</refentrytitle><manvolnum>2</manvolnum></citerefentry>, …)</entry>
</row>
+ <row>
+ <entry>@sandbox</entry>
+ <entry>System calls for sandboxing programs (<citerefentry project='man-pages'><refentrytitle>seccomp</refentrytitle><manvolnum>2</manvolnum></citerefentry>, Landlock system calls, …)</entry>
+ </row>
<row>
<entry>@setuid</entry>
<entry>System calls for changing user ID and group ID credentials, (<citerefentry project='man-pages'><refentrytitle>setuid</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setgid</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setresuid</refentrytitle><manvolnum>2</manvolnum></citerefentry>, …)</entry>
<varlistentry>
<term><varname>$NOTIFY_SOCKET</varname></term>
- <listitem><para>The socket
- <function>sd_notify()</function> talks to. See
+ <listitem><para>The socket <function>sd_notify()</function> talks to. See
<citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
</para></listitem>
</varlistentry>
convey.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>$FDSTORE</varname></term>
+
+ <listitem><para>If the file descriptor store is enabled for a service
+ (i.e. <varname>FileDescriptorStoreMax=</varname> is set to a non-zero value, see
+ <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details), this environment variable will be set to the maximum number of permitted entries, as
+ per the setting. Applications may check this environment variable before sending file descriptors
+ to the service manager via <function>sd_pid_notify_with_fds()</function> (see
+ <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
+ details).</para></listitem>
+ </varlistentry>
+
</variablelist>
<para>For system services, when <varname>PAMName=</varname> is enabled and <command>pam_systemd</command> is part
--- /dev/null
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd.image-policy">
+
+ <refentryinfo>
+ <title>systemd.image-policy</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd.image-policy</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd.image-policy</refname>
+ <refpurpose>Disk Image Dissection Policy</refpurpose>
+ </refnamediv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>In systemd, whenever a disk image (DDI) implementing the <ulink
+ url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
+ Partitions Specification</ulink> is activated, a policy may be specified controlling which partitions to
+ mount and what kind of cryptographic protection to require. Such a disk image dissection policy is a
+ string that contains per-partition-type rules, separated by colons (<literal>:</literal>). The individual
+ rules consist of a partition identifier, an equal sign (<literal>=</literal>), and one or more flags
+ which may be set per partition. If multiple flags are specified per partition they are separated by a
+ plus sign (<literal>+</literal>).</para>
+
+ <para>The partition identifiers currently defined are: <option>root</option>, <option>usr</option>,
+ <option>home</option>, <option>srv</option>, <option>esp</option>, <option>xbootldr</option>,
+ <option>swap</option>, <option>root-verity</option>, <option>root-verity-sig</option>,
+ <option>usr-verity</option>, <option>usr-verity-sig</option>, <option>tmp</option>,
+ <option>var</option>. These identifiers match the relevant partition types in the Discoverable Partitions
+ Specification, but are agnostic to CPU architectures. If the partition identifier is left empty it
+ defines the <emphasis>default</emphasis> policy for partitions defined in the Discoverable Partitions
+ Specification for which no policy flags are explicitly listed in the policy string.</para>
+
+ <para>The following partition policy flags are defined that dictate the existence/absence, the use, and
+ the protection level of partitions:</para>
+
+ <itemizedlist>
+ <listitem><para><option>unprotected</option> for partitions that shall exist and be used, but shall
+ come without cryptographic protection, lacking both Verity authentication and LUKS
+ encryption.</para></listitem>
+
+ <listitem><para><option>verity</option> for partitions that shall exist and be used, with Verity
+ authentication. (Note: if a DDI image carries a data partition, along with a Verity partition and a
+ signature partition for it, and only the <option>verity</option> flag is set – and
+ <option>signed</option> is not –, then the image will be set up with Verity, but the signature data will
+ not be used. Or in other words: any DDI with a set of partitions that qualify for
+ <option>signature</option> also implicitly qualifies for <option>verity</option>, and in fact
+ <option>unprotected</option>).</para></listitem>
+
+ <listitem><para><option>signed</option> for partitions that shall exist and be used, with Verity
+ authentication, which are also accompanied by a PKCS#7 signature of the Verity root
+ hash.</para></listitem>
+
+ <listitem><para><option>encrypted</option> for partitions which shall exist and be used and are
+ encrypted with LUKS.</para></listitem>
+
+ <listitem><para><option>unused</option> for partitions that shall exist but shall not be
+ used.</para></listitem>
+
+ <listitem><para><option>absent</option> for partitions that shall not exist on the
+ image.</para></listitem>
+ </itemizedlist>
+
+ <para>By setting a combination of the flags above, alternatives can be declared. For example the
+ combination <literal>unused+absent</literal> means: the partition may exist (in which case it shall not
+ be used) or may be absent. The combination of
+ <literal>unprotected+verity+signed+encrypted+unused+absent</literal> may be specified via the special
+ shortcut <literal>open</literal>, and indicates that the partition may exist or may be absent, but if it
+ exists is used, regardless of the protection level.</para>
+
+ <para>As special rule: if none of the flags above are set for a listed partition identifier, the default
+ policy of <option>open</option> is implied, i.e. setting none of these flags listed above means
+ effectively all flags listed above will be set.</para>
+
+ <para>The following partition policy flags are defined that dictate the state of specific GPT partition
+ flags:</para>
+
+ <itemizedlist>
+ <listitem><para><option>read-only-off</option>, <option>read-only-on</option> to require that the
+ partitions have the read-only partition flag off or on.</para></listitem>
+
+ <listitem><para><option>growfs-off</option>, <option>growfs-on</option> to require that the
+ partitions have the growfs partition flag off or on.</para></listitem>
+ </itemizedlist>
+
+ <para>If both <option>read-only-off</option> and <option>read-only-on</option> are set for a partition,
+ then the state of the read-only flag on the partition is not dictated by the policy. Setting neither flag
+ is equivalent to setting both, i.e. setting neither of these two flags means effectively both will be
+ set. A similar logic applies to <option>growfs-off</option>/<option>growfs-on</option>.</para>
+
+ <para>If partitions are not listed within an image policy string, the default policy flags are applied
+ (configurable via an empty partition identifier, see above). If no default policy flags are configured in
+ the policy string, it is implied to be <literal>absent+unused</literal>, except for the Verity partition
+ and their signature partitions where the policy is automatically derived from minimal protection level of
+ the data partition they protect, as encoded in the policy.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Special Policies</title>
+
+ <para>The special image policy string <literal>*</literal> is short for "use everything", i.e. is
+ equivalent to:</para>
+
+ <programlisting>=verity+signed+encrypted+unprotected+unused+absent</programlisting>
+
+ <para>The special image policy string <literal>-</literal> is short for "use nothing", i.e. is equivalent
+ to:</para>
+
+ <programlisting>=unused+absent</programlisting>
+
+ <para>The special image policy string <literal>~</literal> is short for "everything must be absent",
+ i.e. is equivalent to:</para>
+
+ <programlisting>=absent</programlisting>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Use</title>
+
+ <para>Most systemd components that support operating with disk images support a
+ <option>--image-policy=</option> command line option to specify the image policy to use, and default to
+ relatively open policies by default (typically the <literal>*</literal> policy, as described above),
+ under the assumption that trust in disk images is established before the images are passed to the program
+ in question.</para>
+
+ <para>For the host image itself
+ <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ is responsible for processing the GPT partition table and making use of the included discoverable
+ partitions. It accepts an image policy via the kernel command line option
+ <option>systemd.image-policy=</option>.</para>
+
+ <para>Note that image policies do not dictate how the components will mount and use disk images — they
+ only dictate which parts to avoid and which protection level and arrangement to require while
+ mounting/using them. For example,
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> only
+ cares for the <filename>/usr/</filename> and <filename>/opt/</filename> trees inside a disk image, and
+ thus ignores any <filename>/home/</filename> partitions (and similar) in all cases, which might be
+ included in the image, regardless whether the configured image policy would allow access to it or
+ not. Similar,
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> is not
+ going to make use of any discovered swap device, regardless if the policy would allow that or not.</para>
+
+ <para>Use the <command>image-policy</command> command of the
+ <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>8</manvolnum></citerefentry> tool
+ to analyze image policy strings, and determine what a specific policy string means for a specific
+ partition.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>The following image policy string dictates one read-only Verity-enabled <filename>/usr/</filename>
+ partition must exist, plus encrypted root and swap partitions. All other partitions are ignored:</para>
+
+ <programlisting>usr=verity+read-only-on:root=encrypted:swap=encrypted</programlisting>
+
+ <para>The following image policy string dictates an encrypted, writable root file system, and optional
+ <filename>/srv/</filename> file system that must be encrypted if it exists and no swap partition may
+ exist:</para>
+
+ <programlisting>root=encrypted+read-only-off:srv=encrypted+absent:swap=absent</programlisting>
+
+ <para>The following image policy string dictates a single root partition that may be encrypted, but
+ doesn't have to be, and ignores swap partitions, and uses all other partitions if they are available, possibly with encryption.</para>
+
+ <programlisting>root=unprotected+encrypted:swap=absent+unused:=unprotected+encrypted+absent</programlisting>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
<example>
<title>(Re-)applying a .link file to an interface</title>
- <para>After a new .link file has been created, or an exisiting .link file modified, the new settings
+ <para>After a new .link file has been created, or an existing .link file modified, the new settings
may be applied to the matching interface with the following commands:</para>
<programlisting>$ sudo udevadm control --reload
<varlistentry>
<term><varname>InheritInnerProtocol=</varname></term>
<listitem>
- <para>Takes a boolean. When true, inner Layer 3 protcol is set as Protocol Type in the GENEVE
+ <para>Takes a boolean. When true, inner Layer 3 protocol is set as Protocol Type in the GENEVE
header instead of Ethernet. Defaults to false.</para>
</listitem>
</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>Interface=</varname></term>
- <listitem><para>Takes a space-separated list of interfaces to
- add to the container. This option corresponds to the
+ <listitem><para>Takes a space-separated list of interfaces to add to the container.
+ The interface object is defined either by a single interface name, referencing the name on the host,
+ or a colon-separated pair of interfaces, in which case the first one references the name on the host,
+ and the second one the name in the container.
+ This option corresponds to the
<option>--network-interface=</option> command line switch and
implies <varname>Private=yes</varname>. This option is
privileged (see above).</para></listitem>
<listitem><para>Takes a space-separated list of interfaces to
add MACLVAN or IPVLAN interfaces to, which are then added to
- the container. These options correspond to the
+ the container. The interface object is defined either by a single interface name, referencing the name
+ on the host, or a colon-separated pair of interfaces, in which case the first one references the name
+ on the host, and the second one the name in the container. These options correspond to the
<option>--network-macvlan=</option> and
<option>--network-ipvlan=</option> command line switches and
imply <varname>Private=yes</varname>. These options are
</listitem>
<listitem>
- <para>When the user OK'ed the update, the symlink <filename>/system-update</filename> is
- created that points to <filename index="false">/var/lib/system-update</filename> (or
- wherever the directory with the upgrade files is located) and the system is rebooted. This
- symlink is in the root directory, since we need to check for it very early at boot, at a
- time where <filename>/var/</filename> is not available yet.</para>
+ <para>When the user OK'ed the update, the symlink <filename>/system-update</filename> or
+ <filename>/etc/system-update</filename> is created that points to
+ <filename index="false">/var/lib/system-update</filename> (or wherever the directory with
+ the upgrade files is located) and the system is rebooted. This symlink is in the root
+ directory, since we need to check for it very early at boot, at a time where
+ <filename>/var/</filename> is not available yet.</para>
</listitem>
<listitem>
<para>Very early in the new boot
<citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- checks whether <filename>/system-update</filename> exists. If so, it (temporarily and for
- this boot only) redirects (i.e. symlinks) <filename>default.target</filename> to
+ checks whether <filename>/system-update</filename> or
+ <filename>/etc/system-update</filename> exists. If so, it (temporarily and for this boot
+ only) redirects (i.e. symlinks) <filename>default.target</filename> to
<filename>system-update.target</filename>, a special target that pulls in the base system
(i.e. <filename>sysinit.target</filename>, so that all file systems are mounted but little
else) and the system update units.</para>
<listitem>
<para>As the first step, an update service should check if the
- <filename>/system-update</filename> symlink points to the location used by that update
- service. In case it does not exist or points to a different location, the service must exit
- without error. It is possible for multiple update services to be installed, and for multiple
- update services to be launched in parallel, and only the one that corresponds to the tool
- that <emphasis>created</emphasis> the symlink before reboot should perform any actions. It
- is unsafe to run multiple updates in parallel.</para>
+ <filename>/system-update</filename> or <filename>/etc/system-update</filename> symlink
+ points to the location used by that update service. In case it does not exist or points to a
+ different location, the service must exit without error. It is possible for multiple update
+ services to be installed, and for multiple update services to be launched in parallel, and
+ only the one that corresponds to the tool that <emphasis>created</emphasis> the symlink
+ before reboot should perform any actions. It is unsafe to run multiple updates in
+ parallel.</para>
</listitem>
<listitem>
<para>The update scripts should exit only after the update is finished. It is expected
that the service which performs the update will cause the machine to reboot after it
is done. If the <filename>system-update.target</filename> is successfully reached, i.e.
- all update services have run, and the <filename>/system-update</filename> symlink still
- exists, it will be removed and the machine rebooted as a safety measure.</para>
+ all update services have run, and the <filename>/system-update</filename> or
+ <filename>/etc/system-update</filename> symlink still exists, it will be removed and
+ the machine rebooted as a safety measure.</para>
</listitem>
<listitem>
- <para>After a reboot, now that the <filename>/system-update</filename> symlink is gone,
- the generator won't redirect <filename>default.target</filename> anymore and the system
- now boots into the default target again.</para>
+ <para>After a reboot, now that the <filename>/system-update</filename> and
+ <filename>/etc/system-update</filename> symlink is gone, the generator won't redirect
+ <filename>default.target</filename> anymore and the system now boots into the default
+ target again.</para>
</listitem>
</orderedlist>
</refsect1>
</listitem>
<listitem>
- <para>Make sure to remove the <filename>/system-update</filename> symlink as early as
- possible in the update script to avoid reboot loops in case the update fails.</para>
+ <para>Make sure to remove the <filename>/system-update</filename> and
+ <filename>/etc/system-update</filename> symlinks as early as possible in the update
+ script to avoid reboot loops in case the update fails.</para>
</listitem>
<listitem>
<refsect1>
<title>Preset File Format</title>
- <para>The preset files contain a list of directives consisting of
- either the word <literal>enable</literal> or
- <literal>disable</literal> followed by a space and a unit name
- (possibly with shell style wildcards), separated by newlines.
- Empty lines and lines whose first non-whitespace character is <literal>#</literal> or
- <literal>;</literal> are ignored. Multiple instance names for unit
- templates may be specified as a space separated list at the end of
- the line instead of the customary position between <literal>@</literal>
- and the unit suffix.</para>
+ <para>The preset files contain a list of directives, one per line. Empty lines and lines whose first
+ non-whitespace character is <literal>#</literal> or <literal>;</literal> are ignored. Each directive
+ consists of one of the words <literal>enable</literal>, <literal>disable</literal>, or
+ <literal>ignore</literal>, followed by whitespace and a unit name. The unit name may contain shell-style
+ wildcards.</para>
+
+ <para>For the enable directive for template units, one or more instance names may be specified as a
+ space-separated list after the unit name. In this case, those instances will be enabled instead of the
+ instance specified via DefaultInstance= in the unit.</para>
<para>Presets must refer to the "real" unit file, and not to any aliases. See
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for a description of unit aliasing.</para>
- <para>Two different directives are understood:
- <literal>enable</literal> may be used to enable units by default,
- <literal>disable</literal> to disable units by default.</para>
+ <para>Three different directives are understood: <literal>enable</literal> may be used to enable units by
+ default, <literal>disable</literal> to disable units by default, and <literal>ignore</literal> to ignore
+ units and leave existing configuration intact.</para>
<para>If multiple lines apply to a unit name, the first matching
one takes precedence over all others.</para>
configuration for <filename>system.slice</filename> and <filename>user.slice</filename>, CPU
resources will be split equally between them. Similarly, resources are allocated equally between
children of <filename>user.slice</filename> and between the child slices beneath
- <filename>user@1000.service</filename>. Assuming that there is no futher configuration of resources
+ <filename>user@1000.service</filename>. Assuming that there is no further configuration of resources
or delegation below slices <filename>app.slice</filename> or <filename>session.slice</filename>, the
<option>cpu</option> controller would not be enabled for units in those slices and CPU resources
would be further allocated using other mechanisms, e.g. based on nice levels. The manager for user
<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
as "5min 20s". Defaults to 100ms.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>RestartSteps=</varname></term>
+ <listitem><para>Configures the number of steps to take to increase the interval
+ of auto-restarts from <varname>RestartSec=</varname> to <varname>RestartSecMax=</varname>.
+ Takes a positive integer or 0 to disable it. Defaults to 0.</para>
+
+ <para>This setting is effective only if <varname>RestartSecMax=</varname> is also set.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>RestartSecMax=</varname></term>
+ <listitem><para>Configures the longest time to sleep before restarting a service
+ as the interval goes up with <varname>RestartSteps=</varname>. Takes a value
+ in the same format as <varname>RestartSec=</varname>, or <literal>infinity</literal>
+ to disable the setting. Defaults to <literal>infinity</literal>.</para>
+
+ <para>This setting is effective only if <varname>RestartSteps=</varname> is also set.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>TimeoutStartSec=</varname></term>
<listitem><para>Configures the time to wait for start-up. If a daemon service does not signal
<literal>FDSTORE=1</literal> messages. This is useful for implementing services that can restart
after an explicit request or a crash without losing state. Any open sockets and other file
descriptors which should not be closed during the restart may be stored this way. Application state
- can either be serialized to a file in <filename>/run/</filename>, or better, stored in a
+ can either be serialized to a file in <varname>RuntimeDirectory=</varname>, or stored in a
<citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
memory file descriptor. Defaults to 0, i.e. no file descriptors may be stored in the service
manager. All file descriptors passed to the service manager from a specific service are passed back
details about the precise protocol used and the order in which the file descriptors are passed). Any
file descriptors passed to the service manager are automatically closed when
<constant>POLLHUP</constant> or <constant>POLLERR</constant> is seen on them, or when the service is
- fully stopped and no job is queued or being executed for it. If this option is used,
+ fully stopped and no job is queued or being executed for it (the latter can be tweaked with
+ <varname>FileDescriptorStorePreserve=</varname>, see below). 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>
+
+ <para>If this option is set to a non-zero value the <varname>$FDSTORE</varname> environment variable
+ will be set for processes invoked for this service. See
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>FileDescriptorStorePreserve=</varname></term>
+ <listitem><para>Takes one of <constant>no</constant>, <constant>yes</constant>,
+ <constant>restart</constant> and controls when to release the service's file descriptor store
+ (i.e. when to close the contained file descriptors, if any). If set to <constant>no</constant> the
+ file descriptor store is automatically released when the service is stopped; if
+ <constant>restart</constant> (the default) it is kept around as long as the unit is neither inactive
+ nor failed, or a job is queued for the service, or the service is expected to be restarted. If
+ <constant>yes</constant> the file descriptor store is kept around until the unit is removed from
+ memory (i.e. is not referenced anymore and inactive). The latter is useful to keep entries in the
+ file descriptor store pinned until the service manage exits.</para>
+
+ <para>Use <command>systemctl clean --what=fdstore …</command> to release the file descriptor store
+ explicitly.</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>
<listitem>
<para>A special target unit that is used for offline system updates.
<citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- will redirect the boot process to this target if <filename>/system-update</filename>
- exists. For more information see
+ will redirect the boot process to this target if <filename>/system-update</filename> or
+ <filename>/etc/system-update</filename> exists. For more information see
<citerefentry><refentrytitle>systemd.offline-updates</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<filename>system-update-pre.target</filename> but not pull it in. Services which want to
run during system updates only, but before the actual system update is executed should
order themselves before this unit and pull it in. As a safety measure, if this does not
- happen, and <filename>/system-update</filename> still exists after
+ happen, and <filename>/system-update</filename> or
+ <filename>/etc/system-update</filename> still exists after
<filename>system-update.target</filename> is reached,
- <filename>system-update-cleanup.service</filename> will remove this symlink and reboot
+ <filename>system-update-cleanup.service</filename> will remove the symlinks and reboot
the machine.</para>
</listitem>
</varlistentry>
<para>Unit files are loaded from a set of paths determined during compilation, described in the next
section.</para>
- <para>Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type. The
- "unit prefix" must consist of one or more valid characters (ASCII letters, digits, <literal>:</literal>,
- <literal>-</literal>, <literal>_</literal>, <literal>.</literal>, and <literal>\</literal>). The total
- length of the unit name including the suffix must not exceed 256 characters. The type suffix must be one
- of <literal>.service</literal>, <literal>.socket</literal>, <literal>.device</literal>,
- <literal>.mount</literal>, <literal>.automount</literal>, <literal>.swap</literal>,
- <literal>.target</literal>, <literal>.path</literal>, <literal>.timer</literal>,
- <literal>.slice</literal>, or <literal>.scope</literal>.</para>
-
- <para>Units names can be parameterized by a single argument called the "instance name". The unit is then
+ <para>Valid unit names consist of a "unit name prefix", and a suffix specifying the unit type which
+ begins with a dot. The "unit name prefix" must consist of one or more valid characters (ASCII letters,
+ digits, <literal>:</literal>, <literal>-</literal>, <literal>_</literal>, <literal>.</literal>, and
+ <literal>\</literal>). The total length of the unit name including the suffix must not exceed 255
+ characters. The unit type suffix must be one of <literal>.service</literal>, <literal>.socket</literal>,
+ <literal>.device</literal>, <literal>.mount</literal>, <literal>.automount</literal>,
+ <literal>.swap</literal>, <literal>.target</literal>, <literal>.path</literal>,
+ <literal>.timer</literal>, <literal>.slice</literal>, or <literal>.scope</literal>.</para>
+
+ <para>Unit names can be parameterized by a single argument called the "instance name". The unit is then
constructed based on a "template file" which serves as the definition of multiple services or other
- units. A template unit must have a single <literal>@</literal> at the end of the name (right before the
- type suffix). The name of the full unit is formed by inserting the instance name between
+ units. A template unit must have a single <literal>@</literal> at the end of the unit name prefix (right
+ before the type suffix). The name of the full unit is formed by inserting the instance name between
<literal>@</literal> and the unit type suffix. In the unit file itself, the instance parameter may be
referred to using <literal>%i</literal> and other specifiers, see below.</para>
<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>
<para>This can be overridden with <option>--log-target=</option>.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>$SYSTEMD_LOG_RATELIMIT_KMSG</varname></term>
+ <listitem><xi:include href="common-variables.xml" xpointer="log-ratelimit-kmsg" /></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>$XDG_CONFIG_HOME</varname></term>
<term><varname>$XDG_CONFIG_DIRS</varname></term>
<term><varname>systemd.log_target=</varname></term>
<term><varname>systemd.log_time</varname></term>
<term><varname>systemd.log_tid</varname></term>
+ <term><varname>systemd.log_ratelimit_kmsg</varname></term>
<listitem><para>Controls log output, with the same effect as the
<varname>$SYSTEMD_LOG_COLOR</varname>, <varname>$SYSTEMD_LOG_LEVEL</varname>,
<varname>$SYSTEMD_LOG_LOCATION</varname>, <varname>$SYSTEMD_LOG_TARGET</varname>,
- <varname>$SYSTEMD_LOG_TIME</varname>, and <varname>$SYSTEMD_LOG_TID</varname> environment variables
- described above. <varname>systemd.log_color</varname>, <varname>systemd.log_location</varname>,
- <varname>systemd.log_time</varname>, and <varname>systemd.log_tid=</varname> can be specified without
+ <varname>$SYSTEMD_LOG_TIME</varname>, <varname>$SYSTEMD_LOG_TID</varname> and
+ <varname>$SYSTEMD_LOG_RATELIMIT_KMSG</varname> environment variables described above.
+ <varname>systemd.log_color</varname>, <varname>systemd.log_location</varname>,
+ <varname>systemd.log_time</varname>, <varname>systemd.log_tid</varname> and
+ <varname>systemd.log_ratelimit_kmsg</varname> can be specified without
an argument, with the same effect as a positive boolean.</para></listitem>
</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>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect2>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
- <citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
<variablelist class='fstab-options'>
+ <varlistentry>
+ <term><option>superblock=<replaceable>BOOL</replaceable></option></term>
+
+ <listitem><para>Use dm-verity with or without permanent on-disk superblock.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>format=<replaceable>NUMBER</replaceable></option></term>
+
+ <listitem><para>Specifies the hash version type. Format type 0 is original Chrome OS version. Format type 1 is
+ modern version.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>data-block-size=<replaceable>BYTES</replaceable></option></term>
+
+ <listitem><para>Used block size for the data device. (Note kernel supports only page-size as maximum
+ here; Multiples of 512 bytes.) </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>hash-block-size=<replaceable>BYTES</replaceable></option></term>
+
+ <listitem><para>Used block size for the hash device. (Note kernel supports only page-size as maximum
+ here; Multiples of 512 bytes.)</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>data-blocks=<replaceable>BLOCKS</replaceable></option></term>
+
+ <listitem><para>Number of blocks of data device used in verification. If not specified, the whole device is
+ used.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>hash-offset=<replaceable>BYTES</replaceable></option></term>
+
+ <listitem><para>Offset of hash area/superblock on <literal>hash-device</literal>. (Multiples of 512 bytes.)
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>salt=<replaceable>HEX</replaceable></option></term>
+
+ <listitem><para>Salt used for format or verification. Format is a hexadecimal string; 256 bytes long maximum;
+ <literal>-</literal>is the special value for empty.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>uuid=<replaceable>UUID</replaceable></option></term>
+
+ <listitem><para>Use the provided UUID for format command instead of generating new one. The UUID must be
+ provided in standard UUID format, e.g. 12345678-1234-1234-1234-123456789abc.</para></listitem>
+ <listitem><para></para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>ignore-corruption</option></term>
<term><option>restart-on-corruption</option></term>
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>hash=<replaceable>HASH</replaceable></option></term>
+
+ <listitem><para>Hash algorithm for dm-verity. This should be the name of the algorithm, like "sha1". For default
+ see <command>veritysetup --help</command>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>fec-device=<replaceable>PATH</replaceable></option></term>
+
+ <listitem><para>Use forward error correction (FEC) to recover from corruption if hash verification fails. Use
+ encoding data from the specified device. The fec device argument can be block device or file image. For format,
+ if fec device path doesn't exist, it will be created as file. Note: block sizes for data and hash devices must
+ match. Also, if the verity data_device is encrypted the fec_device should be too.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>fec-offset=<replaceable>BYTES</replaceable></option></term>
+
+ <listitem><para>This is the offset, in bytes, from the start of the FEC device to the beginning of the encoding
+ data. (Aligned on 512 bytes.)</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>fec-roots=<replaceable>NUM</replaceable></option></term>
+
+ <listitem><para>Number of generator roots. This equals to the number of parity bytes in the encoding data. In
+ RS(M, N) encoding, the number of roots is M-N. M is 255 and M-N is between 2 and 24 (including).</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>root-hash-signature=<replaceable>PATH</replaceable>|base64:<replaceable>HEX</replaceable></option></term>
factorydir = datadir / 'factory'
bootlibdir = prefixdir / 'lib/systemd/boot/efi'
testsdir = prefixdir / 'lib/systemd/tests'
+unittestsdir = testsdir / 'unit-tests'
+testdata_dir = testsdir / 'testdata'
systemdstatedir = localstatedir / 'lib/systemd'
catalogstatedir = systemdstatedir / 'catalog'
randomseeddir = localstatedir / 'lib/systemd'
conf.set_quoted('SYSTEMD_MAKEFS_PATH', rootlibexecdir / 'systemd-makefs')
conf.set_quoted('SYSTEMD_PULL_PATH', rootlibexecdir / 'systemd-pull')
conf.set_quoted('SYSTEMD_SHUTDOWN_BINARY_PATH', rootlibexecdir / 'systemd-shutdown')
-conf.set_quoted('SYSTEMD_TEST_DATA', testsdir / 'testdata')
+conf.set_quoted('SYSTEMD_TEST_DATA', testdata_dir)
conf.set_quoted('SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH', rootbindir / 'systemd-tty-ask-password-agent')
conf.set_quoted('SYSTEMD_UPDATE_HELPER_PATH', rootlibexecdir / 'systemd-update-helper')
conf.set_quoted('SYSTEMD_USERWORK_PATH', rootlibexecdir / 'systemd-userwork')
python = pymod.find_installation('python3', required : true, modules : ['jinja2'])
python_39 = python.language_version().version_compare('>=3.9')
+############################################################
+
+if conf.get('BPF_FRAMEWORK') == 1
+ bpf_clang_flags = [
+ '-std=gnu11',
+ '-Wno-compare-distinct-pointer-types',
+ '-fno-stack-protector',
+ '-O2',
+ '-target',
+ 'bpf',
+ '-g',
+ '-c',
+ ]
+
+ bpf_gcc_flags = [
+ '-std=gnu11',
+ '-fno-stack-protector',
+ '-O2',
+ '-mkernel=5.2',
+ '-mcpu=v3',
+ '-mco-re',
+ '-gbtf',
+ '-c',
+ ]
+
+ # Generate defines that are appropriate to tell the compiler what architecture
+ # we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__.
+ # This dictionary contains the exceptions where this doesn't work.
+ #
+ # C.f. https://mesonbuild.com/Reference-tables.html#cpu-families
+ # and src/basic/missing_syscall_def.h.
+ cpu_arch_defines = {
+ 'ppc' : ['-D__powerpc__'],
+ 'ppc64' : ['-D__powerpc64__', '-D_CALL_ELF=2'],
+ 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32'],
+ 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64'],
+ 'x86' : ['-D__i386__'],
+
+ # For arm, assume hardware fp is available.
+ 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP'],
+ }
+
+ bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(),
+ ['-D__@0@__'.format(host_machine.cpu_family())])
+ if bpf_compiler == 'gcc'
+ bpf_arch_flags += ['-m' + host_machine.endian() + '-endian']
+ endif
+
+ libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir')
+
+ bpf_o_unstripped_cmd = []
+ if bpf_compiler == 'clang'
+ bpf_o_unstripped_cmd += [
+ clang,
+ bpf_clang_flags,
+ bpf_arch_flags,
+ ]
+ elif bpf_compiler == 'gcc'
+ bpf_o_unstripped_cmd += [
+ bpf_gcc,
+ bpf_gcc_flags,
+ bpf_arch_flags,
+ ]
+ endif
+
+ bpf_o_unstripped_cmd += ['-I.']
+
+ if not meson.is_cross_build() and bpf_compiler == 'clang'
+ target_triplet_cmd = run_command('gcc', '-dumpmachine', check: false)
+ if target_triplet_cmd.returncode() == 0
+ target_triplet = target_triplet_cmd.stdout().strip()
+ bpf_o_unstripped_cmd += [
+ '-isystem',
+ '/usr/include/@0@'.format(target_triplet)
+ ]
+ endif
+ endif
+
+ bpf_o_unstripped_cmd += [
+ '-idirafter',
+ libbpf_include_dir,
+ '@INPUT@',
+ '-o',
+ '@OUTPUT@'
+ ]
+
+ if bpftool_strip
+ bpf_o_cmd = [
+ bpftool,
+ 'gen',
+ 'object',
+ '@OUTPUT@',
+ '@INPUT@'
+ ]
+ elif bpf_compiler == 'clang'
+ bpf_o_cmd = [
+ llvm_strip,
+ '-g',
+ '@INPUT@',
+ '-o',
+ '@OUTPUT@'
+ ]
+ endif
+
+ skel_h_cmd = [
+ bpftool,
+ 'gen',
+ 'skeleton',
+ '@INPUT@'
+ ]
+endif
+
#####################################################################
efi_arch = {
endif
if conf.get('ENABLE_LOCALED') == 1
- if conf.get('HAVE_XKBCOMMON') == 1
- # logind will load libxkbcommon.so dynamically on its own, but we still
- # need to specify where the headers are
- deps = [libdl,
- libxkbcommon.partial_dependency(compile_args: true),
- userspace,
- versiondep]
- else
- deps = [userspace,
- versiondep]
- endif
-
dbus_programs += executable(
'systemd-localed',
systemd_localed_sources,
include_directories : includes,
link_with : [libshared],
- dependencies : deps,
+ dependencies : libxkbcommon_deps +
+ [userspace,
+ versiondep],
install_rpath : rootpkglibdir,
install : true,
install_dir : rootlibexecdir)
build_by_default : want_tests != 'false',
install_rpath : rootpkglibdir,
install : install_tests,
- install_dir : testsdir / type,
+ install_dir : unittestsdir / type,
link_depends : runtest_env)
if type == 'manual'
dependencies : userspace,
build_by_default : want_tests != 'false',
install : install_tests,
- install_dir : testsdir)
+ install_dir : unittestsdir)
if want_tests != 'false'
test('test-libsystemd-sym', exe)
endif
],
build_by_default : want_tests != 'false' and static_libsystemd_pic,
install : install_tests and static_libsystemd_pic,
- install_dir : testsdir)
+ install_dir : unittestsdir)
if want_tests != 'false' and static_libsystemd_pic
test('test-libsystemd-static-sym', exe)
endif
dependencies : userspace,
build_by_default : want_tests != 'false',
install : install_tests,
- install_dir : testsdir)
+ install_dir : unittestsdir)
if want_tests != 'false'
test('test-libudev-sym', exe)
endif
dependencies : userspace,
build_by_default : want_tests != 'false' and static_libudev_pic,
install : install_tests and static_libudev_pic,
- install_dir : testsdir)
+ install_dir : unittestsdir)
if want_tests != 'false' and static_libudev_pic
test('test-libudev-static-sym', exe)
endif
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_.
Environment=ASAN_OPTIONS=verify_asan_link_order=false
MKOSI_ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:disable_coredump=0:use_madv_dontdump=1
[Content]
BuildDirectory=mkosi.builddir
-Cache=mkosi.cache
+CacheDirectory=mkosi.cache
+ExtraTrees=src:/root/src
Packages=
acl
bash-completion
diffutils
dnsmasq
dosfstools
+ dracut
e2fsprogs
findutils
gcc # For sanitizer libraries
qrencode
sed
strace
+ systemd
tree
+ udev
util-linux
valgrind
wireguard-tools
zstd
[Host]
-QemuHeadless=yes
-Netdev=yes
+Acl=yes
QemuMem=2G
ExtraSearchPaths=build/
KernelCommandLineExtra=systemd.crash_shell
systemd.log_level=debug
+ systemd.log_ratelimit_kmsg=0
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]
Packages=
alsa-lib
+ base
btrfs-progs
compsize
dhcp
libmnl
libpwquality
libxkbcommon
+ linux
man-db
numactl
openbsd-netcat
+ openssh
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.
-
-[Distribution]
-Distribution=fedora
-Release=37
+[Match]
+Distribution=centos fedora
[Content]
Packages=
alsa-lib
- btrfs-progs
- compsize
+ audit-libs
cryptsetup
dhcp-server
dnf
- f2fs-tools
fuse
glib2
glibc-minimal-langpack
+ glibc.i686
gnutls
iproute
iproute-tc
+ kernel-core
libasan
libbpf
libcap-ng
libxkbcommon
netcat
numactl-libs
+ openssh-server
+ p11-kit
pam
passwd
polkit
popt
procps-ng
- python3dist(pefile)
quota
tpm2-tss
+ util-linux
vim-common
BuildPackages=
+ /usr/bin/pkg-config
bpftool
docbook-xsl
dwarves
+ glibc-devel.i686
glibc-static
- libcap-static
+ glibc-static.i686
+ libxslt
pam-devel
- pkgconfig # pkgconf shim to provide /usr/bin/pkg-config
+ perl-interpreter
pkgconfig(alsa)
pkgconfig(audit)
pkgconfig(blkid)
+ pkgconfig(bzip2)
pkgconfig(dbus-1)
pkgconfig(fdisk)
pkgconfig(fuse)
pkgconfig(glib-2.0)
+ pkgconfig(gnutls)
pkgconfig(libacl)
pkgconfig(libbpf)
pkgconfig(libcap-ng)
pkgconfig(libcurl)
pkgconfig(libdw)
pkgconfig(libfido2)
- pkgconfig(libgcrypt)
pkgconfig(libidn2)
pkgconfig(libkmod)
pkgconfig(libmicrohttpd)
pkgconfig(tss2-mu)
pkgconfig(tss2-rc)
pkgconfig(valgrind)
- pkgconfig(xencontrol)
pkgconfig(xkbcommon)
- python3dist(docutils)
- python3dist(jinja2)
- python3dist(lxml)
- python3dist(pyelftools)
- python3dist(pytest)
+ python3-docutils
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=centos
+
+[Distribution]
+Release=9
+Repositories=epel
# 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.
-
-[Distribution]
-Distribution=debian
-Release=testing
+[Match]
+Distribution=debian ubuntu
[Content]
Packages=
btrfs-progs
cryptsetup-bin
+ dbus-broker
+ default-dbus-session-bus
f2fs-tools
fdisk
fuse
iproute2
isc-dhcp-server
libasound2
- libbpf1
libc6-i386
libcap-ng-utils
libcap-ng0
+ libfdisk1
libfido2-1
libglib2.0-0
libgnutls30
libqrencode4
libtss2-dev # Use the -dev package to avoid churn in updating version numbers
netcat-openbsd
+ openssh-server
passwd
policykit-1
procps
python3-pefile
+ python3-psutil
+ python3-pytest
quota
+ systemd-sysv
+ tzdata
xxd
BuildPackages=
- bpftool
docbook-xsl
dpkg-dev
g++
python3-jinja2
python3-lxml
python3-pyelftools
- python3-pytest
xsltproc
--- /dev/null
+L /etc/default/locale - - - - ../locale.conf
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=debian
+
+[Distribution]
+Release=testing
+
+[Content]
+Packages=
+ libbpf1
+ linux-image-cloud-amd64
+
+BuildPackages=
+ bpftool
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=fedora
+
+[Distribution]
+Release=38
+
+[Content]
+Packages=
+ btrfs-progs
+ compsize
+ f2fs-tools
+ python3dist(pefile)
+ python3dist(psutil)
+ python3dist(pytest)
+
+BuildPackages=
+ libcap-static
+ pkgconfig(libgcrypt)
+ pkgconfig(xencontrol)
+ python3dist(jinja2)
+ python3dist(lxml)
+ python3dist(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=opensuse
[Distribution]
-Distribution=opensuse
Release=tumbleweed
[Content]
gcc # Provides libasan/libubsan
glibc-32bit
glibc-locale-base
+ kernel-default
libasound2
libbpf1
libcap-ng-utils
libqrencode4
libseccomp2
libxkbcommon0
+ openssh-server
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
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=ubuntu
+
+[Distribution]
+Release=jammy
+Repositories=universe
+
+[Content]
+Packages=
+ libbpf0
+ linux-virtual
+
+BuildPackages=
+ linux-tools-common
+ linux-tools-generic
--- /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)
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[powertools-hotfixes]
+name=powertools-hotfixes
+mirrorlist=http://mirrorlist.centos.org/?release=$stream&arch=$basearch&repo=PowerTools
+gpgkey=https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Official
+gpgcheck=1
+enabled=1
+module_hotfixes=1
+skip_if_unavailable=1
--- /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)
+++ /dev/null
-# 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.
-
-[Distribution]
-Distribution=centos
-Repositories=epel
-
-[Content]
-Packages=
- alsa-lib
- audit
- cryptsetup
- dhcp-server
- dnf
- fuse
- glib2
- glibc-minimal-langpack
- glibc.i686
- gnutls
- iproute
- iproute-tc
- kernel-modules-extra
- libasan
- libbpf
- libcap-ng
- libcap-ng-utils
- libfido2
- libmicrohttpd
- libmnl
- libubsan
- libxcrypt
- libxkbcommon
- netcat
- numactl-libs
- p11-kit
- pam
- passwd
- polkit
- popt
- procps-ng
- python3*dist(pefile)
- python3*dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
- python3*dist(pytest)
- python39
- quota
- tpm2-tss
- vim-common
-
-BuildPackages=
- bpftool
- docbook-xsl
- dwarves
- 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(alsa)
- pkgconfig(audit)
- pkgconfig(blkid)
- pkgconfig(bzip2)
- pkgconfig(dbus-1)
- pkgconfig(fdisk)
- pkgconfig(fuse)
- pkgconfig(glib-2.0)
- pkgconfig(gnutls)
- pkgconfig(libacl)
- pkgconfig(libbpf)
- pkgconfig(libcap-ng)
- pkgconfig(libcap)
- pkgconfig(libcryptsetup)
- pkgconfig(libcurl)
- pkgconfig(libdw)
- pkgconfig(libfido2)
- pkgconfig(libidn2)
- pkgconfig(libkmod)
- pkgconfig(libmicrohttpd)
- pkgconfig(libmnl)
- pkgconfig(libpcre2-8)
- pkgconfig(libqrencode)
- pkgconfig(libseccomp)
- pkgconfig(libselinux)
- pkgconfig(libzstd)
- pkgconfig(mount)
- pkgconfig(numa)
- pkgconfig(openssl)
- pkgconfig(p11-kit-1)
- pkgconfig(popt)
- pkgconfig(pwquality)
- pkgconfig(tss2-esys)
- pkgconfig(tss2-mu)
- pkgconfig(tss2-rc)
- pkgconfig(valgrind)
- pkgconfig(xkbcommon)
- python3*dist(docutils)
- python3*dist(jinja2)
- python3*dist(lxml)
- python3*dist(pyelftools)
+++ /dev/null
-# 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.
-
-[Distribution]
-Distribution=ubuntu
-Release=jammy
-Repositories=main,universe
-
-[Content]
-Packages=
- btrfs-progs
- cryptsetup-bin
- f2fs-tools
- fdisk
- fuse
- gcc # Provides libasan/libubsan
- iproute2
- isc-dhcp-server
- libasound2
- libbpf0
- libc6-i386
- libcap-ng-utils
- libcap-ng0
- libfdisk1
- libfido2-1
- libglib2.0-0
- libidn2-0
- libmicrohttpd12
- libmnl0
- libnuma1
- libp11-kit0
- libpopt0
- libpwquality1
- libqrencode4
- libtss2-dev # Use the -dev package to avoid churn in updating version numbers
- linux-tools-common
- linux-tools-generic
- netcat-openbsd
- passwd
- policykit-1
- procps
- python3-pefile
- quota
- xxd
-
-BuildPackages=
- docbook-xsl
- dpkg-dev
- g++
- gcc-multilib
- libacl1-dev
- libasound-dev
- libaudit-dev
- libblkid-dev
- libbpf-dev
- libbz2-dev
- libc6-dev
- libc6-dev-i386
- libcap-dev
- libcap-ng-dev
- libcryptsetup-dev
- libcurl4-openssl-dev
- libdbus-1-dev
- libdw-dev
- libfdisk-dev
- libfido2-dev
- libfuse-dev
- libgcrypt20-dev
- libglib2.0-dev
- libgnutls28-dev
- libidn2-dev
- libiptc-dev
- libkmod-dev
- libmicrohttpd-dev
- libmnl-dev
- libmount-dev
- libnuma-dev
- libp11-kit-dev
- libpam0g-dev
- libpopt-dev
- libpwquality-dev
- libqrencode-dev
- libseccomp-dev
- libsmartcols-dev
- libssl-dev
- libxen-dev
- libxkbcommon-dev
- libzstd-dev
- pahole
- python3-docutils
- python3-jinja2
- python3-lxml
- python3-pyelftools
- python3-pytest
- xsltproc
set debuginfod enabled off
set build-id-verbose 0
+set substitute-path ../src /root/src
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+disable ssh.service
+disable sshd.service
+disable dnsmasq.service
+disable isc-dhcp-server.service
+disable isc-dhcp-server6.service
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
systemctl mask systemd-hwdb-update.service
fi
-# Make sure dnsmasq.service doesn't start on boot on Debian/Ubuntu.
-rm -f /etc/systemd/system/multi-user.target.wants/dnsmasq.service
-
if [ -n "$IMAGE_ID" ] ; then
sed -n \
-i \
-e "\$aIMAGE_VERSION=$IMAGE_VERSION" \
/usr/lib/os-release
fi
+
+if command -v authselect >/dev/null; then
+ authselect select minimal
+
+ if authselect list-features minimal | grep -q "with-homed"; then
+ authselect enable-feature with-homed
+ fi
+fi
+
+# Let tmpfiles.d/systemd-resolve.conf handle the symlink
+rm -f /etc/resolv.conf
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Fran Dieguez <frandieguez@gnome.org>, 2015.
+# Fran Diéguez <frandieguez@gnome.org>, 2023.
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-20 10:35+0200\n"
-"PO-Revision-Date: 2019-12-29 22:30+0100\n"
+"PO-Revision-Date: 2023-04-14 18:20+0000\n"
"Last-Translator: Fran Diéguez <frandieguez@gnome.org>\n"
-"Language-Team: gnome-l10n-gl@gnome.org\n"
+"Language-Team: Galician <https://translate.fedoraproject.org/projects/"
+"systemd/master/gl/>\n"
"Language: gl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 2.2.4\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.15.2\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/home/org.freedesktop.home1.policy:13
msgid "Create a home area"
-msgstr ""
+msgstr "Crear un área persoal"
#: src/home/org.freedesktop.home1.policy:14
-#, fuzzy
msgid "Authentication is required to create a user's home area."
-msgstr "Requírese autenticación para estabelecer os servidores NTP."
+msgstr "Requírese autenticación para crear un área persoal."
#: src/home/org.freedesktop.home1.policy:23
msgid "Remove a home area"
-msgstr ""
+msgstr "Eliminar un área persoal"
#: src/home/org.freedesktop.home1.policy:24
-#, fuzzy
msgid "Authentication is required to remove a user's home area."
-msgstr "Requírese autenticación para estabelecer os servidores NTP."
+msgstr "Requírese autenticación para eliminar un área persoal."
#: src/home/org.freedesktop.home1.policy:33
msgid "Check credentials of a home area"
-msgstr ""
+msgstr "Comprobar as credenciais dun área persoal"
#: src/home/org.freedesktop.home1.policy:34
-#, fuzzy
msgid ""
"Authentication is required to check credentials against a user's home area."
msgstr ""
-"Requírese autenticación para anexar ou desanexar unha imaxe de servizo "
-"portábel."
+"Requírese autenticación para comprobar as credenciais do espazo persoal dun "
+"usuario."
#: src/home/org.freedesktop.home1.policy:43
msgid "Update a home area"
-msgstr ""
+msgstr "Actualizar un espazo persoal"
#: src/home/org.freedesktop.home1.policy:44
-#, fuzzy
msgid "Authentication is required to update a user's home area."
-msgstr "Requírese autenticación para anexar un dispositivo a un asento."
+msgstr "Requírese autenticación para actualizar o espazo persoal dun usuario."
#: src/home/org.freedesktop.home1.policy:53
msgid "Resize a home area"
-msgstr ""
+msgstr "Redimensionar un espazo persoal"
#: src/home/org.freedesktop.home1.policy:54
-#, fuzzy
msgid "Authentication is required to resize a user's home area."
-msgstr "Requírese autenticación para estabelecer os servidores NTP."
+msgstr ""
+"Requírese autenticación para redimensionar o espazo persoal dun usuario."
#: src/home/org.freedesktop.home1.policy:63
msgid "Change password of a home area"
-msgstr ""
+msgstr "Cambiar contrasinal dun espazo persoal"
#: src/home/org.freedesktop.home1.policy:64
-#, fuzzy
msgid ""
"Authentication is required to change the password of a user's home area."
msgstr ""
-"Requírese autenticación para xestionar as sesións, usuarios e asentos "
-"activos."
+"Requírese autenticación para cambiar o contrasinal dun espazo persoal dun "
+"usuario."
#: src/hostname/org.freedesktop.hostname1.policy:20
msgid "Set hostname"
#: src/hostname/org.freedesktop.hostname1.policy:61
msgid "Get hardware serial number"
-msgstr ""
+msgstr "Obter o número de serie do hardware"
#: src/hostname/org.freedesktop.hostname1.policy:62
-#, fuzzy
msgid "Authentication is required to get hardware serial number."
-msgstr "Requírese autenticación para estabelecer a hora do sistema."
+msgstr "Requírese autenticación para obter o número serie do hardware."
#: src/hostname/org.freedesktop.hostname1.policy:71
-#, fuzzy
msgid "Get system description"
-msgstr "Estabelecer o fuso horario"
+msgstr "Obter a descrición do sistema"
#: src/hostname/org.freedesktop.hostname1.policy:72
-#, fuzzy
msgid "Authentication is required to get system description."
-msgstr "Requírese autenticación para estabelecer o fuso horario do sistema."
+msgstr "Requírese autenticación para obter a descrición do sistema."
#: src/import/org.freedesktop.import1.policy:22
msgid "Import a VM or container image"
"do sistema do interruptor da tapa."
#: src/login/org.freedesktop.login1.policy:117
-#, fuzzy
msgid "Allow applications to inhibit system handling of the reboot key"
msgstr ""
-"Permitir ás aplicacións inhibir a xestión do sistema da tecla de acendido"
+"Permitir ás aplicacións inhibir a xestión do sistema da tecla de reinicio"
#: src/login/org.freedesktop.login1.policy:118
-#, fuzzy
msgid ""
"Authentication is required for an application to inhibit system handling of "
"the reboot key."
msgstr "Deter o sistema cando unha aplicación solicitou a súa inhibición"
#: src/login/org.freedesktop.login1.policy:258
-#, fuzzy
msgid ""
"Authentication is required to halt the system while an application is "
"inhibiting this."
#: src/login/org.freedesktop.login1.policy:406
msgid "Change Session"
-msgstr ""
+msgstr "Cambiar sesión"
#: src/login/org.freedesktop.login1.policy:407
-#, fuzzy
msgid "Authentication is required to change the virtual terminal."
-msgstr "Requírese autenticación para deter o sistema."
+msgstr "Requírese autenticación para cambiar o terminal virtual."
#: src/machine/org.freedesktop.machine1.policy:22
msgid "Log into a local container"
#: src/network/org.freedesktop.network1.policy:143
msgid "DHCP server sends force renew message"
-msgstr ""
+msgstr "O servidor DHCP envía un mensaxe de renovación forzada"
#: src/network/org.freedesktop.network1.policy:144
-#, fuzzy
msgid "Authentication is required to send force renew message."
-msgstr "Requírese autenticación para estabelecer unha mensaxe de muro"
+msgstr "Requírese autenticación para enviar o mensaxe de renovación forzada."
#: src/network/org.freedesktop.network1.policy:154
msgid "Renew dynamic addresses"
"'$(unit)'."
#: src/core/dbus-unit.c:762
-#, fuzzy
msgid ""
"Authentication is required to freeze or thaw the processes of '$(unit)' unit."
-msgstr ""
-"Requírese autenticación para enviarlle un sinal UNIX aos procesos de "
-"'$(unit)'."
+msgstr "Requírese autenticación para conxelar o proceso da unidade '$(unit)'."
#~ msgid ""
#~ "Authentication is required to halt the system while an application asked "
# 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
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
# type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --allowlisted -d $devnode", \
SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL} tape/by-id/scsi-$env{ID_SERIAL}-changer"
# iSCSI devices from the same host have all the same ID_SERIAL,
KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
-KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --allowlisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}", OPTIONS+="link_priority=10"
KERNEL=="st*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{wwid}=="?*", ENV{ID_WWN}="$attr{wwid}"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatibility
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+ ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+ ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
+# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
+ OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatibility
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+ ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+ ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
+# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
+ OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
# virtio-blk
KERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"
KERNEL=="sd*[!0-9]|sr*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
# SCSI devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="cciss"
KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
# Previously, ata_id in the above might not be able to retrieve attributes correctly,
ACTION=="remove", GOTO="camera_end"
-SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb" , \
+SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb", \
IMPORT{builtin}="hwdb 'camera:usb:v$env{ID_VENDOR_ID}p$env{ID_MODEL_ID}:name:$attr{name}:'", \
GOTO="camera_end"
--- /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.
+
+SUBSYSTEM!="block", GOTO="iocost_end"
+
+ENV{DEVTYPE}=="partition", GOTO="iocost_end"
+
+ACTION=="remove", GOTO="iocost_end"
+
+ENV{ID_MODEL}!="", IMPORT{builtin}="hwdb 'block::name:$env{ID_MODEL}:fwrev:$env{ID_REVISION}:'"
+
+ENV{IOCOST_SOLUTIONS}!="", RUN+="iocost apply $env{DEVNAME}"
+
+LABEL="iocost_end"
'78-sound-card.rules',
'80-net-setup-link.rules',
'81-net-dhcp.rules',
+ '90-iocost.rules',
)],
[files('80-drivers.rules'),
--show-machine --unique --acquired --activatable --list
-q --quiet --verbose --expect-reply=no --auto-start=no
--allow-interactive-authorization=no --augment-creds=no
- --watch-bind=yes -j -l --full'
+ --watch-bind=yes -j -l --full --xml-interface'
[ARG]='--address -H --host -M --machine --match --timeout --size --json
--destination'
)
fi
done
- n=$(($COMP_CWORD - $i))
+ n=$((COMP_CWORD - i))
if [[ -z ${verb-} ]]; then
comps=${VERBS[*]}
local -A OPTS=(
[STANDALONE]='-q --quiet --runtime --no-reload --cat --no-pager --no-legend
--no-ask-password --enable --now -h --help --version'
- [ARG]='-p --profile --copy -H --host -M --machine'
+ [ARG]='-p --profile --copy -H --host -M --machine --extension'
)
local -A VERBS=(
--machine|-M)
comps=$( __get_machines )
;;
+ --extension)
+ comps=$( compgen -A file -- "$cur" )
+ compopt -o filenames
+ ;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
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
'--list[Do not show tree, but simple object path list]' \
{-q,--quiet}'[Do not show method call reply]'\
'--verbose[Show result values in long format]' \
+ '--xml-interface[Dump the XML description in introspect command]' \
'--json=[Show result values in long format]:format:_busctl_get_json' \
'-j[Show pretty json in interactive sessions, short json otherwise]' \
'--expect-reply=[Expect a method call reply]:boolean:(1 0)' \
log_close();
log_set_open_when_needed(true);
+ log_settle_target();
r = close_all_fds(except, n);
if (r < 0)
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0)
switch (c) {
case 'h':
if (r < 0)
return bus_log_connect_error(r, arg_transport);
- n = acquire_time_data(bus, ×);
+ n = acquire_time_data(bus, /* require_finished = */ false, ×);
if (n <= 0)
return n;
typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
- r = acquire_boot_times(bus, &boot);
+ r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
if (r < 0)
return r;
times = hashmap_get(unit_times_hashmap, id);
- r = acquire_boot_times(bus, &boot);
+ r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
if (r < 0)
return r;
if (r < 0)
return bus_log_connect_error(r, arg_transport);
- n = acquire_time_data(bus, ×);
+ n = acquire_time_data(bus, /* require_finished = */ true, ×);
if (n <= 0)
return n;
--- /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);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "analyze-image-policy.h"
+#include "analyze.h"
+#include "format-table.h"
+#include "terminal-util.h"
+
+static int table_add_designator_line(Table *table, PartitionDesignator d, PartitionPolicyFlags f) {
+ _cleanup_free_ char *q = NULL;
+ const char *color;
+ int r;
+
+ assert(table);
+ assert(f >= 0);
+
+ if (partition_policy_flags_to_string(f & _PARTITION_POLICY_USE_MASK, /* simplify= */ true, &q) < 0)
+ return log_oom();
+
+ color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() :
+ ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ==
+ (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() :
+ (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT ? ansi_highlight_red() :
+ !(f & PARTITION_POLICY_UNPROTECTED) ? ansi_highlight_green() : NULL;
+
+ if (d < 0)
+ r = table_add_many(table,
+ TABLE_STRING, "default",
+ TABLE_SET_COLOR, ansi_highlight_green(),
+ TABLE_STRING, q,
+ TABLE_SET_COLOR, color);
+ else
+ r = table_add_many(table,
+ TABLE_STRING, partition_designator_to_string(d),
+ TABLE_SET_COLOR, ansi_normal(),
+ TABLE_STRING, q,
+ TABLE_SET_COLOR, color);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ switch (f & _PARTITION_POLICY_READ_ONLY_MASK) {
+
+ case PARTITION_POLICY_READ_ONLY_ON:
+ r = table_add_many(table, TABLE_BOOLEAN, true);
+ break;
+
+ case PARTITION_POLICY_READ_ONLY_OFF:
+ r = table_add_many(table, TABLE_BOOLEAN, false);
+ break;
+
+ default:
+ r = table_add_many(table, TABLE_EMPTY);
+ break;
+ }
+ if (r < 0)
+ return table_log_add_error(r);
+
+ switch (f & _PARTITION_POLICY_GROWFS_MASK) {
+
+ case PARTITION_POLICY_GROWFS_ON:
+ r = table_add_many(table, TABLE_BOOLEAN, true);
+ break;
+
+ case PARTITION_POLICY_GROWFS_OFF:
+ r = table_add_many(table, TABLE_BOOLEAN, false);
+ break;
+
+ default:
+ r = table_add_many(table, TABLE_EMPTY);
+ break;
+ }
+
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+int verb_image_policy(int argc, char *argv[], void *userdata) {
+ int r;
+
+ for (int i = 1; i < argc; i++) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ _cleanup_(image_policy_freep) ImagePolicy *pbuf = NULL;
+ _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL;
+ const ImagePolicy *p;
+
+ /* NB: The magic '@' strings are not officially documented for now, since we might change
+ * around defaults (and in particular where precisely to reuse policy). We should document
+ * them once the dust has settled a bit. For now it's just useful for debugging and
+ * introspect our own defaults without guaranteeing API safety. */
+ if (streq(argv[i], "@sysext"))
+ p = &image_policy_sysext;
+ else if (streq(argv[i], "@sysext-strict"))
+ p = &image_policy_sysext_strict;
+ else if (streq(argv[i], "@container"))
+ p = &image_policy_container;
+ else if (streq(argv[i], "@service"))
+ p = &image_policy_service;
+ else if (streq(argv[i], "@host"))
+ p = &image_policy_host;
+ else {
+ r = image_policy_from_string(argv[i], &pbuf);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse image policy '%s': %m", argv[i]);
+
+ p = pbuf;
+ }
+
+ r = image_policy_to_string(p, /* simplify= */ false, &as_string);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]);
+
+ r = image_policy_to_string(p, /* simplify= */ true, &as_string_simplified);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]);
+
+ pager_open(arg_pager_flags);
+
+ if (streq(as_string, as_string_simplified))
+ printf("Analyzing policy: %s%s%s\n", ansi_highlight_magenta_underline(), as_string, ansi_normal());
+ else
+ printf("Analyzing policy: %s%s%s\n"
+ " Long form: %s%s%s\n",
+ ansi_highlight(), as_string_simplified, ansi_normal(),
+ ansi_grey(), as_string, ansi_normal());
+
+ table = table_new("partition", "mode", "read-only", "growfs");
+ if (!table)
+ return log_oom();
+
+ (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ PartitionPolicyFlags f = image_policy_get_exhaustively(p, d);
+ assert(f >= 0);
+
+ r = table_add_designator_line(table, d, f);
+ if (r < 0)
+ return r;
+ }
+
+ r = table_add_designator_line(table, _PARTITION_DESIGNATOR_INVALID, image_policy_default(p));
+ if (r < 0)
+ return r;
+
+ putc('\n', stdout);
+
+ r = table_print(table, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_image_policy(int argc, char *argv[], void *userdata);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
- n = acquire_boot_times(bus, &boot);
+ n = acquire_boot_times(bus, /* require_finished = */ true, &boot);
if (n < 0)
return n;
return n;
}
- n = acquire_time_data(bus, ×);
+ n = acquire_time_data(bus, /* require_finished = */ true, ×);
if (n <= 0)
return n;
profile = profile_path;
}
- r = copy_file(profile, dropin, 0, 0644, 0, 0, 0);
+ r = copy_file(profile, dropin, 0, 0644, 0);
if (r < 0)
return log_error_errno(r, "Failed to copy: %m");
}
return 0;
}
+static int syscall_set_add(Set **s, const SyscallFilterSet *set) {
+ int r;
+
+ assert(s);
+
+ if (!set)
+ return 0;
+
+ NULSTR_FOREACH(sc, set->value) {
+ if (sc[0] == '@')
+ continue;
+
+ r = set_put_strdup(s, sc);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
if (!set)
return;
- NULSTR_FOREACH(syscall, set->value) {
- if (syscall[0] == '@')
+ NULSTR_FOREACH(sc, set->value) {
+ if (sc[0] == '@')
continue;
- free(set_remove(s, syscall));
+ free(set_remove(s, sc));
}
}
int verb_syscall_filters(int argc, char *argv[], void *userdata) {
bool first = true;
+ int r;
pager_open(arg_pager_flags);
_cleanup_set_free_ Set *kernel = NULL, *known = NULL;
int k = 0; /* explicit initialization to appease gcc */
- NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
- if (set_put_strdup(&known, sys) < 0)
- return log_oom();
+ r = syscall_set_add(&known, syscall_filter_sets + SYSCALL_FILTER_SET_KNOWN);
+ if (r < 0)
+ return log_error_errno(r, "Failed to prepare set of known system calls: %m");
if (!arg_quiet)
k = load_kernel_syscalls(&kernel);
}
}
-int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
+static int log_not_finished(usec_t finish_time) {
+ return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
+ "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
+ "Please try again later.\n"
+ "Hint: Use 'systemctl%s list-jobs' to see active jobs",
+ finish_time,
+ arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user");
+}
+
+int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) {
static const struct bus_properties_map property_map[] = {
{ "FirmwareTimestampMonotonic", "t", NULL, offsetof(BootTimes, firmware_time) },
{ "LoaderTimestampMonotonic", "t", NULL, offsetof(BootTimes, loader_time) },
static bool cached = false;
int r;
- if (cached)
- goto finish;
+ if (cached) {
+ if (require_finished && times.finish_time <= 0)
+ return log_not_finished(times.finish_time);
+
+ if (ret)
+ *ret = ×
+ return 0;
+ }
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
if (r < 0)
return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
- if (times.finish_time <= 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
- "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
- "Please try again later.\n"
- "Hint: Use 'systemctl%s list-jobs' to see active jobs",
- times.finish_time,
- arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user");
+ if (require_finished && times.finish_time <= 0)
+ return log_not_finished(times.finish_time);
if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && times.security_start_time > 0) {
/* security_start_time is set when systemd is not running under container environment. */
times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
times.userspace_time = times.security_start_time = times.security_finish_time = 0;
- subtract_timestamp(×.finish_time, times.reverse_offset);
+ if (times.finish_time > 0)
+ subtract_timestamp(×.finish_time, times.reverse_offset);
subtract_timestamp(×.generators_start_time, times.reverse_offset);
subtract_timestamp(×.generators_finish_time, times.reverse_offset);
cached = true;
-finish:
- *ret = ×
+ if (ret)
+ *ret = ×
return 0;
}
BootTimes *t;
int r;
- r = acquire_boot_times(bus, &t);
+ r = acquire_boot_times(bus, /* require_finished = */ true, &t);
if (r < 0)
return r;
return mfree(t);
}
-int acquire_time_data(sd_bus *bus, UnitTimes **out) {
+int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) {
static const struct bus_properties_map property_map[] = {
{ "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
{ "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
- BootTimes *boot_times = NULL;
+ BootTimes *boot_times;
size_t c = 0;
UnitInfo u;
int r;
- r = acquire_boot_times(bus, &boot_times);
+ r = acquire_boot_times(bus, require_finished, &boot_times);
if (r < 0)
return r;
usec_t time;
} UnitTimes;
-int acquire_boot_times(sd_bus *bus, BootTimes **ret);
+int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret);
int pretty_boot_time(sd_bus *bus, char **ret);
UnitTimes* unit_times_free_array(UnitTimes *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);
-int acquire_time_data(sd_bus *bus, UnitTimes **out);
+int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out);
if (!dst)
return -ENOMEM;
- r = copy_file(src, dst, 0, 0644, 0, 0, COPY_REFLINK);
+ r = copy_file(src, dst, 0, 0644, COPY_REFLINK);
if (r < 0)
return r;
#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 "analyze-image-policy.h"
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
char *arg_profile = NULL;
bool arg_legend = true;
bool arg_table = false;
+ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
int acquire_bus(sd_bus **bus, bool *use_full_bus) {
int r;
" 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"
" -q --quiet Do not emit hints\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ARG_REQUIRE,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_SYSTEM,
ARG_USER,
ARG_GLOBAL,
{ "require", no_argument, NULL, ARG_REQUIRE },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
{ "offline", required_argument, NULL, ARG_OFFLINE },
{ "threshold", required_argument, NULL, ARG_THRESHOLD },
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_SYSTEM:
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
break;
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 },
+ { "image-policy", 2, 2, 0, verb_image_policy },
{}
};
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
DISSECT_IMAGE_READ_ONLY,
extern char *arg_profile;
extern bool arg_legend;
extern bool arg_table;
+extern ImagePolicy *arg_image_policy;
int acquire_bus(sd_bus **bus, bool *use_full_bus);
'analyze-dot.c',
'analyze-dump.c',
'analyze-exit-status.c',
+ 'analyze-fdstore.c',
'analyze-filesystems.c',
+ 'analyze-image-policy.c',
'analyze-inspect-elf.c',
'analyze-log-control.c',
'analyze-malloc.c',
/* Note the asymmetry: the long option --echo= allows an optional argument, the short option does
* not. */
+
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0)
switch (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)
+++ /dev/null
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <dirent.h>
-#include <stdio.h>
-
-#include "stat-util.h"
-
-typedef enum ChaseSymlinksFlags {
- CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */
- CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */
- CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */
- CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
- CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */
- CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */
- CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's
- * right-most component refers to symlink, return O_PATH fd of the symlink. */
- CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered.
- * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
- CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
- * relative to the given directory fd instead of root. */
- CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
- CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the
- * full path is still stored in ret_path and only the returned
- * file descriptor will point to the parent directory. */
- CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. */
-} ChaseSymlinksFlags;
-
-bool unsafe_transition(const struct stat *a, const struct stat *b);
-
-/* How many iterations to execute before returning -ELOOP */
-#define CHASE_SYMLINKS_MAX 32
-
-int chase_symlinks(const char *path_with_prefix, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, int *ret_fd);
-
-int chase_symlinks_and_open(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
-int chase_symlinks_and_opendir(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, DIR **ret_dir);
-int chase_symlinks_and_stat(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat);
-int chase_symlinks_and_access(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path);
-int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseSymlinksFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
-int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path);
-
-int chase_symlinks_at(int dir_fd, const char *path, ChaseSymlinksFlags flags, char **ret_path, int *ret_fd);
-int chase_symlinks_at_and_open(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
#include <linux/magic.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
}
-static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFlags flags) {
+static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
_cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
struct stat st;
strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
}
-static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags flags) {
+static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
_cleanup_free_ char *n1 = NULL;
if (!FLAGS_SET(flags, CHASE_WARN))
strna(n1), path);
}
-static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
+static int log_prohibited_symlink(int fd, ChaseFlags flags) {
_cleanup_free_ char *n1 = NULL;
assert(fd >= 0);
strna(n1));
}
-int chase_symlinks_at(
- int dir_fd,
- const char *path,
- ChaseSymlinksFlags 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_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
+ unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
bool exists = true, append_trail_slash = false;
- struct stat previous_stat;
+ struct stat st; /* stat obtained from fd */
const char *todo;
int r;
assert(path);
assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
+ assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
+ assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME));
+ assert(!FLAGS_SET(flags, CHASE_MKDIR_0755) || (flags & (CHASE_NONEXISTENT | CHASE_PARENT)) != 0);
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
/* Either the file may be missing, or we return an fd to the final object, but both make no sense */
- if ((flags & CHASE_NONEXISTENT))
+ if (FLAGS_SET(flags, CHASE_NONEXISTENT))
assert(!ret_fd);
- if ((flags & CHASE_STEP))
+ if (FLAGS_SET(flags, CHASE_STEP))
assert(!ret_fd);
if (isempty(path))
path = ".";
/* This function resolves symlinks of the path relative to the given directory file descriptor. If
- * CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
+ * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
* are resolved relative to the given directory file descriptor. Otherwise, they are resolved
* relative to the root directory of the host.
*
* given directory file descriptor, even if it is absolute. If the given directory file descriptor is
* AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
*
- * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this
- * functions returns a relative path in "ret_path" because openat() like functions generally ignore
- * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is
- * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because
- * otherwise, if the caller passes the returned relative path to another openat() like function, it
- * would be resolved relative to the current working directory instead of to "/".
+ * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function
+ * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat()
+ * like functions generally ignore the directory fd if they are provided with an absolute path. When
+ * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file
+ * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When
+ * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path"
+ * because otherwise, if the caller passes the returned relative path to another openat() like
+ * function, it would be resolved relative to the current working directory instead of to "/".
+ *
+ * Summary about the result path:
+ * - "dir_fd" points to the root directory
+ * → result will be absolute
+ * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set
+ * → relative
+ * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set
+ * → relative when all resolved symlinks are relative, otherwise absolute
+ * - "dir_fd" is AT_FDCWD, and "path" is absolute
+ * → absolute
+ * - "dir_fd" is AT_FDCWD, and "path" is relative
+ * → relative when all resolved symlinks are relative, otherwise absolute
*
* Algorithmically this operates on two path buffers: "done" are the components of the path we
* already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
* 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)) &&
/* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
* set and doesn't care about any of the other special features we provide either. */
- r = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+ r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
if (r < 0)
return -errno;
return 0;
}
- if (!buffer) {
- buffer = strdup(path);
- if (!buffer)
- return -ENOMEM;
- }
+ buffer = strdup(path);
+ if (!buffer)
+ return -ENOMEM;
/* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
- * a relative path would be interpreted relative to the current working directory. */
- bool need_absolute = dir_fd == AT_FDCWD && path_is_absolute(path);
+ * a relative path would be interpreted relative to the current working directory. Also, let's make
+ * the result absolute when the file descriptor of the root directory is specified. */
+ bool need_absolute = (dir_fd == AT_FDCWD && path_is_absolute(path)) || dir_fd_is_root(dir_fd) > 0;
if (need_absolute) {
done = strdup("/");
if (!done)
}
/* 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);
if (fd < 0)
return -errno;
- if (fstat(fd, &previous_stat) < 0)
+ if (fstat(fd, &st) < 0)
return -errno;
- if (flags & CHASE_TRAIL_SLASH)
+ if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
append_trail_slash = ENDSWITH_SET(buffer, "/", "/.");
for (todo = buffer;;) {
_cleanup_free_ char *first = NULL;
_cleanup_close_ int child = -EBADF;
- struct stat st;
+ struct stat st_child;
const char *e;
r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
if (path_equal(first, "..")) {
_cleanup_free_ char *parent = NULL;
_cleanup_close_ int fd_parent = -EBADF;
+ struct stat st_parent;
/* If we already are at the top, then going up will not change anything. This is
* in-line with how the kernel handles this. */
if (fd_parent < 0)
return -errno;
- if (fstat(fd_parent, &st) < 0)
+ if (fstat(fd_parent, &st_parent) < 0)
return -errno;
- /* If we opened the same directory, that means we're at the host root directory, so
+ /* If we opened the same directory, that _may_ indicate that we're at the host root
+ * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
* going up won't change anything. */
- if (st.st_dev == previous_stat.st_dev && st.st_ino == previous_stat.st_ino)
- continue;
+ if (stat_inode_same(&st_parent, &st)) {
+ r = dir_fd_is_root(fd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
r = path_extract_directory(done, &parent);
if (r >= 0 || r == -EDESTADDRREQ)
} else
return r;
- if (flags & CHASE_STEP)
+ if (FLAGS_SET(flags, CHASE_STEP))
goto chased_one;
- if (flags & CHASE_SAFE) {
- if (unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(fd, fd_parent, path, flags);
-
- previous_stat = st;
- }
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st, &st_parent))
+ return log_unsafe_transition(fd, fd_parent, path, flags);
if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
break;
+ /* update fd and stat */
+ st = st_parent;
close_and_replace(fd, fd_parent);
-
continue;
}
return r;
if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
- child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755);
+ child = xopenat(fd, first, O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0755);
if (child < 0)
return child;
} else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
return -ENOMEM;
break;
- } else if (flags & CHASE_NONEXISTENT) {
+ } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
if (!path_extend(&done, first, todo))
return -ENOMEM;
return r;
}
- if (fstat(child, &st) < 0)
+ if (fstat(child, &st_child) < 0)
return -errno;
- if ((flags & CHASE_SAFE) &&
- unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(fd, child, path, flags);
- previous_stat = st;
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st, &st_child))
+ return log_unsafe_transition(fd, child, path, flags);
- if ((flags & CHASE_NO_AUTOFS) &&
+ if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
return log_autofs_mount_point(child, path, flags);
- if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
+ if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
_cleanup_free_ char *destination = NULL;
- if (flags & CHASE_PROHIBIT_SYMLINKS)
+ if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
return log_prohibited_symlink(child, flags);
/* This is a symlink, in this case read the destination. But let's make sure we
if (fd < 0)
return fd;
- if (flags & CHASE_SAFE) {
- if (fstat(fd, &st) < 0)
- return -errno;
+ if (fstat(fd, &st) < 0)
+ return -errno;
- if (unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(child, fd, path, flags);
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st_child, &st))
+ return log_unsafe_transition(child, fd, path, flags);
- previous_stat = st;
- }
+ /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
+ * outside of the specified dir_fd. Let's make the result absolute. */
+ if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
+ need_absolute = true;
r = free_and_strdup(&done, need_absolute ? "/" : NULL);
if (r < 0)
free_and_replace(buffer, destination);
todo = buffer;
- if (flags & CHASE_STEP)
+ if (FLAGS_SET(flags, CHASE_STEP))
goto chased_one;
continue;
break;
/* And iterate again, but go one directory further down. */
+ st = st_child;
close_and_replace(fd, child);
}
- if (flags & CHASE_PARENT) {
- r = fd_verify_directory(fd);
+ if (FLAGS_SET(flags, CHASE_PARENT)) {
+ r = stat_verify_directory(&st);
if (r < 0)
return r;
}
if (ret_path) {
+ if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
+ _cleanup_free_ char *f = NULL;
+
+ r = path_extract_filename(done, &f);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+
+ /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
+ free_and_replace(done, f);
+ }
+
if (!done) {
done = strdup(append_trail_slash ? "./" : ".");
if (!done)
*ret_fd = TAKE_FD(fd);
}
- if (flags & CHASE_STEP)
+ if (FLAGS_SET(flags, CHASE_STEP))
return 1;
return exists;
return 0;
}
-int chase_symlinks(
- const char *path,
- const char *original_root,
- ChaseSymlinksFlags flags,
- char **ret_path,
- int *ret_fd) {
-
- _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
+int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
+ _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
_cleanup_close_ int fd = -EBADF, pfd = -EBADF;
int r;
if (isempty(path))
return -EINVAL;
- /* A root directory of "/" or "" is identical to none */
- if (empty_or_root(original_root))
- original_root = NULL;
-
- if (original_root) {
- r = path_make_absolute_cwd(original_root, &root);
+ /* A root directory of "/" or "" is identical to "/". */
+ if (empty_or_root(root))
+ root = "/";
+ else {
+ r = path_make_absolute_cwd(root, &root_abs);
if (r < 0)
return r;
/* Simplify the root directory, so that it has no duplicate slashes and nothing at the
- * end. While we won't resolve the root path we still simplify it. Note that dropping the
- * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
- * anyway. Moreover at the end of this function after processing everything we'll always turn
- * the empty string back to "/". */
- delete_trailing_chars(root, "/");
- path_simplify(root);
-
- if (flags & CHASE_PREFIX_ROOT) {
+ * end. While we won't resolve the root path we still simplify it. */
+ root = path_simplify(root_abs);
+
+ assert(path_is_absolute(root));
+ assert(!empty_or_root(root));
+
+ if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
absolute = path_join(root, path);
if (!absolute)
return -ENOMEM;
}
+
+ flags |= CHASE_AT_RESOLVE_IN_ROOT;
}
if (!absolute) {
return r;
}
- path = path_startswith(absolute, empty_to_root(root));
+ path = path_startswith(absolute, 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));
+ return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
+ SYNTHETIC_ERRNO(ECHRNG),
+ "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+ absolute, root);
- fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
+ fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
- flags |= CHASE_AT_RESOLVE_IN_ROOT;
- flags &= ~CHASE_PREFIX_ROOT;
-
- r = chase_symlinks_at(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;
if (ret_path) {
- _cleanup_free_ char *q = NULL;
+ if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
- q = path_join(empty_to_root(root), p);
- if (!q)
- return -ENOMEM;
+ /* When "root" points to the root directory, the result of chaseat() is always
+ * absolute, hence it is not necessary to prefix with the root. When "root" points to
+ * a non-root directory, the result path is always normalized and relative, hence
+ * we can simply call path_join() and not necessary to call path_simplify().
+ * Note that the result of chaseat() may start with "." (more specifically, it may be
+ * "." or "./"), and we need to drop "." in that case. */
- path_simplify(q);
+ if (empty_or_root(root))
+ assert(path_is_absolute(p));
+ else {
+ char *q;
- if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/."))
- if (!strextend(&q, "/"))
- return -ENOMEM;
+ assert(!path_is_absolute(p));
+
+ q = path_join(root, p + (*p == '.'));
+ if (!q)
+ return -ENOMEM;
+
+ free_and_replace(p, q);
+ }
+ }
- *ret_path = TAKE_PTR(q);
+ *ret_path = TAKE_PTR(p);
}
if (ret_fd)
return r;
}
-int chase_symlinks_and_open(
- const char *path,
- const char *root,
- ChaseSymlinksFlags chase_flags,
- int open_flags,
- char **ret_path) {
+int chaseat_prefix_root(const char *path, const char *root, char **ret) {
+ char *q;
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ /* This is mostly for prefixing the result of chaseat(). */
+ if (!path_is_absolute(path)) {
+ _cleanup_free_ char *root_abs = NULL;
+
+ /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
+ assert(!empty_or_root(root));
+
+ r = path_make_absolute_cwd(root, &root_abs);
+ if (r < 0)
+ return r;
+
+ root = path_simplify(root_abs);
+
+ q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
+ } else
+ q = strdup(path);
+ if (!q)
+ return -ENOMEM;
+
+ *ret = q;
+ return 0;
+}
+
+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;
open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
mode));
- r = chase_symlinks(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
+ r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
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_symlinks_and_opendir(
- const char *path,
- const char *root,
- ChaseSymlinksFlags 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;
}
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
return 0;
}
-int chase_symlinks_and_stat(
- const char *path,
- const char *root,
- ChaseSymlinksFlags 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;
assert(ret_stat);
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
/* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
- if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
- return -errno;
-
- return 1;
- }
-
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
if (ret_path)
*ret_path = TAKE_PTR(p);
- return 1;
+ return 0;
}
-int chase_symlinks_and_access(
- const char *path,
- const char *root,
- ChaseSymlinksFlags 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;
assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
/* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
- if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
- return -errno;
-
- return 1;
- }
-
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
if (ret_path)
*ret_path = TAKE_PTR(p);
- return 1;
+ return 0;
}
-int chase_symlinks_and_fopen_unlocked(
+int chase_and_fopen_unlocked(
const char *path,
const char *root,
- ChaseSymlinksFlags chase_flags,
+ ChaseFlags chase_flags,
const char *open_flags,
char **ret_path,
FILE **ret_file) {
if (mode_flags < 0)
return mode_flags;
- fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
+ fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
if (fd < 0)
return fd;
return 0;
}
-int chase_symlinks_and_unlink(
- const char *path,
- const char *root,
- ChaseSymlinksFlags 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;
assert(path);
assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
- fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
+ fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
if (fd < 0)
return fd;
return 0;
}
-int chase_symlinks_at_and_open(
- int dir_fd,
- const char *path,
- ChaseSymlinksFlags chase_flags,
- int open_flags,
- char **ret_path) {
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
+ int pfd, r;
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+ r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
+ if (r < 0)
+ return r;
+
+ return pfd;
+}
+
+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;
open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
mode));
- r = chase_symlinks_at(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
+ r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
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) {
+ _cleanup_close_ int path_fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ DIR *d;
+ int r;
+
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+ assert(ret_dir);
+
+ if (dir_fd == AT_FDCWD && !ret_path &&
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ /* Shortcut this call if none of the special features of this call are requested */
+ d = opendir(path);
+ if (!d)
+ return -errno;
+
+ *ret_dir = d;
+ return 0;
+ }
+
+ r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
+
+ d = xopendirat(path_fd, ".", O_NOFOLLOW);
+ if (!d)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ *ret_dir = d;
+ return 0;
+}
+
+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;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+ assert(ret_stat);
+
+ if (dir_fd == AT_FDCWD && !ret_path &&
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
+ /* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
+
+ r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
+
+ if (fstat(path_fd, ret_stat) < 0)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ return 0;
+}
+
+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;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+ if (dir_fd == AT_FDCWD && !ret_path &&
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
+ /* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
+
+ r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
+
+ r = access_fd(path_fd, access_mode);
+ if (r < 0)
+ return r;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ return 0;
+}
+
+int chase_and_fopenat_unlocked(
+ int dir_fd,
+ const char *path,
+ ChaseFlags chase_flags,
+ const char *open_flags,
+ char **ret_path,
+ FILE **ret_file) {
+
+ _cleanup_free_ char *final_path = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int mode_flags, r;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
+ assert(open_flags);
+ assert(ret_file);
+
+ mode_flags = fopen_mode_to_flags(open_flags);
+ if (mode_flags < 0)
+ return mode_flags;
+
+ fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
+ if (fd < 0)
+ return fd;
+
+ r = take_fdopen_unlocked(&fd, open_flags, ret_file);
+ if (r < 0)
+ return r;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(final_path);
+
+ return 0;
+}
+
+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;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
+
+ fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
+ if (fd < 0)
+ return fd;
+
+ r = path_extract_filename(p, &fname);
+ if (r < 0)
+ return r;
+
+ if (unlinkat(fd, fname, unlink_flags) < 0)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ return 0;
+}
+
+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)));
+
+ r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
+ if (r < 0)
+ return r;
+
+ return pfd;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <dirent.h>
+#include <stdio.h>
+
+#include "stat-util.h"
+
+typedef enum ChaseFlags {
+ CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */
+ CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */
+ CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */
+ CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
+ CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */
+ CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */
+ CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's
+ * right-most component refers to symlink, return O_PATH fd of the symlink. */
+ CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered.
+ * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
+ CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
+ * relative to the given directory fd instead of root. */
+ CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
+ CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the
+ * full path is still stored in ret_path and only the returned
+ * file descriptor will point to the parent directory. Note that
+ * the result path is the root or '.', then the file descriptor
+ * also points to the result path even if this flag is set.
+ * When this specified, chase() will succeed with 1 even if the
+ * file points to the last path component does not exist. */
+ CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. This
+ * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT.
+ * Note, chase_and_open() or friends always add CHASE_PARENT flag
+ * when internally call chase(), hence CHASE_MKDIR_0755 can be
+ * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */
+ CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */
+} ChaseFlags;
+
+bool unsafe_transition(const struct stat *a, const struct stat *b);
+
+/* How many iterations to execute before returning -ELOOP */
+#define CHASE_MAX 32
+
+int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd);
+
+int chaseat_prefix_root(const char *path, const char *root, char **ret);
+
+int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename);
+
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd);
+
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename);
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
return -ENOBUFS;
*dst_size = out_pos;
- return COMPRESSION_XZ;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
unaligned_write_le64(dst, src_size);
*dst_size = r + 8;
- return COMPRESSION_LZ4;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
return zstd_ret_to_errno(k);
*dst_size = k;
- return COMPRESSION_ZSTD;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
s.total_in, s.total_out,
(double) s.total_out / s.total_in * 100);
- return COMPRESSION_XZ;
+ return 0;
}
}
}
total_in, total_out,
(double) total_out / total_in * 100);
- return COMPRESSION_LZ4;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes)",
in_bytes, max_bytes - left);
- return COMPRESSION_ZSTD;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
#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,
int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size);
int decompress_stream_zstd(int fdf, int fdt, uint64_t max_size);
-static inline int compress_blob_explicit(
+static inline int compress_blob(
Compression compression,
const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size) {
}
}
-#define compress_blob(src, src_size, dst, dst_alloc_size, dst_size) \
- compress_blob_explicit( \
- DEFAULT_COMPRESSION, \
- src, src_size, \
- dst, dst_alloc_size, dst_size)
-
static inline int compress_stream(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
switch (DEFAULT_COMPRESSION) {
case COMPRESSION_ZSTD:
#include <stdio.h>
#include <stdlib.h>
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "constants.h"
#include "dirent-util.h"
#include "terminal-util.h"
static int files_add(
- Hashmap **h,
+ DIR *dir,
+ const char *dirpath,
+ Hashmap **files,
Set **masked,
const char *suffix,
- const char *root,
- unsigned flags,
- const char *path) {
+ unsigned flags) {
- _cleanup_free_ char *dirpath = NULL;
- _cleanup_closedir_ DIR *dir = NULL;
int r;
- assert(h);
+ assert(dir);
+ assert(dirpath);
+ assert(files);
assert(masked);
- assert(path);
-
- r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &dirpath, &dir);
- if (r == -ENOENT)
- return 0;
- if (r < 0)
- return log_debug_errno(r, "Failed to open directory '%s/%s': %m", empty_or_root(root) ? "" : root, dirpath);
FOREACH_DIRENT(de, dir, return -errno) {
_cleanup_free_ char *n = NULL, *p = NULL;
continue;
/* Has this file already been found in an earlier directory? */
- if (hashmap_contains(*h, de->d_name)) {
+ if (hashmap_contains(*files, de->d_name)) {
log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
continue;
}
return -ENOMEM;
if ((flags & CONF_FILES_BASENAME))
- r = hashmap_ensure_put(h, &string_hash_ops_free, n, n);
+ r = hashmap_ensure_put(files, &string_hash_ops_free, n, n);
else {
p = path_join(dirpath, de->d_name);
if (!p)
return -ENOMEM;
- r = hashmap_ensure_put(h, &string_hash_ops_free_free, n, p);
+ r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p);
}
if (r < 0)
return r;
}
static int base_cmp(char * const *a, char * const *b) {
- return strcmp(basename(*a), basename(*b));
+ assert(a);
+ assert(b);
+ return path_compare_filename(*a, *b);
}
-static int conf_files_list_strv_internal(
+static int copy_and_sort_files_from_hashmap(Hashmap *fh, char ***ret) {
+ _cleanup_free_ char **sv = NULL;
+ char **files;
+
+ assert(ret);
+
+ sv = hashmap_get_strv(fh);
+ if (!sv)
+ return -ENOMEM;
+
+ /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
+ files = strv_copy(sv);
+ if (!files)
+ return -ENOMEM;
+
+ typesafe_qsort(files, strv_length(files), base_cmp);
+
+ *ret = files;
+ return 0;
+}
+
+int conf_files_list_strv(
char ***ret,
const char *suffix,
const char *root,
unsigned flags,
- char **dirs) {
+ const char * const *dirs) {
_cleanup_hashmap_free_ Hashmap *fh = NULL;
_cleanup_set_free_ Set *masked = NULL;
- _cleanup_strv_free_ char **files = NULL;
- _cleanup_free_ char **sv = NULL;
int r;
assert(ret);
- /* This alters the dirs string array */
- if (!path_strv_resolve_uniq(dirs, root))
- return -ENOMEM;
-
STRV_FOREACH(p, dirs) {
- r = files_add(&fh, &masked, suffix, root, flags, *p);
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ r = chase_and_opendir(*p, root, CHASE_PREFIX_ROOT, &path, &dir);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+ continue;
+ }
+
+ r = files_add(dir, path, &fh, &masked, suffix, flags);
if (r == -ENOMEM)
return r;
if (r < 0)
- log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p);
+ log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
}
- sv = hashmap_get_strv(fh);
- if (!sv)
- return -ENOMEM;
+ return copy_and_sort_files_from_hashmap(fh, ret);
+}
- /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
- files = strv_copy(sv);
- if (!files)
- return -ENOMEM;
+int conf_files_list_strv_at(
+ char ***ret,
+ const char *suffix,
+ int rfd,
+ unsigned flags,
+ const char * const *dirs) {
- typesafe_qsort(files, strv_length(files), base_cmp);
- *ret = TAKE_PTR(files);
+ _cleanup_hashmap_free_ Hashmap *fh = NULL;
+ _cleanup_set_free_ Set *masked = NULL;
+ int r;
- return 0;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(ret);
+
+ STRV_FOREACH(p, dirs) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+ continue;
+ }
+
+ r = files_add(dir, path, &fh, &masked, suffix, flags);
+ if (r == -ENOMEM)
+ return r;
+ if (r < 0)
+ log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
+ }
+
+ return copy_and_sort_files_from_hashmap(fh, ret);
}
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) {
return r;
}
-int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs) {
- _cleanup_strv_free_ char **copy = NULL;
-
- assert(ret);
-
- copy = strv_copy((char**) dirs);
- if (!copy)
- return -ENOMEM;
+int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
+ return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
+}
- return conf_files_list_strv_internal(ret, suffix, root, flags, copy);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir) {
+ return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
}
-int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
- _cleanup_strv_free_ char **dirs = NULL;
+int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+ _cleanup_strv_free_ char **d = NULL;
assert(ret);
- dirs = strv_new(dir);
- if (!dirs)
+ d = strv_split_nulstr(dirs);
+ if (!d)
return -ENOMEM;
- return conf_files_list_strv_internal(ret, suffix, root, flags, dirs);
+ return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
}
-int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs) {
_cleanup_strv_free_ char **d = NULL;
assert(ret);
if (!d)
return -ENOMEM;
- return conf_files_list_strv_internal(ret, suffix, root, flags, d);
+ return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
}
int conf_files_list_with_replacement(
};
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir);
int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs);
+int conf_files_list_strv_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char * const *dirs);
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs);
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs);
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path);
int conf_files_list_with_replacement(
const char *root,
/* 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)
#include <string.h>
#include <sys/stat.h>
-#include "chase-symlinks.h"
+#include "chase.h"
#include "devnum-util.h"
#include "parse-util.h"
#include "path-util.h"
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);
if (r < 0)
return r;
- return chase_symlinks(p, NULL, 0, ret, NULL);
+ return chase(p, NULL, 0, ret, NULL);
}
int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) {
}
#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;
+}
return r;
}
+int parse_env_file_fdv(int fd, const char *fname, va_list ap) {
+ _cleanup_fclose_ FILE *f = NULL;
+ va_list aq;
+ int r;
+
+ assert(fd >= 0);
+
+ r = fdopen_independent(fd, "re", &f);
+ if (r < 0)
+ return r;
+
+ va_copy(aq, ap);
+ r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
+ va_end(aq);
+ return r;
+}
+
int parse_env_file_sentinel(
FILE *f,
const char *fname,
const char *fname, /* only used for logging */
...) {
- _cleanup_close_ int fd_ro = -EBADF;
- _cleanup_fclose_ FILE *f = NULL;
va_list ap;
int r;
assert(fd >= 0);
- fd_ro = fd_reopen(fd, O_CLOEXEC | O_RDONLY);
- if (fd_ro < 0)
- return fd_ro;
-
- f = fdopen(fd_ro, "re");
- if (!f)
- return -errno;
-
- TAKE_FD(fd_ro);
-
va_start(ap, fname);
- r = parse_env_filev(f, fname, ap);
+ r = parse_env_file_fdv(fd, fname, ap);
va_end(ap);
return r;
int r;
assert(f || fname);
+ assert(ret);
r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
if (r < 0)
return 0;
}
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(fd >= 0);
+
+ r = fdopen_independent(fd, "re", &f);
+ if (r < 0)
+ return r;
+
+ return load_env_file_pairs(f, fname, ret);
+}
+
static int merge_env_file_push(
const char *filename, unsigned line,
const char *key, char *value,
#include "macro.h"
int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
#define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
#define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL)
int load_env_file(FILE *f, const char *fname, char ***ret);
int load_env_file_pairs(FILE *f, const char *fname, char ***ret);
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret);
int merge_env_file(char ***env, FILE *f, const char *fname);
return strv_env_replace_consume(l, p);
}
+int _strv_env_assign_many(char ***l, ...) {
+ va_list ap;
+ int r;
+
+ assert(l);
+
+ va_start(ap, l);
+ for (;;) {
+ const char *key, *value;
+
+ key = va_arg(ap, const char *);
+ if (!key)
+ break;
+
+ if (!env_name_is_valid(key)) {
+ va_end(ap);
+ return -EINVAL;
+ }
+
+ value = va_arg(ap, const char *);
+ if (!value) {
+ strv_env_unset(*l, key);
+ continue;
+ }
+
+ char *p = strjoin(key, "=", value);
+ if (!p) {
+ va_end(ap);
+ return -ENOMEM;
+ }
+
+ r = strv_env_replace_consume(l, p);
+ if (r < 0) {
+ va_end(ap);
+ return r;
+ }
+ }
+ va_end(ap);
+
+ return 0;
+}
+
char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
assert(name);
int strv_env_replace_strdup(char ***l, const char *assignment);
int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
int strv_env_assign(char ***l, const char *key, const char *value);
+int _strv_env_assign_many(char ***l, ...) _sentinel_;
+#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
char *strv_env_get(char **x, const char *n) _pure_;
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
int fd_reopen(int fd, int flags) {
int new_fd, r;
+ assert(fd >= 0 || fd == AT_FDCWD);
+
/* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
* turn O_RDWR fds into O_RDONLY fds.
*
* This doesn't work on sockets (since they cannot be open()ed, ever).
*
- * This implicitly resets the file read index to 0. */
-
- if (FLAGS_SET(flags, O_DIRECTORY)) {
+ * This implicitly resets the file read index to 0.
+ *
+ * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd.
+ *
+ * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used
+ * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without
+ * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you
+ * can reopen it only if you pass O_PATH again. */
+
+ if (FLAGS_SET(flags, O_NOFOLLOW))
+ /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is primarily implemented
+ * via a symlink-based interface in /proc/self/fd. Let's refuse this here early. Note that
+ * the kernel would generate ELOOP here too, hence this manual check is mostly redundant –
+ * the only reason we add it here is so that the O_DIRECTORY special case (see below) behaves
+ * the same way as the non-O_DIRECTORY case. */
+ return -ELOOP;
+
+ if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) {
/* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole
* magic /proc/ directory, and make ourselves independent of that being mounted. */
- new_fd = openat(fd, ".", flags);
+ new_fd = openat(fd, ".", flags | O_DIRECTORY);
if (new_fd < 0)
return -errno;
return new_fd;
}
+ assert(fd >= 0);
+
new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
if (new_fd < 0) {
if (errno != ENOENT)
if (r < 0)
return r;
+ r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
+ if (r < 0)
+ return r;
+
+ /* First, compare inode. If these are different, the fd does not point to the root directory "/". */
+ if (!statx_inode_same(&st.sx, &pst.sx))
+ return false;
+
+ /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+ * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+ * following could be used to trick us:
+ *
+ * $ mkdir /tmp/x /tmp/x/y
+ * $ mount --bind /tmp/x /tmp/x/y
+ *
+ * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old
+ * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious
+ * mount points in an early boot stage, and silently skip the following check. */
+
if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
int mntid;
r = path_get_mnt_id_at(dir_fd, "", &mntid);
+ if (r == -ENOSYS)
+ return true; /* skip the mount ID check */
if (r < 0)
return r;
assert(mntid >= 0);
st.nsx.stx_mask |= STATX_MNT_ID;
}
- r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
- if (r < 0)
- return r;
-
if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) {
int mntid;
r = path_get_mnt_id_at(dir_fd, "..", &mntid);
+ if (r == -ENOSYS)
+ return true; /* skip the mount ID check */
if (r < 0)
return r;
assert(mntid >= 0);
pst.nsx.stx_mask |= STATX_MNT_ID;
}
- /* If the parent directory is the same inode, the fd points to the root directory "/". We also check
- * that the mount ids are the same. Otherwise, a construct like the following could be used to trick
- * us:
- *
- * $ mkdir /tmp/x /tmp/x/y
- * $ mount --bind /tmp/x /tmp/x/y
- */
- return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
+ return 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);
+
+/* Like ASSERT_PTR, but for fds */
+#define ASSERT_FD(fd) \
+ ({ \
+ int _fd_ = (fd); \
+ assert(_fd_ >= 0); \
+ _fd_; \
+ })
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
* can detect EOFs. */
#define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret) {
- int r;
-
- assert(ret);
-
- r = xfopenat(dir_fd, path, options, flags, ret);
- if (r < 0)
- return r;
-
- (void) __fsetlocking(*ret, FSETLOCKING_BYCALLER);
-
- return 0;
-}
-
int fdopen_unlocked(int fd, const char *options, FILE **ret) {
assert(ret);
const struct timespec *ts) {
_cleanup_fclose_ FILE *f = NULL;
- int q, r, fd;
+ _cleanup_close_ int fd = -EBADF;
+ int q, r;
assert(fn);
assert(line);
goto fail;
}
- r = fdopen_unlocked(fd, "w", &f);
- if (r < 0) {
- safe_close(fd);
+ r = take_fdopen_unlocked(&fd, "w", &f);
+ if (r < 0)
goto fail;
- }
if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
setvbuf(f, NULL, _IONBF, 0);
return write_string_file(fn, p, flags);
}
-int read_one_line_file(const char *fn, char **line) {
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
int r;
- assert(fn);
- assert(line);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(filename);
+ assert(ret);
- r = fopen_unlocked(fn, "re", &f);
+ r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f);
if (r < 0)
return r;
- return read_line(f, LONG_LINE_MAX, line);
+ return read_line(f, LONG_LINE_MAX, ret);
}
int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) {
size_t *ret_size) {
_cleanup_fclose_ FILE *f = NULL;
+ XfopenFlags xflags = XFOPEN_UNLOCKED;
int r;
assert(filename);
assert(ret_contents);
- r = xfopenat(dir_fd, filename, "re", 0, &f);
- if (r < 0) {
- _cleanup_close_ int sk = -EBADF;
-
- /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
- if (r != -ENXIO)
- return r;
-
- /* If this is enabled, let's try to connect to it */
- if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
- return -ENXIO;
-
- /* Seeking is not supported on AF_UNIX sockets */
- if (offset != UINT64_MAX)
- return -ENXIO;
-
- sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
- if (sk < 0)
- return -errno;
-
- if (bind_name) {
- /* If the caller specified a socket name to bind to, do so before connecting. This is
- * useful to communicate some minor, short meta-information token from the client to
- * the server. */
- union sockaddr_union bsa;
-
- r = sockaddr_un_set_path(&bsa.un, bind_name);
- if (r < 0)
- return r;
-
- if (bind(sk, &bsa.sa, r) < 0)
- return -errno;
- }
-
- r = connect_unix_path(sk, dir_fd, filename);
- if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
- return -ENXIO;
- if (r < 0)
- return r;
-
- if (shutdown(sk, SHUT_WR) < 0)
- return -errno;
-
- f = fdopen(sk, "r");
- if (!f)
- return -errno;
-
- TAKE_FD(sk);
- }
+ if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+ offset == UINT64_MAX) /* Seeking is not supported on AF_UNIX sockets */
+ xflags |= XFOPEN_SOCKET;
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+ if (r < 0)
+ return r;
return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
}
}
DIR *xopendirat(int fd, const char *name, int flags) {
- int nfd;
- DIR *d;
+ _cleanup_close_ int nfd = -EBADF;
assert(!(flags & O_CREAT));
if (nfd < 0)
return NULL;
- d = fdopendir(nfd);
- if (!d) {
- safe_close(nfd);
- return NULL;
- }
-
- return d;
+ return take_fdopendir(&nfd);
}
int fopen_mode_to_flags(const char *mode) {
return flags;
}
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
FILE *f;
/* A combination of fopen() with openat() */
- if (dir_fd == AT_FDCWD && flags == 0) {
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(mode);
+ assert(ret);
+
+ if (dir_fd == AT_FDCWD && open_flags == 0)
f = fopen(path, mode);
- if (!f)
- return -errno;
- } else {
- int fd, mode_flags;
+ else {
+ _cleanup_close_ int fd = -EBADF;
+ int mode_flags;
mode_flags = fopen_mode_to_flags(mode);
if (mode_flags < 0)
return mode_flags;
- fd = openat(dir_fd, path, mode_flags | flags);
+ fd = openat(dir_fd, path, mode_flags | open_flags);
if (fd < 0)
return -errno;
- f = fdopen(fd, mode);
- if (!f) {
- safe_close(fd);
+ f = take_fdopen(&fd, mode);
+ }
+ if (!f)
+ return -errno;
+
+ *ret = f;
+ return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+ _cleanup_close_ int sk = -EBADF;
+ FILE *f;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (sk < 0)
+ return -errno;
+
+ if (bind_name) {
+ /* If the caller specified a socket name to bind to, do so before connecting. This is
+ * useful to communicate some minor, short meta-information token from the client to
+ * the server. */
+ union sockaddr_union bsa;
+
+ r = sockaddr_un_set_path(&bsa.un, bind_name);
+ if (r < 0)
+ return r;
+
+ if (bind(sk, &bsa.sa, r) < 0)
return -errno;
- }
}
+ r = connect_unix_path(sk, dir_fd, path);
+ if (r < 0)
+ return r;
+
+ if (shutdown(sk, SHUT_WR) < 0)
+ return -errno;
+
+ f = take_fdopen(&sk, "r");
+ if (!f)
+ return -errno;
+
+ *ret = f;
+ return 0;
+}
+
+int xfopenat_full(
+ int dir_fd,
+ const char *path,
+ const char *mode,
+ int open_flags,
+ XfopenFlags flags,
+ const char *bind_name,
+ FILE **ret) {
+
+ FILE *f = NULL; /* avoid false maybe-uninitialized warning */
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(mode);
+ assert(ret);
+
+ r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+ if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+ /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+ r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+ if (IN_SET(r, -ENOTSOCK, -EINVAL))
+ return -ENXIO; /* propagate original error if this is not a socket after all */
+ }
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
*ret = f;
return 0;
}
+int fdopen_independent(int fd, const char *mode, FILE **ret) {
+ _cleanup_close_ int copy_fd = -EBADF;
+ _cleanup_fclose_ FILE *f = NULL;
+ int mode_flags;
+
+ assert(fd >= 0);
+ assert(mode);
+ assert(ret);
+
+ /* A combination of fdopen() + fd_reopen(). i.e. reopens the inode the specified fd points to and
+ * returns a FILE* for it */
+
+ mode_flags = fopen_mode_to_flags(mode);
+ if (mode_flags < 0)
+ return mode_flags;
+
+ copy_fd = fd_reopen(fd, mode_flags);
+ if (copy_fd < 0)
+ return copy_fd;
+
+ f = take_fdopen(©_fd, mode);
+ if (!f)
+ return -errno;
+
+ *ret = TAKE_PTR(f);
+ return 0;
+}
+
static int search_and_fopen_internal(
const char *path,
const char *mode,
READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */
} ReadFullFileFlags;
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret);
-static inline int fopen_unlocked(const char *path, const char *options, FILE **ret) {
- return fopen_unlocked_at(AT_FDCWD, path, options, 0, ret);
-}
int fdopen_unlocked(int fd, const char *options, FILE **ret);
int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
FILE* take_fdopen(int *fd, const char *options);
int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
-int read_one_line_file(const char *filename, char **line);
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret);
+static inline int read_one_line_file(const char *filename, char **ret) {
+ return read_one_line_file_at(AT_FDCWD, filename, ret);
+}
int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) {
return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
DIR *xopendirat(int dirfd, const char *name, int flags);
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
+
+typedef enum XfopenFlags {
+ XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */
+ XFOPEN_SOCKET = 1 << 1, /* also try to open unix socket */
+} XfopenFlags;
+
+int xfopenat_full(
+ int dir_fd,
+ const char *path,
+ const char *mode,
+ int open_flags,
+ XfopenFlags flags,
+ const char *bind_name,
+ FILE **ret);
+static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+ return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret);
+}
+static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+ return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret);
+}
+static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) {
+ return fopen_unlocked_at(AT_FDCWD, path, mode, 0, ret);
+}
+
+int fdopen_independent(int fd, const char *mode, FILE **ret);
int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path);
int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path);
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
+#include <sys/file.h>
#include <linux/falloc.h>
#include <linux/magic.h>
#include <unistd.h>
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
+#include "lock-util.h"
#include "log.h"
#include "macro.h"
#include "missing_fcntl.h"
return 0;
}
-int open_parent(const char *path, int flags, mode_t mode) {
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
_cleanup_free_ char *parent = NULL;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
r = path_extract_directory(path, &parent);
- if (r < 0)
+ if (r == -EDESTADDRREQ) {
+ parent = strdup(".");
+ if (!parent)
+ return -ENOMEM;
+ } else if (r == -EADDRNOTAVAIL) {
+ parent = strdup(path);
+ if (!parent)
+ return -ENOMEM;
+ } else if (r < 0)
return r;
/* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
else if (!FLAGS_SET(flags, O_TMPFILE))
flags |= O_DIRECTORY|O_RDONLY;
- return RET_NERRNO(open(parent, flags, mode));
+ return RET_NERRNO(openat(dir_fd, parent, flags, mode));
}
int conservative_renameat(
* 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);
int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
_cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
- _cleanup_free_ char *fname = NULL;
+ _cleanup_free_ char *fname = NULL, *parent = NULL;
int r;
/* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
/* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
* flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
- if (isempty(path))
- return -EINVAL;
-
- if (!filename_is_valid(path)) {
- _cleanup_free_ char *parent = NULL;
-
- /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
- * that we can pin it, and operate below it. */
-
- r = path_extract_directory(path, &parent);
- if (r < 0)
+ /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
+ * that we can pin it, and operate below it. */
+ r = path_extract_directory(path, &parent);
+ if (r < 0) {
+ if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
return r;
-
+ } else {
r = path_extract_filename(path, &fname);
if (r < 0)
return r;
bool made = false;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ if (isempty(path)) {
+ assert(!FLAGS_SET(flags, O_CREAT|O_EXCL));
+ return fd_reopen(dir_fd, flags & ~O_NOFOLLOW);
+ }
+
if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) {
r = RET_NERRNO(mkdirat(dir_fd, path, mode));
if (r == -EEXIST) {
return TAKE_FD(fd);
}
+
+int xopenat_lock(int dir_fd, const char *path, int flags, mode_t mode, LockType locktype, int operation) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
+
+ /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
+ * the same error here). */
+ if (FLAGS_SET(flags, O_DIRECTORY) && locktype != LOCK_BSD)
+ return -EBADF;
+
+ for (;;) {
+ struct stat st;
+
+ fd = xopenat(dir_fd, path, flags, mode);
+ if (fd < 0)
+ return fd;
+
+ r = lock_generic(fd, locktype, operation);
+ if (r < 0)
+ return r;
+
+ /* If we acquired the lock, let's check if the file/directory still exists in the file
+ * system. If not, then the previous exclusive owner removed it and then closed it. In such a
+ * case our acquired lock is worthless, hence try again. */
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+ if (st.st_nlink > 0)
+ break;
+
+ fd = safe_close(fd);
+ }
+
+ return TAKE_FD(fd);
+}
#include "alloc-util.h"
#include "errno-util.h"
+#include "lock-util.h"
#include "time-util.h"
#include "user-util.h"
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
-int open_parent(const char *path, int flags, mode_t mode);
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode);
+static inline int open_parent(const char *path, int flags, mode_t mode) {
+ return open_parent_at(AT_FDCWD, path, flags, mode);
+}
int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
static inline int conservative_rename(const char *oldpath, const char *newpath) {
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created);
int xopenat(int dir_fd, const char *path, int flags, mode_t mode);
+
+int xopenat_lock(int dir_fd, const char *path, int flags, mode_t mode, LockType locktype, int operation);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <getopt.h>
+
+#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:"
+
+#define COMMON_GETOPT_ARGS \
+ ARG_LOG_LEVEL = 0x100, \
+ ARG_LOG_TARGET, \
+ ARG_LOG_COLOR, \
+ ARG_LOG_LOCATION, \
+ ARG_LOG_TIME
+
+#define SYSTEMD_GETOPT_ARGS \
+ ARG_UNIT, \
+ ARG_SYSTEM, \
+ ARG_USER, \
+ ARG_TEST, \
+ ARG_NO_PAGER, \
+ ARG_VERSION, \
+ ARG_DUMP_CONFIGURATION_ITEMS, \
+ ARG_DUMP_BUS_PROPERTIES, \
+ ARG_BUS_INTROSPECT, \
+ ARG_DUMP_CORE, \
+ ARG_CRASH_CHVT, \
+ ARG_CRASH_SHELL, \
+ ARG_CRASH_REBOOT, \
+ ARG_CONFIRM_SPAWN, \
+ ARG_SHOW_STATUS, \
+ ARG_DESERIALIZE, \
+ ARG_SWITCHED_ROOT, \
+ ARG_DEFAULT_STD_OUTPUT, \
+ ARG_DEFAULT_STD_ERROR, \
+ ARG_MACHINE_ID, \
+ ARG_SERVICE_WATCHDOGS
+
+#define SHUTDOWN_GETOPT_ARGS \
+ ARG_EXIT_CODE, \
+ ARG_TIMEOUT
+
+#define COMMON_GETOPT_OPTIONS \
+ { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, \
+ { "log-target", required_argument, NULL, ARG_LOG_TARGET }, \
+ { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, \
+ { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, \
+ { "log-time", optional_argument, NULL, ARG_LOG_TIME }
+
+#define SYSTEMD_GETOPT_OPTIONS \
+ { "unit", required_argument, NULL, ARG_UNIT }, \
+ { "system", no_argument, NULL, ARG_SYSTEM }, \
+ { "user", no_argument, NULL, ARG_USER }, \
+ { "test", no_argument, NULL, ARG_TEST }, \
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER }, \
+ { "help", no_argument, NULL, 'h' }, \
+ { "version", no_argument, NULL, ARG_VERSION }, \
+ { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \
+ { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES }, \
+ { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, \
+ { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, \
+ { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \
+ { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \
+ { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \
+ { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \
+ { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \
+ { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \
+ { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, \
+ { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, \
+ { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, \
+ { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, \
+ { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS }
+
+#define SHUTDOWN_GETOPT_OPTIONS \
+ { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, \
+ { "timeout", required_argument, NULL, ARG_TIMEOUT }
/* Remove an item from the list */
#define LIST_REMOVE(name,head,item) \
- ({ \
+ ({ \
typeof(*(head)) **_head = &(head), *_item = (item); \
assert(_item); \
if (_item->name##_next) \
_b; \
})
-#define LIST_JUST_US(name,item) \
- (!(item)->name##_prev && !(item)->name##_next)
+#define LIST_JUST_US(name, item) \
+ ({ \
+ typeof(*(item)) *_item = (item); \
+ !(_item)->name##_prev && !(_item)->name##_next; \
+ })
/* The type of the iterator 'i' is automatically determined by the type of 'head', and declared in the
* loop. Hence, do not declare the same variable in the outer scope. Sometimes, we set 'head' through
#include "missing_fcntl.h"
#include "path-util.h"
-int make_lock_file(const char *p, int operation, LockFile *ret) {
- _cleanup_close_ int fd = -EBADF;
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret) {
+ _cleanup_close_ int fd = -EBADF, dfd = -EBADF;
_cleanup_free_ char *t = NULL;
- int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(p);
assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
assert(ret);
+ if (isempty(p))
+ return -EINVAL;
+
/* We use UNPOSIX locks as they have nice semantics, and are mostly compatible with NFS. */
+ dfd = fd_reopen(dir_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
+ if (dfd < 0)
+ return dfd;
+
t = strdup(p);
if (!t)
return -ENOMEM;
- for (;;) {
- struct stat st;
-
- fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
- if (fd < 0)
- return -errno;
-
- r = unposix_lock(fd, operation);
- if (r < 0)
- return r == -EAGAIN ? -EBUSY : r;
-
- /* If we acquired the lock, let's check if the file still exists in the file system. If not,
- * then the previous exclusive owner removed it and then closed it. In such a case our
- * acquired lock is worthless, hence try again. */
-
- if (fstat(fd, &st) < 0)
- return -errno;
- if (st.st_nlink > 0)
- break;
-
- fd = safe_close(fd);
- }
+ fd = xopenat_lock(dfd, p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600, LOCK_UNPOSIX, operation);
+ if (fd < 0)
+ return fd == -EAGAIN ? -EBUSY : fd;
*ret = (LockFile) {
+ .dir_fd = TAKE_FD(dfd),
.path = TAKE_PTR(t),
.fd = TAKE_FD(fd),
.operation = operation,
f->operation = LOCK_EX|LOCK_NB;
if ((f->operation & ~LOCK_NB) == LOCK_EX)
- (void) unlink(f->path);
+ (void) unlinkat(f->dir_fd, f->path, 0);
f->path = mfree(f->path);
}
+ f->dir_fd = safe_close(f->dir_fd);
f->fd = safe_close(f->fd);
f->operation = 0;
}
(void) fcntl_lock(**fd, LOCK_UN, /*ofd=*/ true);
*fd = NULL;
}
+
+int lock_generic(int fd, LockType type, int operation) {
+ assert(fd >= 0);
+
+ switch (type) {
+ case LOCK_BSD:
+ return RET_NERRNO(flock(fd, operation));
+ case LOCK_POSIX:
+ return posix_lock(fd, operation);
+ case LOCK_UNPOSIX:
+ return unposix_lock(fd, operation);
+ default:
+ assert_not_reached();
+ }
+}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <fcntl.h>
+
typedef struct LockFile {
+ int dir_fd;
char *path;
int fd;
int operation;
} LockFile;
-int make_lock_file(const char *p, int operation, LockFile *ret);
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret);
+static inline int make_lock_file(const char *p, int operation, LockFile *ret) {
+ return make_lock_file_at(AT_FDCWD, p, operation, ret);
+}
int make_lock_file_for(const char *p, int operation, LockFile *ret);
void release_lock_file(LockFile *f);
-#define LOCK_FILE_INIT { .fd = -EBADF, .path = NULL }
+#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF }
/* POSIX locks with the same interface as flock(). */
int posix_lock(int fd, int operation);
#define CLEANUP_UNPOSIX_UNLOCK(fd) \
_cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd)
+
+typedef enum LockType {
+ LOCK_BSD,
+ LOCK_POSIX,
+ LOCK_UNPOSIX,
+} LockType;
+
+int lock_generic(int fd, LockType type, int operation);
#include "utf8.h"
#define SNDBUF_SIZE (8*1024*1024)
-#define IOVEC_MAX 128U
+#define IOVEC_MAX 256U
static log_syntax_callback_t log_syntax_callback = NULL;
static void *log_syntax_callback_userdata = NULL;
static LogTarget log_target = LOG_TARGET_CONSOLE;
static int log_max_level = LOG_INFO;
static int log_facility = LOG_DAEMON;
+static bool ratelimit_kmsg = true;
static int console_fd = STDERR_FILENO;
static int syslog_fd = -EBADF;
static char *log_abort_msg = NULL;
typedef struct LogContext {
+ unsigned n_ref;
/* Depending on which destructor is used (log_context_free() or log_context_detach()) the memory
* referenced by this is freed or not */
char **fields;
struct iovec *input_iovec;
size_t n_input_iovec;
+ char *key;
+ char *value;
bool owned;
LIST_FIELDS(struct LogContext, ll);
} LogContext;
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 = {
if (kmsg_fd < 0)
return 0;
- if (!ratelimit_below(&ratelimit))
- return 0;
+ if (ratelimit_kmsg && !ratelimit_below(&ratelimit)) {
+ if (ratelimit_num_dropped(&ratelimit) > 1)
+ return 0;
+
+ buffer = "Too many messages being logged to kmsg, ignoring";
+ }
xsprintf(header_priority, "<%i>", level);
xsprintf(header_pid, "["PID_FMT"]: ", getpid_cached());
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"),
};
iovec[(*n)++] = c->input_iovec[i];
iovec[(*n)++] = IOVEC_MAKE_STRING("\n");
}
+
+ if (c->key && c->value) {
+ if (*n + 3 >= iovec_len)
+ return;
+
+ iovec[(*n)++] = IOVEC_MAKE_STRING(c->key);
+ iovec[(*n)++] = IOVEC_MAKE_STRING(c->value);
+ iovec[(*n)++] = 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,
return 0;
}
+static int log_set_ratelimit_kmsg_from_string(const char *e) {
+ int r;
+
+ r = parse_boolean(e);
+ if (r < 0)
+ return r;
+
+ ratelimit_kmsg = r;
+ return 0;
+}
+
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
/*
if (log_show_time_from_string(value ?: "1") < 0)
log_warning("Failed to parse log time setting '%s'. Ignoring.", value);
+ } else if (proc_cmdline_key_streq(key, "systemd.log_ratelimit_kmsg")) {
+
+ if (log_set_ratelimit_kmsg_from_string(value ?: "1") < 0)
+ log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", value);
}
return 0;
e = getenv("SYSTEMD_LOG_TID");
if (e && log_show_tid_from_string(e) < 0)
log_warning("Failed to parse log tid '%s'. Ignoring.", e);
+
+ e = getenv("SYSTEMD_LOG_RATELIMIT_KMSG");
+ if (e && log_set_ratelimit_kmsg_from_string(e) < 0)
+ log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", e);
}
void log_parse_environment(void) {
return log_target;
}
+void log_settle_target(void) {
+
+ /* If we're using LOG_TARGET_AUTO and opening the log again on every single log call, we'll check if
+ * stderr is attached to the journal every single log call. However, if we then close all file
+ * descriptors later, that will stop working because stderr will be closed as well. To avoid that
+ * problem, this function is used to permanently change the log target depending on whether stderr is
+ * connected to the journal or not. */
+
+ LogTarget t = log_get_target();
+
+ if (t != LOG_TARGET_AUTO)
+ return;
+
+ t = getpid_cached() == 1 || stderr_is_journal() ? (prohibit_ipc ? LOG_TARGET_KMSG : LOG_TARGET_JOURNAL_OR_KMSG)
+ : LOG_TARGET_CONSOLE;
+ log_set_target(t);
+}
+
int log_get_max_level(void) {
return log_max_level;
}
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) {
return saved_log_context_enabled;
}
-LogContext* log_context_attach(LogContext *c) {
+static LogContext* log_context_attach(LogContext *c) {
assert(c);
_log_context_num_fields += strv_length(c->fields);
_log_context_num_fields += c->n_input_iovec;
+ _log_context_num_fields += !!c->key;
return LIST_PREPEND(ll, _log_context, c);
}
-LogContext* log_context_detach(LogContext *c) {
+static LogContext* log_context_detach(LogContext *c) {
if (!c)
return NULL;
- assert(_log_context_num_fields >= strv_length(c->fields) + c->n_input_iovec);
+ assert(_log_context_num_fields >= strv_length(c->fields) + c->n_input_iovec +!!c->key);
_log_context_num_fields -= strv_length(c->fields);
_log_context_num_fields -= c->n_input_iovec;
+ _log_context_num_fields -= !!c->key;
LIST_REMOVE(ll, _log_context, c);
return NULL;
}
-LogContext* log_context_new(char **fields, bool owned) {
+LogContext* log_context_new(const char *key, const char *value) {
+ assert(key);
+ assert(endswith(key, "="));
+ assert(value);
+
+ LIST_FOREACH(ll, i, _log_context)
+ if (i->key == key && i->value == value)
+ return log_context_ref(i);
+
LogContext *c = new(LogContext, 1);
if (!c)
return NULL;
*c = (LogContext) {
+ .n_ref = 1,
+ .key = (char *) key,
+ .value = (char *) value,
+ };
+
+ return log_context_attach(c);
+}
+
+LogContext* log_context_new_strv(char **fields, bool owned) {
+ if (!fields)
+ return NULL;
+
+ LIST_FOREACH(ll, i, _log_context)
+ if (i->fields == fields) {
+ assert(!owned);
+ return log_context_ref(i);
+ }
+
+ LogContext *c = new(LogContext, 1);
+ if (!c)
+ return NULL;
+
+ *c = (LogContext) {
+ .n_ref = 1,
.fields = fields,
.owned = owned,
};
return log_context_attach(c);
}
-LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bool owned) {
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned) {
if (!input_iovec || n_input_iovec == 0)
- return NULL; /* Nothing to do */
+ return NULL;
+
+ LIST_FOREACH(ll, i, _log_context)
+ if (i->input_iovec == input_iovec && i->n_input_iovec == n_input_iovec) {
+ assert(!owned);
+ return log_context_ref(i);
+ }
LogContext *c = new(LogContext, 1);
if (!c)
return NULL;
*c = (LogContext) {
+ .n_ref = 1,
.input_iovec = input_iovec,
.n_input_iovec = n_input_iovec,
.owned = owned,
return log_context_attach(c);
}
-LogContext* log_context_free(LogContext *c) {
+static LogContext* log_context_free(LogContext *c) {
if (!c)
return NULL;
if (c->owned) {
strv_free(c->fields);
iovec_array_free(c->input_iovec, c->n_input_iovec);
+ free(c->key);
+ free(c->value);
}
return mfree(c);
}
-LogContext* log_context_new_consume(char **fields) {
- LogContext *c = log_context_new(fields, /*owned=*/ true);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(LogContext, log_context, log_context_free);
+
+LogContext* log_context_new_strv_consume(char **fields) {
+ LogContext *c = log_context_new_strv(fields, /*owned=*/ true);
if (!c)
strv_free(fields);
return c;
}
-LogContext* log_context_new_consumev(struct iovec *input_iovec, size_t n_input_iovec) {
- LogContext *c = log_context_newv(input_iovec, n_input_iovec, /*owned=*/ true);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec) {
+ LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ true);
if (!c)
iovec_array_free(input_iovec, n_input_iovec);
void log_set_target_and_open(LogTarget target);
int log_set_target_from_string(const char *e);
LogTarget log_get_target(void) _pure_;
+void log_settle_target(void);
void log_set_max_level(int level);
int log_set_max_level_from_string(const char *e);
#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.
bool log_context_enabled(void);
-LogContext* log_context_attach(LogContext *c);
-LogContext* log_context_detach(LogContext *c);
-
-LogContext* log_context_new(char **fields, bool owned);
-LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
-LogContext* log_context_free(LogContext *c);
+LogContext* log_context_new(const char *key, const char *value);
+LogContext* log_context_new_strv(char **fields, bool owned);
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
/* Same as log_context_new(), but frees the given fields strv/iovec on failure. */
-LogContext* log_context_new_consume(char **fields);
-LogContext* log_context_new_consumev(struct iovec *input_iovec, size_t n_input_iovec);
+LogContext* log_context_new_strv_consume(char **fields);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec);
+
+LogContext *log_context_ref(LogContext *c);
+LogContext *log_context_unref(LogContext *c);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref);
/* Returns the number of attached log context objects. */
size_t log_context_num_contexts(void);
/* Returns the number of fields in all attached log contexts. */
size_t log_context_num_fields(void);
-DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_detach);
-DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_free);
-
#define LOG_CONTEXT_PUSH(...) \
LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__))
#define LOG_CONTEXT_PUSHF(...) \
LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__))
+#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value);
+
+#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \
+ _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ))
+
#define _LOG_CONTEXT_PUSH_STRV(strv, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new(strv, /*owned=*/ false);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false);
#define LOG_CONTEXT_PUSH_STRV(strv) \
_LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ))
#define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_newv(input_iovec, n_input_iovec, /*owned=*/ false);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false);
#define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \
_LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
_unused_ _cleanup_strv_free_ strv = strv_new(s); \
if (!strv) \
free(s); \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(TAKE_PTR(strv))
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv))
#define LOG_CONTEXT_CONSUME_STR(s) \
_LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ))
#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(strv);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv);
#define LOG_CONTEXT_CONSUME_STRV(strv) \
_LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ))
#define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consumev(input_iovec, n_input_iovec);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec);
#define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \
_LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
})
/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
- * convertable. The iteration variable 'entry' must already be defined. */
+ * convertible. The iteration variable 'entry' must already be defined. */
#define VA_ARGS_FOREACH(entry, ...) \
_VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \
'cap-list.c',
'capability-util.c',
'cgroup-util.c',
- 'chase-symlinks.c',
+ 'chase.c',
'chattr-util.c',
'conf-files.c',
'devnum-util.c',
#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
#include <string.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
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;
if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
_cleanup_free_ char *p = NULL;
- r = chase_symlinks_at(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
+ r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
if (r < 0)
return r;
if (r == 0)
if (r < 0)
return r;
- dfd = chase_symlinks_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
+ dfd = chase_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
if (dfd < 0)
return dfd;
}
#endif
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "filesystems.h"
h->handle_bytes = n;
- if (name_to_handle_at(fd, path, h, &mnt_id, flags) >= 0) {
+ if (name_to_handle_at(fd, strempty(path), h, &mnt_id, flags) >= 0) {
if (ret_handle)
*ret_handle = TAKE_PTR(h);
/* The buffer was too small. Size the new buffer by what name_to_handle_at() returned. */
n = h->handle_bytes;
- if (offsetof(struct file_handle, f_handle) + n < n) /* check for addition overflow */
+
+ /* paranoia: check for overflow (note that .handle_bytes is unsigned only) */
+ if (n > UINT_MAX - offsetof(struct file_handle, f_handle))
return -EOVERFLOW;
}
}
r = read_full_virtual_file(path, &fdinfo, NULL);
if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
- return -EOPNOTSUPP;
+ return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
if (r < 0)
return r;
- p = startswith(fdinfo, "mnt_id:");
- if (!p) {
- p = strstr(fdinfo, "\nmnt_id:");
- if (!p) /* The mnt_id field is a relatively new addition */
- return -EOPNOTSUPP;
-
- p += 8;
- }
+ p = find_line_startswith(fdinfo, "mnt_id:");
+ if (!p) /* The mnt_id field is a relatively new addition */
+ return -EOPNOTSUPP;
p += strspn(p, WHITESPACE);
p[strcspn(p, WHITESPACE)] = 0;
* reported. Also, btrfs subvolumes have different st_dev, even though they aren't real mounts of
* their own. */
- if (statx(fd, filename, (FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW) |
- (flags & AT_EMPTY_PATH) |
- AT_NO_AUTOMOUNT, STATX_TYPE, &sx) < 0) {
+ if (statx(fd,
+ filename,
+ (FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW) |
+ (flags & AT_EMPTY_PATH) |
+ AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */
+ AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */
+ STATX_TYPE,
+ &sx) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
/* If the file handle for the directory we are interested in and its parent are identical,
* we assume this is the root directory, which is a mount point. */
- if (h->handle_bytes == h_parent->handle_bytes &&
- h->handle_type == h_parent->handle_type &&
- memcmp(h->f_handle, h_parent->f_handle, h->handle_bytes) == 0)
+ if (h->handle_type == h_parent->handle_type &&
+ memcmp_nn(h->f_handle, h->handle_bytes,
+ h_parent->f_handle, h_parent->handle_bytes) == 0)
return 1;
return mount_id != mount_id_parent;
fallback_fdinfo:
r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
- if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM))
+ if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM, -ENOSYS))
goto fallback_fstat;
if (r < 0)
return r;
* /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
* look at needs to be /usr, not /. */
if (flags & AT_SYMLINK_FOLLOW) {
- r = chase_symlinks(t, root, CHASE_TRAIL_SLASH, &canonical, NULL);
+ r = chase(t, root, CHASE_TRAIL_SLASH, &canonical, NULL);
if (r < 0)
return r;
int r;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
- assert(path);
assert(ret);
if (statx(dir_fd,
- path,
- AT_NO_AUTOMOUNT|(isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW),
+ strempty(path),
+ (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) |
+ AT_NO_AUTOMOUNT | /* don't trigger automounts, mnt_id is a local concept */
+ AT_STATX_DONT_SYNC, /* don't go to the network, mnt_id is a local concept */
STATX_MNT_ID,
&buf.sx) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return r;
r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
+ if (r == -ENOENT)
+ return proc_mounted() > 0 ? -ENOENT : -ENOSYS;
if (r < 0)
return r;
if (mid != mount_id)
continue;
- e = strstr(line, " - ");
+ e = strstrafter(line, " - ");
if (!e)
continue;
/* accept any name that starts with the currently expected type */
- if (startswith(e + 3, "devtmpfs"))
+ if (startswith(e, "devtmpfs"))
return true;
}
_cleanup_close_ int fd = -EBADF;
int r;
- /* Checks if the specified file system supports a mount option. Returns > 0 if it suppors it, == 0 if
+ /* Checks if the specified file system supports a mount option. Returns > 0 if it supports it, == 0 if
* it does not. Return -EAGAIN if we can't determine it. And any other error otherwise. */
assert(fstype);
#include "string-util.h"
#include "strv.h"
-char** strv_parse_nulstr(const char *s, size_t l) {
+char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) {
/* l is the length of the input data, which will be split at NULs into elements of the resulting
* strv. Hence, the number of items in the resulting strv will be equal to one plus the number of NUL
* bytes in the l bytes starting at s, unless s[l-1] is NUL, in which case the final empty string is
assert(s || l <= 0);
+ if (drop_trailing_nuls)
+ while (l > 0 && s[l-1] == '\0')
+ l--;
+
if (l <= 0)
return new0(char*, 1);
return nulstr_get(nulstr, needle);
}
-char** strv_parse_nulstr(const char *s, size_t l);
+char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls);
+static inline char** strv_parse_nulstr(const char *s, size_t l) {
+ return strv_parse_nulstr_full(s, l, false);
+}
char** strv_split_nulstr(const char *s);
int strv_make_nulstr(char * const *l, char **p, size_t *n);
int set_make_nulstr(Set *s, char **ret, size_t *ret_size);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <pthread.h>
+
+#include "random-util.h"
+
+/* This pattern needs to be repeated exactly in multiple modules, so macro it.
+ * To ensure an object is not passed into a different module (e.g.: when two shared objects statically
+ * linked to libsystemd get loaded in the same process, and the object created by one is passed to the
+ * other, see https://github.com/systemd/systemd/issues/27216), create a random static global random
+ * (mixed with PID, so that we can also check for reuse after fork) that is stored in the object and
+ * checked by public API on use. */
+#define _DEFINE_ORIGIN_ID_HELPERS(type, name, scope) \
+static uint64_t origin_id; \
+ \
+static void origin_id_initialize(void) { \
+ origin_id = random_u64(); \
+} \
+ \
+static uint64_t origin_id_query(void) { \
+ static pthread_once_t once = PTHREAD_ONCE_INIT; \
+ assert_se(pthread_once(&once, origin_id_initialize) == 0); \
+ return origin_id ^ getpid_cached(); \
+} \
+ \
+scope bool name##_origin_changed(type *p) { \
+ assert(p); \
+ return p->origin_id != origin_id_query(); \
+}
+
+#define DEFINE_ORIGIN_ID_HELPERS(type, name) \
+ _DEFINE_ORIGIN_ID_HELPERS(type, name,);
+
+#define DEFINE_PRIVATE_ORIGIN_ID_HELPERS(type, name) \
+ _DEFINE_ORIGIN_ID_HELPERS(type, name, static);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "env-file.h"
#include "env-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "utf8.h"
#include "xattr-util.h"
+static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
+ [IMAGE_MACHINE] = "machine",
+ [IMAGE_PORTABLE] = "portable",
+ [IMAGE_SYSEXT] = "extension",
+ [IMAGE_CONFEXT] = "confext",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
+
+/* 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);
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) {
- _cleanup_free_ char *q = NULL;
- int r, fd;
-
- if (extension) {
- 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);
- r = chase_symlinks(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);
-
- /* Cannot find the expected extension-release file? The image filename might have been
- * mangled on deployment, so fallback to checking for any file in the extension-release.d
- * directory, and return the first one with a user.extension-release xattr instead.
- * The user.extension-release.strict xattr is checked to ensure the author of the image
- * considers it OK if names do not match. */
- if (r == -ENOENT) {
- _cleanup_free_ char *extension_release_dir_path = NULL;
- _cleanup_closedir_ DIR *extension_release_dir = NULL;
-
- r = chase_symlinks_and_opendir("/usr/lib/extension-release.d/", 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);
-
- r = -ENOENT;
- FOREACH_DIRENT(de, extension_release_dir, return -errno) {
- int k;
-
- if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
- continue;
-
- const char *image_name = startswith(de->d_name, "extension-release.");
- if (!image_name)
- continue;
-
- if (!image_name_is_valid(image_name)) {
- log_debug("%s/%s is not a valid extension-release file name, ignoring.",
- extension_release_dir_path, de->d_name);
- continue;
- }
-
- /* We already chased the directory, and checked that
- * this is a real file, so we shouldn't fail to open it. */
- _cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir),
- de->d_name,
- 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",
- extension_release_dir_path,
- de->d_name);
-
- /* Really ensure it is a regular file after we open it. */
- if (fd_verify_regular(extension_release_fd) < 0) {
- log_debug("%s/%s is not a regular file, ignoring.", extension_release_dir_path, de->d_name);
- continue;
- }
-
- if (!relax_extension_release_check) {
- k = extension_release_strict_xattr_value(extension_release_fd,
- extension_release_dir_path,
- de->d_name);
- if (k != 0)
- continue;
- }
-
- /* We already found what we were looking for, but there's another candidate?
- * We treat this as an error, as we want to enforce that there are no ambiguities
- * in case we are in the fallback path. */
- if (r == 0) {
- r = -ENOTUNIQ;
- break;
- }
-
- r = 0; /* Found it! */
-
- if (ret_fd)
- fd = TAKE_FD(extension_release_fd);
-
- if (ret_path) {
- q = path_join(extension_release_dir_path, de->d_name);
- if (!q)
- return -ENOMEM;
- }
- }
- }
- } else {
- const char *var = secure_getenv("SYSTEMD_OS_RELEASE");
- if (var)
- r = chase_symlinks(var, root, 0,
- ret_path ? &q : NULL,
- ret_fd ? &fd : NULL);
- else
- FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
- r = chase_symlinks(path, root, CHASE_PREFIX_ROOT,
- ret_path ? &q : NULL,
- ret_fd ? &fd : NULL);
- if (r != -ENOENT)
- break;
- }
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
+ const char *e;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ e = secure_getenv("SYSTEMD_OS_RELEASE");
+ if (e)
+ return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+
+ FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+ if (r != -ENOENT)
+ return r;
}
+
+ return -ENOENT;
+}
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd) {
+ _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+
+ r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
if (r < 0)
return r;
- if (ret_fd) {
- int real_fd;
+ if (ret_path) {
+ r = chaseat_prefix_root(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd);
+
+ return 0;
+}
+
+int open_extension_release_at(
+ int rfd,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ char **ret_path,
+ int *ret_fd) {
+
+ _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
+ _cleanup_close_ int fd_found = -EBADF;
+ _cleanup_closedir_ DIR *dir = NULL;
+ bool found = false;
+ const char *p;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
+
+ if (!extension)
+ return open_os_release_at(rfd, ret_path, ret_fd);
+
+ if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
+ return -EINVAL;
+
+ if (!image_name_is_valid(extension))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
+
+ p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
+ r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+ log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
+ if (r != -ENOENT)
+ return r;
+
+ /* Cannot find the expected extension-release file? The image filename might have been mangled on
+ * deployment, so fallback to checking for any file in the extension-release.d directory, and return
+ * the first one with a user.extension-release xattr instead. The user.extension-release.strict
+ * xattr is checked to ensure the author of the image considers it OK if names do not match. */
+
+ p = image_class_release_info[image_class].release_file_directory;
+ r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
+ if (r < 0)
+ return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
+
+ FOREACH_DIRENT(de, dir, return -errno) {
+ _cleanup_close_ int fd = -EBADF;
+ const char *image_name;
- /* Convert the O_PATH fd into a proper, readable one */
- real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY);
- safe_close(fd);
- if (real_fd < 0)
- return real_fd;
+ if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
+ continue;
+
+ image_name = startswith(de->d_name, "extension-release.");
+ if (!image_name)
+ continue;
- *ret_fd = real_fd;
+ if (!image_name_is_valid(image_name)) {
+ log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
+ continue;
+ }
+
+ /* We already chased the directory, and checked that this is a real file, so we shouldn't
+ * fail to open it. */
+ fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0)
+ return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
+
+ /* Really ensure it is a regular file after we open it. */
+ r = fd_verify_regular(fd);
+ if (r < 0) {
+ log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
+ continue;
+ }
+
+ if (!relax_extension_release_check &&
+ extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0)
+ continue;
+
+ /* We already found what we were looking for, but there's another candidate? We treat this as
+ * an error, as we want to enforce that there are no ambiguities in case we are in the
+ * fallback path. */
+ if (found)
+ return -ENOTUNIQ;
+
+ found = true;
+
+ if (ret_fd)
+ fd_found = TAKE_FD(fd);
+
+ if (ret_path) {
+ path_found = path_join(dir_path, de->d_name);
+ if (!path_found)
+ return -ENOMEM;
+ }
}
+ if (!found)
+ return -ENOENT;
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd_found);
if (ret_path)
- *ret_path = TAKE_PTR(q);
+ *ret_path = TAKE_PTR(path_found);
return 0;
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+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_close_ int rfd = -EBADF, fd = -EBADF;
_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);
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
- r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+ r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
+ ret_path ? &p : NULL, ret_fd ? &fd : NULL);
if (r < 0)
return r;
- f = take_fdopen(&fd, "r");
- if (!f)
- return -errno;
+ if (ret_path) {
+ r = chaseat_prefix_root(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
- if (ret_path)
- *ret_path = TAKE_PTR(p);
- *ret_file = f;
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd);
return 0;
}
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
- _cleanup_fclose_ FILE *f = NULL;
+static int parse_extension_release_atv(
+ int rfd,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ va_list ap) {
+
+ _cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
if (r < 0)
return r;
- return parse_env_filev(f, p, ap);
+ return parse_env_file_fdv(fd, p, ap);
}
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int parse_extension_release_at_sentinel(
+ int rfd,
+ ImageClass image_class,
+ bool relax_extension_release_check,
+ const char *extension,
+ ...) {
+
va_list ap;
int r;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
va_start(ap, extension);
- r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+ r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
va_end(ap);
-
return r;
}
-int _parse_os_release(const char *root, ...) {
+int parse_extension_release_sentinel(
+ const char *root,
+ ImageClass image_class,
+ bool relax_extension_release_check,
+ const char *extension,
+ ...) {
+
+ _cleanup_close_ int rfd = -EBADF;
va_list ap;
int r;
- va_start(ap, root);
- r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
- va_end(ap);
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+ va_start(ap, extension);
+ r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
+ va_end(ap);
return r;
}
-int load_os_release_pairs(const char *root, char ***ret) {
- _cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *p = NULL;
- int r;
-
- r = fopen_os_release(root, &p, &f);
- if (r < 0)
- return r;
-
- return load_env_file_pairs(f, p, ret);
-}
-
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
_cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
int r;
return 0;
}
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret) {
- _cleanup_fclose_ FILE *f = NULL;
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
+ _cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
if (r < 0)
return r;
- return load_env_file_pairs(f, p, ret);
+ return load_env_file_pairs_fd(fd, p, ret);
}
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
#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);
-}
-
-int open_extension_release(const char *root, 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 path_is_extension_tree(_IMAGE_CLASS_INVALID, path, NULL, false);
}
-int fopen_extension_release(const char *root, 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);
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_extension_release_at(int rfd, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_os_release(const char *root, char **ret_path, int *ret_fd);
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd);
+
+int parse_extension_release_sentinel(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release(root, image_class, extension, relax_extension_release_check, ...) \
+ parse_extension_release_sentinel(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release(root, ...) \
+ parse_extension_release_sentinel(root, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int parse_extension_release_at_sentinel(int rfd, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release_at(rfd, image_class, extension, relax_extension_release_check, ...) \
+ parse_extension_release_at_sentinel(rfd, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release_at(rfd, ...) \
+ parse_extension_release_at_sentinel(rfd, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
+static inline int load_os_release_pairs(const char *root, char ***ret) {
+ return load_extension_release_pairs(root, _IMAGE_CLASS_INVALID, NULL, false, ret);
}
-
-int _parse_extension_release(const char *root, 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_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_os_release_pairs(const char *root, char ***ret);
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol);
int r;
assert(s);
- assert(ret_pid);
r = safe_atolu(s, &ul);
if (r < 0)
if (!pid_is_valid(pid))
return -ERANGE;
- *ret_pid = pid;
+ if (ret_pid)
+ *ret_pid = pid;
return 0;
}
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fs-util.h"
} else
t = *s;
- r = chase_symlinks(t, root, 0, &u, NULL);
+ r = chase(t, root, 0, &u, NULL);
if (r == -ENOENT) {
if (root) {
u = TAKE_PTR(orig);
return path_equal(a, b) || files_same(a, b, flags) > 0;
}
-bool path_equal_filename(const char *a, const char *b) {
- _cleanup_free_ char *a_basename = NULL, *b_basename = NULL;
- int r;
+int path_compare_filename(const char *a, const char *b) {
+ _cleanup_free_ char *fa = NULL, *fb = NULL;
+ int r, j, k;
- assert(a);
- assert(b);
+ /* Order NULL before non-NULL */
+ r = CMP(!!a, !!b);
+ if (r != 0)
+ return r;
- r = path_extract_filename(a, &a_basename);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse basename of %s: %m", a);
- return false;
- }
- r = path_extract_filename(b, &b_basename);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse basename of %s: %m", b);
- return false;
- }
+ j = path_extract_filename(a, &fa);
+ k = path_extract_filename(b, &fb);
- return path_equal(a_basename, b_basename);
+ /* When one of paths is "." or root, then order it earlier. */
+ r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL);
+ if (r != 0)
+ return r;
+
+ /* When one of paths is invalid (or we get OOM), order invalid path after valid one. */
+ r = CMP(j < 0, k < 0);
+ if (r != 0)
+ return r;
+
+ /* fallback to use strcmp() if both paths are invalid. */
+ if (j < 0)
+ return strcmp(a, b);
+
+ return strcmp(fa, fb);
}
char* path_extend_internal(char **x, ...) {
assert(name);
- /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of
+ /* Function chase() is invoked only when root is not NULL, as using it regardless of
* root value would alter the behavior of existing callers for example: /bin/sleep would become
* /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when
* needed to avoid unforeseen regression or other complicated changes. */
if (root) {
- r = chase_symlinks(name,
- root,
- CHASE_PREFIX_ROOT,
- &path_name,
- /* ret_fd= */ NULL); /* prefix root to name in case full paths are not specified */
+ /* prefix root to name in case full paths are not specified */
+ r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL);
if (r < 0)
return r;
continue;
if (q > path && strneq(q - 1, "/.", 2))
continue;
+ if (q == path && *q == '.')
+ continue;
break;
}
return q;
* ret: "bbbbb/cc//././"
* return value: 5 (== strlen("bbbbb"))
*
+ * Input: path: "//.//aaa///bbbbb/cc//././"
+ * next: "///bbbbb/cc//././"
+ * Output: next: "//.//aaa///bbbbb/cc//././" (next == path)
+ * ret: "aaa///bbbbb/cc//././"
+ * return value: 3 (== strlen("aaa"))
+ *
* Input: path: "/", ".", "", or NULL
* Output: next: equivalent to path
* ret: NULL
static inline char* path_startswith(const char *path, const char *prefix) {
return path_startswith_full(path, prefix, true);
}
-int path_compare(const char *a, const char *b) _pure_;
+int path_compare(const char *a, const char *b) _pure_;
static inline bool path_equal(const char *a, const char *b) {
return path_compare(a, b) == 0;
}
+int path_compare_filename(const char *a, const char *b);
+static inline bool path_equal_filename(const char *a, const char *b) {
+ return path_compare_filename(a, b) == 0;
+}
+
bool path_equal_or_files_same(const char *a, const char *b, int flags);
-/* Compares only the last portion of the input paths, ie: the filenames */
-bool path_equal_filename(const char *a, const char *b);
char* path_extend_internal(char **x, ...);
#define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
#include "efivars.h"
#include "extract-word.h"
#include "fileio.h"
+#include "getopt-defs.h"
#include "initrd-util.h"
#include "macro.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
-#include "special.h"
#include "string-util.h"
+#include "strv.h"
#include "virt.h"
+int proc_cmdline_filter_pid1_args(
+ char **argv, /* input, may be reordered by this function. */
+ char ***ret) {
+
+ enum {
+ COMMON_GETOPT_ARGS,
+ SYSTEMD_GETOPT_ARGS,
+ SHUTDOWN_GETOPT_ARGS,
+ };
+
+ static const struct option options[] = {
+ COMMON_GETOPT_OPTIONS,
+ SYSTEMD_GETOPT_OPTIONS,
+ SHUTDOWN_GETOPT_OPTIONS,
+ {}
+ };
+
+ int saved_optind, saved_opterr, saved_optopt, argc;
+ char *saved_optarg;
+ char **filtered;
+ size_t idx;
+
+ assert(argv);
+ assert(ret);
+
+ /* Backup global variables. */
+ saved_optind = optind;
+ saved_opterr = opterr;
+ saved_optopt = optopt;
+ saved_optarg = optarg;
+
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
+ * the GNU extensions, but might be used previously. Hence, we need to always reset it. */
+ optind = 0;
+
+ /* Do not print an error message. */
+ opterr = 0;
+
+ /* Filter out all known options. */
+ argc = strv_length(argv);
+ while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
+ ;
+
+ idx = optind;
+
+ /* Restore global variables. */
+ optind = saved_optind;
+ opterr = saved_opterr;
+ optopt = saved_optopt;
+ optarg = saved_optarg;
+
+ filtered = strv_copy(strv_skip(argv, idx));
+ if (!filtered)
+ return -ENOMEM;
+
+ *ret = filtered;
+ return 0;
+}
+
int proc_cmdline(char **ret) {
const char *e;
+
assert(ret);
/* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
return read_one_line_file("/proc/cmdline", ret);
}
-static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
- const char *q = *p;
+static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
+ const char *e;
int r;
- for (;;) {
- _cleanup_free_ char *word = NULL;
- const char *c;
+ assert(ret);
+
+ /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
+ e = secure_getenv("SYSTEMD_PROC_CMDLINE");
+ if (e)
+ return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
- r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (detect_container() > 0) {
+ _cleanup_strv_free_ char **args = NULL;
+
+ r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
if (r < 0)
return r;
- if (r == 0)
- break;
- /* Filter out arguments that are intended only for the initrd */
- c = startswith(word, "rd.");
- if (c) {
- if (!in_initrd())
- continue;
+ if (filter_pid1_args)
+ return proc_cmdline_filter_pid1_args(args, ret);
- if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
- r = free_and_strdup(&word, c);
- if (r < 0)
- return r;
- }
+ *ret = TAKE_PTR(args);
+ return 0;
+
+ } else {
+ _cleanup_free_ char *s = NULL;
- } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
- continue; /* And optionally filter out arguments that are intended only for the host */
+ r = read_one_line_file("/proc/cmdline", &s);
+ if (r < 0)
+ return r;
- *p = q;
- *ret_word = TAKE_PTR(word);
- return 1;
+ return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
}
+}
- *p = q;
- *ret_word = NULL;
- return 0;
+int proc_cmdline_strv(char ***ret) {
+ return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
}
-int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
- const char *p;
+static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
+ char *c;
+
+ c = startswith(word, "rd.");
+ if (c) {
+ /* Filter out arguments that are intended only for the initrd */
+
+ if (!in_initrd())
+ return NULL;
+
+ if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
+ return c;
+
+ } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
+ /* And optionally filter out arguments that are intended only for the host */
+ return NULL;
+
+ return (char*) word;
+}
+
+static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
int r;
assert(parse_item);
- /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
- * clear. */
+ /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
+ * make this clear. */
assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
- char *value;
+ STRV_FOREACH(word, args) {
+ char *key, *value;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0)
- break;
+ key = mangle_word(*word, flags);
+ if (!key)
+ continue;
- value = strchr(word, '=');
+ value = strchr(key, '=');
if (value)
- *(value++) = 0;
+ *(value++) = '\0';
- r = parse_item(word, value, data);
+ r = parse_item(key, value, data);
if (r < 0)
return r;
}
}
int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
- _cleanup_free_ char *line = NULL;
+ _cleanup_strv_free_ char **args = NULL;
int r;
assert(parse_item);
/* We parse the EFI variable first, because later settings have higher priority. */
if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+ _cleanup_free_ char *line = NULL;
+
r = systemd_efi_options_variable(&line);
if (r < 0) {
if (r != -ENODATA)
log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
} else {
- r = proc_cmdline_parse_given(line, parse_item, data, flags);
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
- line = mfree(line);
+ r = proc_cmdline_parse_strv(args, parse_item, data, flags);
+ if (r < 0)
+ return r;
+
+ args = strv_free(args);
}
}
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
- return proc_cmdline_parse_given(line, parse_item, data, flags);
+ return proc_cmdline_parse_strv(args, parse_item, data, flags);
}
static bool relaxed_equal_char(char a, char b) {
return true;
}
-static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
- _cleanup_free_ char *ret = NULL;
+static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
+ _cleanup_free_ char *v = NULL;
bool found = false;
- const char *p;
int r;
- assert(line);
assert(key);
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(p, args) {
+ const char *word;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0)
- break;
+ word = mangle_word(*p, flags);
+ if (!word)
+ continue;
if (ret_value) {
const char *e;
continue;
if (*e == '=') {
- r = free_and_strdup(&ret, e+1);
+ r = free_and_strdup(&v, e+1);
if (r < 0)
return r;
found = true;
} else {
- if (streq(word, key)) {
+ if (proc_cmdline_key_streq(word, key)) {
found = true;
break; /* we found what we were looking for */
}
}
if (ret_value)
- *ret_value = TAKE_PTR(ret);
+ *ret_value = TAKE_PTR(v);
return found;
}
int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
+ _cleanup_strv_free_ char **args = NULL;
_cleanup_free_ char *line = NULL, *v = NULL;
int r;
if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
return -EINVAL;
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
- return cmdline_get_key(line, key, flags, ret_value);
+ return cmdline_get_key(args, key, flags, ret_value);
- r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
+ r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL);
if (r < 0)
return r;
if (r > 0) {
return r;
}
- line = mfree(line);
r = systemd_efi_options_variable(&line);
if (r == -ENODATA) {
if (ret_value)
if (r < 0)
return r;
- return cmdline_get_key(line, key, flags, ret_value);
+ args = strv_free(args);
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
+
+ return cmdline_get_key(args, key, flags, ret_value);
}
int proc_cmdline_get_bool(const char *key, bool *ret) {
return 1;
}
-int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
- _cleanup_free_ char *line = NULL;
- bool processing_efi = true;
- const char *p;
- va_list ap;
+static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
int r, ret = 0;
- /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
- * this clear. */
- assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
+ for (;;) {
+ char **v;
+ const char *k, *e;
- /* This call may clobber arguments on failure! */
+ k = va_arg(ap, const char*);
+ if (!k)
+ break;
- if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
- r = systemd_efi_options_variable(&line);
- if (r < 0 && r != -ENODATA)
- log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
- }
+ assert_se(v = va_arg(ap, char**));
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(p, args) {
+ const char *word;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0) {
- /* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */
- if (processing_efi) {
- processing_efi = false;
+ word = mangle_word(*p, flags);
+ if (!word)
+ continue;
- line = mfree(line);
- r = proc_cmdline(&line);
+ e = proc_cmdline_key_startswith(word, k);
+ if (e && *e == '=') {
+ r = free_and_strdup(v, e + 1);
if (r < 0)
return r;
- p = line;
- continue;
+ ret++;
}
-
- break;
}
+ }
- va_start(ap, flags);
+ return ret;
+}
- for (;;) {
- char **v;
- const char *k, *e;
+int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
+ _cleanup_strv_free_ char **args = NULL;
+ int r, ret = 0;
+ va_list ap;
- k = va_arg(ap, const char*);
- if (!k)
- break;
+ /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
+ * this clear. */
+ assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
- assert_se(v = va_arg(ap, char**));
+ /* This call may clobber arguments on failure! */
- e = proc_cmdline_key_startswith(word, k);
- if (e && *e == '=') {
- r = free_and_strdup(v, e + 1);
- if (r < 0) {
- va_end(ap);
- return r;
- }
+ if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+ _cleanup_free_ char *line = NULL;
- ret++;
- }
- }
+ r = systemd_efi_options_variable(&line);
+ if (r < 0 && r != -ENODATA)
+ log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
+ if (r >= 0) {
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
- va_end(ap);
+ va_start(ap, flags);
+ r = cmdline_get_key_ap(flags, args, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ ret = r;
+ args = strv_free(args);
+ }
}
- return ret;
+ r = proc_cmdline_strv(&args);
+ if (r < 0)
+ return r;
+
+ va_start(ap, flags);
+ r = cmdline_get_key_ap(flags, args, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ return ret + r;
}
typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data);
+int proc_cmdline_filter_pid1_args(char **argv, char ***ret);
+
int proc_cmdline(char **ret);
+int proc_cmdline_strv(char ***ret);
-int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags);
int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, ProcCmdlineFlags flags);
int proc_cmdline_get_key(const char *parameter, ProcCmdlineFlags flags, char **value);
_cleanup_strv_free_ char **args = NULL;
- args = strv_parse_nulstr(t, k);
+ /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end.
+ * See also issue #21186. */
+ args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
if (!args)
return -ENOMEM;
- /* Drop trailing empty strings. See issue #21186. */
- STRV_FOREACH_BACKWARDS(p, args) {
- if (!isempty(*p))
- break;
-
- *p = mfree(*p);
- }
-
ans = quote_command_line(args, shflags);
if (!ans)
return -ENOMEM;
return 0;
}
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
+ _cleanup_free_ char *t = NULL;
+ char **args;
+ size_t k;
+ int r;
+
+ assert(pid >= 0);
+ assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0);
+ assert(ret);
+
+ r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
+ if (r < 0)
+ return r;
+
+ args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
+ if (!args)
+ return -ENOMEM;
+
+ *ret = args;
+ return 0;
+}
+
int container_get_leader(const char *machine, pid_t *pid) {
_cleanup_free_ char *s = NULL, *class = NULL;
const char *p;
/* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
log_close();
log_set_open_when_needed(true);
+ log_settle_target();
}
if (name) {
}
}
+ if (!FLAGS_SET(flags, FORK_KEEP_NOTIFY_SOCKET)) {
+ r = RET_NERRNO(unsetenv("NOTIFY_SOCKET"));
+ if (r < 0) {
+ log_full_errno(prio, r, "Failed to unset $NOTIFY_SOCKET: %m");
+ _exit(EXIT_FAILURE);
+ }
+ }
+
if (ret_pid)
*ret_pid = getpid_cached();
char *p;
int r;
+ /* Converts a pidfd into a pid. Well known errors:
+ *
+ * -EBADF → fd invalid
+ * -ENOSYS → /proc/ not mounted
+ * -ENOTTY → fd valid, but not a pidfd
+ * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
+ * -ESRCH → fd valid, but process is already reaped
+ */
+
if (fd < 0)
return -EBADF;
r = read_full_virtual_file(path, &fdinfo, NULL);
if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */
- return -ESRCH;
+ return proc_mounted() > 0 ? -EBADF : -ENOSYS;
if (r < 0)
return r;
- p = startswith(fdinfo, "Pid:");
- if (!p) {
- p = strstr(fdinfo, "\nPid:");
- if (!p)
- return -ENOTTY; /* not a pidfd? */
-
- p += 5;
- }
+ p = find_line_startswith(fdinfo, "Pid:");
+ if (!p)
+ return -ENOTTY; /* not a pidfd? */
p += strspn(p, WHITESPACE);
p[strcspn(p, WHITESPACE)] = 0;
+ if (streq(p, "0"))
+ return -EREMOTE; /* PID is in foreign PID namespace? */
+ if (streq(p, "-1"))
+ return -ESRCH; /* refers to reaped process? */
+
return parse_pid(p, ret);
}
int get_process_comm(pid_t pid, char **ret);
int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret);
int get_process_exe(pid_t pid, char **ret);
int get_process_uid(pid_t pid, uid_t *ret);
int get_process_gid(pid_t pid, gid_t *ret);
FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */
FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace */
FORK_CLOEXEC_OFF = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
+ FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
} ForkFlags;
int safe_fork_full(
bool ratelimit_below(RateLimit *r) {
usec_t ts;
- bool good = false;
assert(r);
if (r->begin <= 0 ||
usec_sub_unsigned(ts, r->begin) > r->interval) {
- r->begin = ts;
+ r->begin = ts; /* Start a new time window */
+ r->num = 1; /* Reset counter */
+ return true;
+ }
- /* Reset counter */
- r->num = 0;
- good = true;
- } else if (r->num < r->burst)
- good = true;
+ if (_unlikely_(r->num == UINT_MAX))
+ return false;
r->num++;
- return good;
+ return r->num <= r->burst;
}
unsigned ratelimit_num_dropped(RateLimit *r) {
assert(r);
- return r->num > r->burst ? r->num - r->burst : 0;
+ if (r->num == UINT_MAX) /* overflow, return as special case */
+ return UINT_MAX;
+
+ return LESS_BY(r->num, r->burst);
+}
+
+usec_t ratelimit_end(const RateLimit *rl) {
+ assert(rl);
+
+ if (rl->begin == 0)
+ return 0;
+
+ return usec_add(rl->begin, rl->interval);
}
bool ratelimit_below(RateLimit *r);
unsigned ratelimit_num_dropped(RateLimit *r);
+
+usec_t ratelimit_end(const RateLimit *rl);
}
if (found)
- *ret_fd = *(int*) CMSG_DATA(found);
+ *ret_fd = *CMSG_TYPED_DATA(found, int);
else
*ret_fd = -EBADF;
return NULL;
}
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len) {
+ struct cmsghdr *cmsg;
+
+ assert(mh);
+ assert(buf);
+ assert(buf_len > 0);
+
+ /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used
+ * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on
+ * RISCV32. See issue #27241. */
+
+ cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len));
+ if (!cmsg)
+ return NULL;
+
+ return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len);
+}
+
int socket_ioctl_fd(void) {
int fd;
#define CMSG_FOREACH(cmsg, mh) \
for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
+/* Returns the cmsghdr's data pointer, but safely cast to the specified type. Does two alignment checks: one
+ * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one
+ * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as
+ * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8
+ * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as
+ * riscv32. */
#define CMSG_TYPED_DATA(cmsg, type) \
({ \
- struct cmsghdr *_cmsg = cmsg; \
+ struct cmsghdr *_cmsg = (cmsg); \
+ assert_cc(alignof(type) <= alignof(struct cmsghdr)); \
_cmsg ? CAST_ALIGN_PTR(type, CMSG_DATA(_cmsg)) : (type*) NULL; \
})
struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len);
/* Type-safe, dereferencing version of cmsg_find() */
#define CMSG_FIND_DATA(mh, level, type, ctype) \
CMSG_TYPED_DATA(cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))), ctype)
+/* Type-safe version of cmsg_find_and_copy_data() */
+#define CMSG_FIND_AND_COPY_DATA(mh, level, type, ctype) \
+ (ctype*) cmsg_find_and_copy_data(mh, level, type, &(ctype){}, sizeof(ctype))
+
/* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type
* itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr"
* structures. */
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "errno-util.h"
#include "fd-util.h"
if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
return true;
- r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
+ r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
if (r < 0)
return r;
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;
assert(fileb);
if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
- return -errno;
+ return log_debug_errno(errno, "Cannot stat %s: %m", filea);
if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
- return -errno;
+ return log_debug_errno(errno, "Cannot stat %s: %m", fileb);
return stat_inode_same(&a, &b);
}
return stat_verify_regular(&st);
}
+int verify_regular_at(int dir_fd, const char *path, bool follow) {
+ struct stat st;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
+ return -errno;
+
+ return stat_verify_regular(&st);
+}
+
int stat_verify_directory(const struct stat *st) {
assert(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 stat_verify_regular(const struct stat *st);
int fd_verify_regular(int fd);
+int verify_regular_at(int dir_fd, const char *path, bool follow);
int stat_verify_directory(const struct stat *st);
int fd_verify_directory(int fd);
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);
#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,) \
_DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
+#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
return strndup(a, strcspn(a, reject));
}
+
+char *find_line_startswith(const char *haystack, const char *needle) {
+ char *p;
+
+ assert(haystack);
+ assert(needle);
+
+ /* Finds the first line in 'haystack' that starts with the specified string. Returns a pointer to the
+ * first character after it */
+
+ p = strstr(haystack, needle);
+ if (!p)
+ return NULL;
+
+ if (p > haystack)
+ while (p[-1] != '\n') {
+ p = strstr(p + 1, needle);
+ if (!p)
+ return NULL;
+ }
+
+ return p + strlen(needle);
+}
+
+char *startswith_strv(const char *string, char **strv) {
+ char *found = NULL;
+
+ STRV_FOREACH(i, strv) {
+ found = startswith(string, *i);
+ if (found)
+ break;
+ }
+
+ return found;
+}
return strstr(haystack, needle);
}
+static inline char *strstrafter(const char *haystack, const char *needle) {
+ char *p;
+
+ /* Returns NULL if not found, or pointer to first character after needle if found */
+
+ p = strstr_ptr(haystack, needle);
+ if (!p)
+ return NULL;
+
+ return p + strlen(needle);
+}
+
static inline const char* strnull(const char *s) {
return s ?: "(null)";
}
char *strdupspn(const char *a, const char *accept);
char *strdupcspn(const char *a, const char *reject);
+
+char *find_line_startswith(const char *haystack, const char *needle);
+
+char *startswith_strv(const char *string, char **strv);
+
+#define STARTSWITH_SET(p, ...) \
+ startswith_strv(p, STRV_MAKE(__VA_ARGS__))
#include <stdlib.h>
#include "alloc-util.h"
+#include "env-util.h"
#include "escape.h"
#include "extract-word.h"
#include "fileio.h"
return NULL;
}
+char* strv_find_first_field(char * const *needles, char * const *haystack) {
+ STRV_FOREACH(k, needles) {
+ char *value = strv_env_pairs_get((char **)haystack, *k);
+ if (value)
+ return value;
+ }
+
+ return NULL;
+}
+
char** strv_free(char **l) {
STRV_FOREACH(k, l)
free(*k);
char* strv_find_case(char * const *l, const char *name) _pure_;
char* strv_find_prefix(char * const *l, const char *name) _pure_;
char* strv_find_startswith(char * const *l, const char *name) _pure_;
+/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
+ * of the first key from the first vector that is found in the second vector. */
+char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;
#define strv_contains(l, s) (!!strv_find((l), (s)))
#define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
_x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
})
-#define STARTSWITH_SET(p, ...) \
- ({ \
- const char *_p = (p); \
- char *_found = NULL; \
- STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \
- _found = startswith(_p, *_i); \
- if (_found) \
- break; \
- } \
- _found; \
- })
-
#define ENDSWITH_SET(p, ...) \
({ \
const char *_p = (p); \
&ttynr) != 1)
return -EIO;
- if (major(ttynr) == 0 && minor(ttynr) == 0)
+ if (devnum_is_zero(ttynr))
return -ENXIO;
if (d)
return fd;
}
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
_cleanup_free_ char *tmp = NULL;
int r, fd;
* which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
* "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
- fd = open_parent(target, O_TMPFILE|flags, 0640);
+ fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
if (fd >= 0) {
*ret_path = NULL;
return fd;
if (r < 0)
return r;
- fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+ fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
if (fd < 0)
return -errno;
return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
}
-int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, bool replace) {
_cleanup_free_ char *tmp = NULL;
int r;
assert(fd >= 0);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(target);
/* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
if (path) {
if (replace)
- return RET_NERRNO(rename(path, target));
+ return RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
- return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+ return rename_noreplace(dir_fd, path, dir_fd, target);
}
- r = link_fd(fd, AT_FDCWD, target);
+ r = link_fd(fd, dir_fd, target);
if (r != -EEXIST || !replace)
return r;
if (r < 0)
return r;
- if (link_fd(fd, AT_FDCWD, tmp) < 0)
+ if (link_fd(fd, dir_fd, tmp) < 0)
return -EEXIST; /* propagate original error */
- r = RET_NERRNO(rename(tmp, target));
+ r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
if (r < 0) {
- (void) unlink(tmp);
+ (void) unlinkat(dir_fd, tmp, 0);
return r;
}
#pragma once
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
int tempfn_random_child(const char *p, const char *extra, char **ret);
int open_tmpfile_unlinkable(const char *directory, int flags);
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path);
+static inline int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+ return open_tmpfile_linkable_at(AT_FDCWD, target, flags, ret_path);
+}
int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file);
-int link_tmpfile(int fd, const char *path, const char *target, bool replace);
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, bool replace);
+static inline int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
+ return link_tmpfile_at(fd, AT_FDCWD, path, target, replace);
+}
int flink_tmpfile(FILE *f, const char *path, const char *target, bool replace);
int mkdtemp_malloc(const char *template, char **ret);
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_DEAD_RESOURCES_PINNED] = "dead-resources-pinned",
+ [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_DEAD_RESOURCES_PINNED, /* Like SERVICE_DEAD, but with pinned resources */
SERVICE_AUTO_RESTART,
SERVICE_CLEANING,
_SERVICE_STATE_MAX,
#include "sd-id128.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fs-util.h"
}
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 */
}
/* Get rid of "." and ".." components in target path */
- r = chase_symlinks(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
+ r = chase(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
if (r < 0)
return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m",
dir, filename, target);
if (r < 0)
return log_oom();
- r = chase_symlinks(*dir, NULL, 0, &resolved_dir, NULL);
+ r = chase(*dir, NULL, 0, &resolved_dir, NULL);
if (r < 0) {
if (r != -ENOENT)
log_warning_errno(r, "Failed to resolve symlink %s, ignoring: %m", *dir);
}
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 */
#include "sd-messages.h"
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
-#include "path-util.h"
#include "random-util.h"
#include "string-util.h"
#include "strv.h"
"/usr/bin/true");
}
-const char* default_root_shell(const char *root) {
+const char* default_root_shell_at(int rfd) {
/* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
* will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
* or any access errors. */
- int r = chase_symlinks(DEFAULT_USER_SHELL, root, CHASE_PREFIX_ROOT, NULL, NULL);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
if (r < 0 && r != -ENOENT)
- log_debug_errno(r, "Failed to look up shell '%s%s%s': %m",
- strempty(root), root ? "/" : "", DEFAULT_USER_SHELL);
+ log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
if (r > 0)
return DEFAULT_USER_SHELL;
return "/bin/sh";
}
+const char *default_root_shell(const char *root) {
+ _cleanup_close_ int rfd = -EBADF;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return "/bin/sh";
+
+ return default_root_shell_at(rfd);
+}
+
static int synthesize_user_creds(
const char **username,
uid_t *uid, gid_t *gid,
#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 */
#endif
bool is_nologin_shell(const char *shell);
+const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
int is_this_me(const char *username);
* so we fallback to using the product name which is less restricted
* to distinguish metal systems from virtualized instances */
_cleanup_free_ char *s = NULL;
+ const char *e;
r = read_full_virtual_file("/sys/class/dmi/id/product_name", &s, NULL);
/* In EC2, virtualized is much more common than metal, so if for some reason
" assuming virtualized: %m");
return VIRTUALIZATION_AMAZON;
}
- if (endswith(truncate_nl(s), ".metal")) {
- log_debug("DMI product name ends with '.metal', assuming no virtualization");
+ e = strstrafter(truncate_nl(s), ".metal");
+ if (e && IN_SET(*e, 0, '-')) {
+ log_debug("DMI product name has '.metal', assuming no virtualization");
return VIRTUALIZATION_NONE;
} else
return VIRTUALIZATION_AMAZON;
#include "bootctl-install.h"
#include "bootctl-random-seed.h"
#include "bootctl-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "copy.h"
#include "dirent-util.h"
#include "efi-api.h"
bool layout_type1 = use_boot_loader_spec_type1();
if (arg_make_entry_directory < 0) { /* Automatic mode */
if (layout_type1) {
- if (arg_entry_token_type == ARG_ENTRY_TOKEN_MACHINE_ID) {
+ if (arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID) {
r = path_is_temporary_fs("/etc/machine-id");
if (r < 0)
return log_debug_errno(r, "Couldn't determine whether /etc/machine-id is on a temporary file system: %m");
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 (!p)
return log_oom();
- r = chase_symlinks(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
+ r = chase(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
/* 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_symlinks(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
+ r = chase(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
if (r < 0)
return log_error_errno(r,
"Failed to resolve path %s%s%s: %m",
if (!q)
return log_oom();
- r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
+ r = chase(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path);
v = strjoina("/EFI/BOOT/BOOT", e);
ascii_strupper(strrchr(v, '/') + 1);
- r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
+ r = chase(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path);
_cleanup_free_ char *path = NULL;
int r;
- r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+ r = chase_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
/* 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_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+ 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;
/* Let's save the used entry token in /etc/kernel/entry-token if we used it to create the entry
* directory, or if anything else but the machine ID */
- if (!arg_make_entry_directory && arg_entry_token_type == ARG_ENTRY_TOKEN_MACHINE_ID)
+ if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
return 0;
p = path_join(arg_root, etc_kernel(), "entry-token");
uint64_t psize,
sd_id128_t uuid,
const char *path,
- bool first) {
+ bool first,
+ bool graceful) {
uint16_t slot;
int r;
return 0;
}
- r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
+ r = chase_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
if (r == -ENOENT)
return 0;
if (r < 0)
return log_error_errno(r, "Cannot access \"%s/%s\": %m", esp_path, path);
r = find_slot(uuid, path, &slot);
- if (r < 0)
- return log_error_errno(r,
- r == -ENOENT ?
- "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" :
- "Failed to determine current boot order: %m");
+ if (r < 0) {
+ int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
+ const char *skip = graceful ? ", skipping" : "";
+
+ log_full_errno(level, r,
+ r == -ENOENT ?
+ "Failed to access EFI variables%s. Is the \"efivarfs\" filesystem mounted?" :
+ "Failed to determine current boot order%s: %m", skip);
+
+ return graceful ? 0 : r;
+ }
if (first || r == 0) {
r = efi_add_boot_option(slot, pick_efi_boot_option_description(),
part, pstart, psize,
uuid, path);
- if (r < 0)
- return log_error_errno(r, "Failed to create EFI Boot variable entry: %m");
+ if (r < 0) {
+ int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
+ const char *skip = graceful ? ", skipping" : "";
+
+ log_full_errno(level, r, "Failed to create EFI Boot variable entry%s: %m", skip);
+
+ return graceful ? 0 : r;
+ }
log_info("Created EFI boot entry \"%s\".", pick_efi_boot_option_description());
}
}
char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi");
- return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install);
+ return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install, graceful);
}
static int remove_boot_efi(const char *esp_path) {
_cleanup_free_ char *p = NULL;
int r, c = 0;
- r = chase_symlinks_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
+ r = chase_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
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);
#include "bootctl-status.h"
#include "bootctl-util.h"
#include "bootspec.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "devnum-util.h"
#include "dirent-util.h"
#include "efi-api.h"
assert(previous);
assert(is_first);
- r = chase_symlinks_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
+ r = chase_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
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);
return;
if (arg_dry_run) {
- r = chase_symlinks_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
+ r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
if (r < 0)
log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn);
else
return;
}
- r = chase_symlinks_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
+ r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
if (r >= 0)
log_info("Removed \"%s\"", path);
else if (r != -ENOENT)
_cleanup_free_ char *d = NULL;
if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) {
- r = chase_symlinks_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
+ r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT))
log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d);
}
if (arg_dry_run)
log_info("Would remove \"%s\"", e->path);
else {
- r = chase_symlinks_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
+ r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
if (r < 0)
return log_error_errno(r, "Failed to remove \"%s\": %m", e->path);
if (r < 0)
return log_error_errno(r, "Failed to count files in %s: %m", root);
- dir_fd = chase_symlinks_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
+ dir_fd = chase_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
O_DIRECTORY|O_CLOEXEC, &full);
if (dir_fd == -ENOENT)
return 0;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <fcntl.h>
+
#include "alloc-util.h"
#include "bootctl-uki.h"
#include "kernel-image.h"
KernelImageType t;
int r;
- r = inspect_kernel(argv[1], &t, NULL, NULL, NULL);
+ r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL);
if (r < 0)
return r;
KernelImageType t;
int r;
- r = inspect_kernel(argv[1], &t, &cmdline, &uname, &pname);
+ r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname);
if (r < 0)
return r;
#include "bootctl.h"
#include "bootctl-util.h"
#include "fileio.h"
-#include "os-util.h"
-#include "path-util.h"
#include "stat-util.h"
#include "sync-util.h"
-#include "utf8.h"
int sync_everything(void) {
int ret = 0, k;
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 settle_entry_token(void) {
int r;
- switch (arg_entry_token_type) {
-
- case ARG_ENTRY_TOKEN_AUTO: {
- _cleanup_free_ char *buf = NULL, *p = NULL;
- p = path_join(arg_root, etc_kernel(), "entry-token");
- if (!p)
- return log_oom();
- r = read_one_line_file(p, &buf);
- if (r < 0 && r != -ENOENT)
- return log_error_errno(r, "Failed to read %s: %m", p);
-
- if (!isempty(buf)) {
- free_and_replace(arg_entry_token, buf);
- arg_entry_token_type = ARG_ENTRY_TOKEN_LITERAL;
- } else if (sd_id128_is_null(arg_machine_id)) {
- _cleanup_free_ char *id = NULL, *image_id = NULL;
-
- r = parse_os_release(arg_root,
- "IMAGE_ID", &image_id,
- "ID", &id);
- if (r < 0)
- return log_error_errno(r, "Failed to load /etc/os-release: %m");
-
- if (!isempty(image_id)) {
- free_and_replace(arg_entry_token, image_id);
- arg_entry_token_type = ARG_ENTRY_TOKEN_OS_IMAGE_ID;
- } else if (!isempty(id)) {
- free_and_replace(arg_entry_token, id);
- arg_entry_token_type = ARG_ENTRY_TOKEN_OS_ID;
- } else
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set, and /etc/os-release carries no ID=/IMAGE_ID= fields.");
- } else {
- r = free_and_strdup_warn(&arg_entry_token, SD_ID128_TO_STRING(arg_machine_id));
- if (r < 0)
- return r;
-
- arg_entry_token_type = ARG_ENTRY_TOKEN_MACHINE_ID;
- }
-
- break;
- }
-
- case ARG_ENTRY_TOKEN_MACHINE_ID:
- if (sd_id128_is_null(arg_machine_id))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set.");
-
- r = free_and_strdup_warn(&arg_entry_token, SD_ID128_TO_STRING(arg_machine_id));
- if (r < 0)
- return r;
-
- break;
-
- case ARG_ENTRY_TOKEN_OS_IMAGE_ID: {
- _cleanup_free_ char *buf = NULL;
-
- r = parse_os_release(arg_root, "IMAGE_ID", &buf);
- if (r < 0)
- return log_error_errno(r, "Failed to load /etc/os-release: %m");
-
- if (isempty(buf))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "IMAGE_ID= field not set in /etc/os-release.");
-
- free_and_replace(arg_entry_token, buf);
- break;
- }
-
- case ARG_ENTRY_TOKEN_OS_ID: {
- _cleanup_free_ char *buf = NULL;
-
- r = parse_os_release(arg_root, "ID", &buf);
- if (r < 0)
- return log_error_errno(r, "Failed to load /etc/os-release: %m");
-
- if (isempty(buf))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ID= field not set in /etc/os-release.");
-
- free_and_replace(arg_entry_token, buf);
- break;
- }
-
- case ARG_ENTRY_TOKEN_LITERAL:
- assert(!isempty(arg_entry_token)); /* already filled in by command line parser */
- break;
- }
-
- if (isempty(arg_entry_token) || !(utf8_is_valid(arg_entry_token) && string_is_safe(arg_entry_token)))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected entry token not valid: %s", arg_entry_token);
+ r = boot_entry_token_ensure(
+ arg_root,
+ etc_kernel(),
+ arg_machine_id,
+ /* machine_id_is_random = */ false,
+ &arg_entry_token_type,
+ &arg_entry_token);
+ if (r < 0)
+ return r;
log_debug("Using entry token: %s", arg_entry_token);
return 0;
int arg_make_entry_directory = false; /* tri-state: < 0 for automatic logic */
sd_id128_t arg_machine_id = SD_ID128_NULL;
char *arg_install_layout = NULL;
-EntryTokenType arg_entry_token_type = ARG_ENTRY_TOKEN_AUTO;
+BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
char *arg_entry_token = NULL;
JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
bool arg_arch_all = false;
InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO;
char *arg_efi_boot_option_description = NULL;
bool arg_dry_run = false;
+ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
int acquire_esp(
bool unprivileged_mode,
" --boot-path=PATH Path to the $BOOT partition\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY\n"
+ " Specify disk image dissection policy\n"
" --install-source=auto|image|host\n"
" Where to pick files when using --root=/--image=\n"
" -p --print-esp-path Print path to the EFI System Partition mount point\n"
ARG_BOOT_PATH,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_INSTALL_SOURCE,
ARG_VERSION,
ARG_NO_VARIABLES,
{ "boot-path", required_argument, NULL, ARG_BOOT_PATH },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "install-source", required_argument, NULL, ARG_INSTALL_SOURCE },
{ "print-esp-path", no_argument, NULL, 'p' },
{ "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */
};
int c, r;
- bool b;
assert(argc >= 0);
assert(argv);
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_INSTALL_SOURCE:
if (streq(optarg, "auto"))
arg_install_source = ARG_INSTALL_SOURCE_AUTO;
arg_quiet = true;
break;
- case ARG_ENTRY_TOKEN: {
- const char *e;
-
- if (streq(optarg, "machine-id")) {
- arg_entry_token_type = ARG_ENTRY_TOKEN_MACHINE_ID;
- arg_entry_token = mfree(arg_entry_token);
- } else if (streq(optarg, "os-image-id")) {
- arg_entry_token_type = ARG_ENTRY_TOKEN_OS_IMAGE_ID;
- arg_entry_token = mfree(arg_entry_token);
- } else if (streq(optarg, "os-id")) {
- arg_entry_token_type = ARG_ENTRY_TOKEN_OS_ID;
- arg_entry_token = mfree(arg_entry_token);
- } else if ((e = startswith(optarg, "literal:"))) {
- arg_entry_token_type = ARG_ENTRY_TOKEN_LITERAL;
-
- r = free_and_strdup_warn(&arg_entry_token, e);
- if (r < 0)
- return r;
- } else
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Unexpected parameter for --entry-token=: %s", optarg);
-
+ case ARG_ENTRY_TOKEN:
+ r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
+ if (r < 0)
+ return r;
break;
- }
case ARG_MAKE_ENTRY_DIRECTORY:
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;
_cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
int r;
- log_parse_environment();
- log_open();
+ log_setup();
/* If we run in a container, automatically turn off EFI file system access */
if (detect_container() > 0)
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK,
&unlink_dir,
#include "sd-id128.h"
+#include "boot-entry.h"
+#include "image-policy.h"
#include "json.h"
#include "pager.h"
-typedef enum EntryTokenType {
- ARG_ENTRY_TOKEN_MACHINE_ID,
- ARG_ENTRY_TOKEN_OS_IMAGE_ID,
- ARG_ENTRY_TOKEN_OS_ID,
- ARG_ENTRY_TOKEN_LITERAL,
- ARG_ENTRY_TOKEN_AUTO,
-} EntryTokenType;
-
typedef enum InstallSource {
ARG_INSTALL_SOURCE_IMAGE,
ARG_INSTALL_SOURCE_HOST,
extern int arg_make_entry_directory; /* tri-state: < 0 for automatic logic */
extern sd_id128_t arg_machine_id;
extern char *arg_install_layout;
-extern EntryTokenType arg_entry_token_type;
+extern BootEntryTokenType arg_entry_token_type;
extern char *arg_entry_token;
extern JsonFormatFlags arg_json_format_flags;
extern bool arg_arch_all;
extern InstallSource arg_install_source;
extern char *arg_efi_boot_option_description;
extern bool arg_dry_run;
+extern ImagePolicy *arg_image_policy;
static inline const char *arg_dollar_boot_path(void) {
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
efi_c_ld_args_primary = efi_c_ld_args
efi_c_ld_args_alt = efi_c_ld_args
-efi_c_args_primary += {
+efi_arch_c_args = {
'aarch64' : ['-mgeneral-regs-only'],
'arm' : ['-mgeneral-regs-only'],
'x86_64' : ['-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'],
- 'x86' : ['-march=i686', '-mgeneral-regs-only'],
-}.get(host_machine.cpu_family(), [])
+ 'x86' : ['-march=i686', '-mgeneral-regs-only', '-malign-double'],
+}
+efi_c_args_primary += efi_arch_c_args.get(host_machine.cpu_family(), [])
if efi_arch_alt == 'ia32'
- efi_c_args_alt += ['-m32', '-march=i686', '-mgeneral-regs-only']
+ efi_c_args_alt += ['-m32', efi_arch_c_args['x86']]
efi_c_ld_args_alt += '-m32'
endif
uint32_t entry_point;
} _packed_ LinuxPeCompat1;
- while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) {
+ while (size >= sizeof(LinuxPeCompat1) && addr % alignof(LinuxPeCompat1) == 0) {
LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) dos + addr);
if (compat->type == 0 || compat->size == 0 || compat->size > size)
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "graphics.h"
+#include "logarithm.h"
#include "proto/graphics-output.h"
#include "splash.h"
#include "unaligned-fundamental.h"
channel_shift[R] = __builtin_ctz(dib->channel_mask_r);
channel_shift[G] = __builtin_ctz(dib->channel_mask_g);
channel_shift[B] = __builtin_ctz(dib->channel_mask_b);
- channel_scale[R] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_r)) - 1);
- channel_scale[G] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_g)) - 1);
- channel_scale[B] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_b)) - 1);
+ channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1);
+ channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1);
+ channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1);
if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) {
channel_mask[A] = dib->channel_mask_a;
channel_shift[A] = __builtin_ctz(dib->channel_mask_a);
- channel_scale[A] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_a)) - 1);
+ channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1);
} else {
channel_mask[A] = 0;
channel_shift[A] = 0;
mangle_stub_cmdline(cmdline);
}
+ /* SMBIOS strings are measured in PCR1, so we do not re-measure these command line extensions. */
const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
if (extra) {
_cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra);
#include "util.h"
#include "vmm.h"
-#ifdef __x86_64__
-static uint64_t ticks_read(void) {
- uint64_t a, d;
+#if defined(__i386__) || defined(__x86_64__)
+# include <cpuid.h>
+static uint64_t ticks_read_arch(void) {
/* The TSC might or might not be virtualized in VMs (and thus might not be accurate or start at zero
* at boot), depending on hypervisor and CPU functionality. If it's not virtualized it's not useful
* for keeping time, hence don't attempt to use it. */
if (in_hypervisor())
return 0;
- __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
- return (d << 32) | a;
+ return __builtin_ia32_rdtsc();
}
-#elif defined(__i386__)
-static uint64_t ticks_read(void) {
- uint64_t val;
- if (in_hypervisor())
+static uint64_t ticks_freq_arch(void) {
+ /* Detect TSC frequency from CPUID information if available. */
+
+ unsigned max_leaf, ebx, ecx, edx;
+ if (__get_cpuid(0, &max_leaf, &ebx, &ecx, &edx) == 0)
return 0;
- __asm__ volatile ("rdtsc" : "=A" (val));
- return val;
+ /* Leaf 0x15 is Intel only. */
+ if (max_leaf < 0x15 || ebx != signature_INTEL_ebx || ecx != signature_INTEL_ecx ||
+ edx != signature_INTEL_edx)
+ return 0;
+
+ unsigned denominator, numerator, crystal_hz;
+ __cpuid(0x15, denominator, numerator, crystal_hz, edx);
+ if (denominator == 0 || numerator == 0)
+ return 0;
+
+ uint64_t freq = crystal_hz;
+ if (crystal_hz == 0) {
+ /* If the crystal frquency is not available, try to deduce it from
+ * the processor frequency leaf if available. */
+ if (max_leaf < 0x16)
+ return 0;
+
+ unsigned core_mhz;
+ __cpuid(0x16, core_mhz, ebx, ecx, edx);
+ freq = core_mhz * 1000ULL * 1000ULL * denominator / numerator;
+ }
+
+ return freq * numerator / denominator;
}
+
#elif defined(__aarch64__)
-static uint64_t ticks_read(void) {
+
+static uint64_t ticks_read_arch(void) {
uint64_t val;
asm volatile("mrs %0, cntvct_el0" : "=r"(val));
return val;
}
-#else
-static uint64_t ticks_read(void) {
- return 0;
-}
-#endif
-#if defined(__aarch64__)
-static uint64_t ticks_freq(void) {
+static uint64_t ticks_freq_arch(void) {
uint64_t freq;
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
return freq;
}
+
#else
-/* count TSC ticks during a millisecond delay */
+
+static uint64_t ticks_read_arch(void) {
+ return 0;
+}
+
+static uint64_t ticks_freq_arch(void) {
+ return 0;
+}
+
+#endif
+
static uint64_t ticks_freq(void) {
- uint64_t ticks_start, ticks_end;
static uint64_t cache = 0;
if (cache != 0)
return cache;
- ticks_start = ticks_read();
+ cache = ticks_freq_arch();
+ if (cache != 0)
+ return cache;
+
+ /* As a fallback, count ticks during a millisecond delay. */
+ uint64_t ticks_start = ticks_read_arch();
BS->Stall(1000);
- ticks_end = ticks_read();
+ uint64_t ticks_end = ticks_read_arch();
if (ticks_end < ticks_start) /* Check for an overflow (which is not that unlikely, given on some
* archs the value is 32bit) */
cache = (ticks_end - ticks_start) * 1000UL;
return cache;
}
-#endif
uint64_t time_usec(void) {
- uint64_t ticks, freq;
-
- ticks = ticks_read();
+ uint64_t ticks = ticks_read_arch();
if (ticks == 0)
return 0;
- freq = ticks_freq();
+ uint64_t freq = ticks_freq();
if (freq == 0)
return 0;
# define notify_debugger(i, w)
#endif
+/* On x86 the compiler assumes a different incoming stack alignment than what we get.
+ * This will cause long long variables to be misaligned when building with
+ * '-mlong-double' (for correct struct layouts). Normally, the compiler realigns the
+ * stack itself on entry, but we have to do this ourselves here as the compiler does
+ * not know that this is our entry point. */
+#ifdef __i386__
+# define _realign_stack_ __attribute__((force_align_arg_pointer))
+#else
+# define _realign_stack_
+#endif
+
#define DEFINE_EFI_MAIN_FUNCTION(func, identity, wait_for_debugger) \
EFI_SYSTEM_TABLE *ST; \
EFI_BOOT_SERVICES *BS; \
EFI_RUNTIME_SERVICES *RT; \
+ _realign_stack_ \
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table); \
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) { \
ST = system_table; \
#include "blkid-util.h"
#include "blockdev-util.h"
#include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "efi-loader.h"
#include "efivars.h"
#include "escape.h"
if (optind != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
- dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
+ dfd = chase_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
if (dfd < 0)
return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
-static bool arg_full = false;
+static int arg_full = -1;
static const char *arg_address = NULL;
static bool arg_unique = false;
static bool arg_acquired = false;
if (!table)
return log_oom();
- if (arg_full)
+ if (arg_full > 0)
table_set_width(table, 0);
r = table_set_align_percent(table, table_get_cell(table, 0, COLUMN_PID), 100);
}
static void print_subtree(const char *prefix, const char *path, char **l) {
- const char *vertical, *space;
- char **n;
-
/* We assume the list is sorted. Let's first skip over the
* entry we are looking at. */
for (;;) {
l++;
}
- vertical = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
- space = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_SPACE));
+ const char
+ *vertical = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL)),
+ *space = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_SPACE));
for (;;) {
bool has_more = false;
+ char **n;
if (!*l || !path_startswith(*l, path))
break;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_xml = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(member_set_freep) Set *members = NULL;
- unsigned name_width, type_width, signature_width, result_width, j, k = 0;
- Member *m, **sorted = NULL;
+ unsigned name_width, type_width, signature_width, result_width;
+ Member *m;
const char *xml;
int r;
signature_width = strlen("SIGNATURE");
result_width = strlen("RESULT/VALUE");
- sorted = newa(Member*, set_size(members));
+ Member **sorted = newa(Member*, set_size(members));
+ size_t k = 0;
SET_FOREACH(m, members) {
if (argv[3] && !streq(argv[3], m->interface))
sorted[k++] = m;
}
- if (result_width > 40)
+ if (result_width > 40 && arg_full <= 0)
result_width = 40;
typesafe_qsort(sorted, k, member_compare_funcp);
(int) result_width, "RESULT/VALUE",
"FLAGS");
- for (j = 0; j < k; j++) {
+ for (size_t j = 0; j < k; j++) {
_cleanup_free_ char *ellipsized = NULL;
const char *rv;
bool is_interface;
" --verbose Show result values in long format\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
+ " --xml-interface Dump the XML description in introspect command\n"
" --expect-reply=BOOL Expect a method call reply\n"
" --auto-start=BOOL Auto-start destination service\n"
" --allow-interactive-authorization=BOOL\n"
assert_not_reached();
}
+ if (arg_full < 0)
+ arg_full = terminal_is_dumb();
+
return 1;
}
assert(argc >= 1);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "-hkalM:u::xc", options, NULL)) >= 0)
switch (c) {
}
if (n_ipv4 > 0) {
+ char *name = strjoina("4_", u->id);
ipv4_map_fd = bpf_map_new(
+ name,
BPF_MAP_TYPE_LPM_TRIE,
offsetof(struct bpf_lpm_trie_key, data) + sizeof(uint32_t),
sizeof(uint64_t),
}
if (n_ipv6 > 0) {
+ char *name = strjoina("6_", u->id);
ipv6_map_fd = bpf_map_new(
+ name,
BPF_MAP_TYPE_LPM_TRIE,
offsetof(struct bpf_lpm_trie_key, data) + sizeof(uint32_t)*4,
sizeof(uint64_t),
if (enabled) {
if (*fd_ingress < 0) {
- r = bpf_map_new(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
+ char *name = strjoina("I_", u->id);
+ r = bpf_map_new(name, BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
if (r < 0)
return r;
}
if (*fd_egress < 0) {
-
- r = bpf_map_new(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
+ char *name = strjoina("E_", u->id);
+ r = bpf_map_new(name, BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
if (r < 0)
return r;
#include "fileio.h"
#include "filesystems.h"
#include "log.h"
+#include "lsm-util.h"
#include "manager.h"
#include "mkdir.h"
#include "nulstr-util.h"
return 0;
}
-static int mac_bpf_use(void) {
- _cleanup_free_ char *lsm_list = NULL;
- static int cached_use = -1;
- int r;
-
- if (cached_use >= 0)
- return cached_use;
-
- cached_use = 0;
-
- r = read_one_line_file("/sys/kernel/security/lsm", &lsm_list);
- if (r < 0) {
- if (r != -ENOENT)
- log_notice_errno(r, "bpf-lsm: Failed to read /sys/kernel/security/lsm, assuming bpf is unavailable: %m");
- return 0;
- }
-
- for (const char *p = lsm_list;;) {
- _cleanup_free_ char *word = NULL;
-
- r = extract_first_word(&p, &word, ",", 0);
- if (r == 0)
- return 0;
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_notice_errno(r, "bpf-lsm: Failed to parse /sys/kernel/security/lsm, assuming bpf is unavailable: %m");
- return 0;
- }
-
- if (streq(word, "bpf"))
- return cached_use = 1;
- }
-}
-
bool lsm_bpf_supported(bool initialize) {
_cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
static int supported = -1;
if (!cgroup_bpf_supported())
return (supported = false);
- r = mac_bpf_use();
+ r = lsm_supported("bpf");
if (r < 0) {
log_warning_errno(r, "bpf-lsm: Can't determine whether the BPF LSM module is used: %m");
return (supported = false);
}
-
if (r == 0) {
log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"bpf-lsm: BPF LSM hook not enabled in the kernel, BPF LSM not supported");
+++ /dev/null
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-if conf.get('BPF_FRAMEWORK') != 1
- subdir_done()
-endif
-
-bpf_clang_flags = [
- '-std=gnu11',
- '-Wno-compare-distinct-pointer-types',
- '-fno-stack-protector',
- '-O2',
- '-target',
- 'bpf',
- '-g',
- '-c',
-]
-
-bpf_gcc_flags = [
- '-std=gnu11',
- '-fno-stack-protector',
- '-O2',
- '-mkernel=5.2',
- '-mcpu=v3',
- '-mco-re',
- '-gbtf',
- '-c',
-]
-
-# Generate defines that are appropriate to tell the compiler what architecture
-# we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__.
-# This dictionary contains the exceptions where this doesn't work.
-#
-# C.f. https://mesonbuild.com/Reference-tables.html#cpu-families
-# and src/basic/missing_syscall_def.h.
-cpu_arch_defines = {
- 'ppc' : ['-D__powerpc__'],
- 'ppc64' : ['-D__powerpc64__', '-D_CALL_ELF=2'],
- 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32'],
- 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64'],
- 'x86' : ['-D__i386__'],
-
- # For arm, assume hardware fp is available.
- 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP'],
-}
-
-bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(),
- ['-D__@0@__'.format(host_machine.cpu_family())])
-if bpf_compiler == 'gcc'
- bpf_arch_flags += ['-m' + host_machine.endian() + '-endian']
-endif
-
-libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir')
-
-bpf_o_unstripped_cmd = []
-if bpf_compiler == 'clang'
- bpf_o_unstripped_cmd += [
- clang,
- bpf_clang_flags,
- bpf_arch_flags,
- ]
-elif bpf_compiler == 'gcc'
- bpf_o_unstripped_cmd += [
- bpf_gcc,
- bpf_gcc_flags,
- bpf_arch_flags,
- ]
-endif
-
-bpf_o_unstripped_cmd += ['-I.']
-
-if not meson.is_cross_build() and bpf_compiler == 'clang'
- target_triplet_cmd = run_command('gcc', '-dumpmachine', check: false)
- if target_triplet_cmd.returncode() == 0
- target_triplet = target_triplet_cmd.stdout().strip()
- bpf_o_unstripped_cmd += [
- '-isystem',
- '/usr/include/@0@'.format(target_triplet)
- ]
- endif
-endif
-
-bpf_o_unstripped_cmd += [
- '-idirafter',
- libbpf_include_dir,
- '@INPUT@',
- '-o',
- '@OUTPUT@'
-]
-
-if bpftool_strip
- bpf_o_cmd = [
- bpftool,
- 'gen',
- 'object',
- '@OUTPUT@',
- '@INPUT@'
- ]
-elif bpf_compiler == 'clang'
- bpf_o_cmd = [
- llvm_strip,
- '-g',
- '@INPUT@',
- '-o',
- '@OUTPUT@'
- ]
-endif
-
-skel_h_cmd = [
- bpftool,
- 'gen',
- 'skeleton',
- '@INPUT@'
-]
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode);
-static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
+BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_proc, protect_proc, ProtectProc);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_proc_subset, proc_subset, ProcSubset);
return sd_bus_message_close_container(reply);
}
+static int property_get_image_policy(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ImagePolicy **pp = ASSERT_PTR(userdata);
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert(bus);
+ assert(property);
+ assert(reply);
+
+ r = image_policy_to_string(*pp ?: &image_policy_service, /* simplify= */ true, &s);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_append(reply, "s", s);
+}
+
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", bus_property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectHostname", "b", bus_property_get_bool, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RootImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, root_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MountImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, mount_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ExtensionImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, extension_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
/* Obsolete/redundant properties: */
SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
unsigned n = 0;
int r;
+ /* Drop Ex from the written setting. E.g. ExecStart=, not ExecStartEx=. */
+ const char *written_name = is_ex_prop ? strndupa(name, strlen(name) - 2) : name;
+
r = sd_bus_message_enter_container(message, 'a', is_ex_prop ? "(sasas)" : "(sasb)");
if (r < 0)
return r;
if (!f)
return -ENOMEM;
- fprintf(f, "%s=\n", name);
+ fprintf(f, "%s=\n", written_name);
LIST_FOREACH(command, c, *exec_command) {
_cleanup_free_ char *a = NULL, *exec_chars = NULL;
+ UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS |
+ (FLAGS_SET(c->flags, EXEC_COMMAND_NO_ENV_EXPAND) ? UNIT_ESCAPE_EXEC_SYNTAX : UNIT_ESCAPE_EXEC_SYNTAX_ENV);
exec_chars = exec_command_flags_to_exec_chars(c->flags);
if (!exec_chars)
return -ENOMEM;
- a = unit_concat_strv(c->argv, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX);
+ a = unit_concat_strv(c->argv, esc_flags);
if (!a)
return -ENOMEM;
if (streq_ptr(c->path, c->argv ? c->argv[0] : NULL))
- fprintf(f, "%s=%s%s\n", name, exec_chars, a);
+ fprintf(f, "%s=%s%s\n", written_name, exec_chars, a);
else {
_cleanup_free_ char *t = NULL;
const char *p;
- p = unit_escape_setting(c->path,
- UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX, &t);
+ p = unit_escape_setting(c->path, esc_flags, &t);
if (!p)
return -ENOMEM;
- fprintf(f, "%s=%s@%s %s\n", name, exec_chars, p, a);
+ fprintf(f, "%s=%s@%s %s\n", written_name, exec_chars, p, a);
}
}
if (r < 0)
return r;
- unit_write_setting(u, flags, name, buf);
+ unit_write_setting(u, flags, written_name, buf);
}
return 1;
static BUS_DEFINE_SET_TRANSIENT_PARSE(keyring_mode, ExecKeyringMode, exec_keyring_mode_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_proc, ProtectProc, protect_proc_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE(proc_subset, ProcSubset, proc_subset_from_string);
-static BUS_DEFINE_SET_TRANSIENT_PARSE(preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string);
+BUS_DEFINE_SET_TRANSIENT_PARSE(exec_preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string);
static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality);
static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check);
static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint64_t, "%" PRIu64, capability_set_to_string);
return bus_set_transient_proc_subset(u, name, &c->proc_subset, message, flags, error);
if (streq(name, "RuntimeDirectoryPreserve"))
- return bus_set_transient_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error);
+ return bus_set_transient_exec_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error);
if (streq(name, "UMask"))
return bus_set_transient_mode_t(u, name, &c->umask, message, flags, error);
return 1;
+ } else if (STR_IN_SET(name, "RootImagePolicy", "MountImagePolicy", "ExtensionImagePolicy")) {
+ _cleanup_(image_policy_freep) ImagePolicy *p = NULL;
+ const char *s;
+
+ r = sd_bus_message_read(message, "s", &s);
+ if (r < 0)
+ return r;
+
+ r = image_policy_from_string(s, &p);
+ if (r < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse image policy string: %s", s);
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ _cleanup_free_ char *t = NULL;
+ ImagePolicy **pp =
+ streq(name, "RootImagePolicy") ? &c->root_image_policy :
+ streq(name, "MountImagePolicy") ? &c->mount_image_policy :
+ &c->extension_image_policy;
+
+ r = image_policy_to_string(p, /* simplify= */ true, &t);
+ if (r < 0)
+ return r;
+
+ image_policy_free(*pp);
+ *pp = TAKE_PTR(p);
+
+ unit_write_settingf(
+ u, flags, name,
+ "%s=%s",
+ name,
+ t); /* no escaping necessary */
+ }
+
+ return 1;
}
return 0;
int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
int bus_property_get_exec_ex_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
+int bus_property_get_exec_preserve_mode(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
int bus_set_transient_exec_command(Unit *u, const char *name, ExecCommand **exec_command, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+int bus_set_transient_exec_preserve_mode(Unit *u, const char *name, ExecPreserveMode *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
#include "bus-common-errors.h"
#include "bus-get-properties.h"
#include "bus-log-control-api.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "data-fd-util.h"
#include "dbus-cgroup.h"
#include "dbus-execute.h"
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Path to init binary '%s' not absolute.", init);
- r = chase_symlinks_and_access(init, root, CHASE_PREFIX_ROOT, X_OK, NULL);
+ r = chase_and_access(init, root, CHASE_PREFIX_ROOT, X_OK, NULL);
if (r == -EACCES)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Init binary %s is not executable.", init);
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Manager *m = ASSERT_PTR(userdata);
UnitFileList *item;
- Hashmap *h;
+ _cleanup_(hashmap_freep) Hashmap *h = NULL;
int r;
assert(message);
if (r < 0)
return r;
- h = hashmap_new(&string_hash_ops);
+ h = hashmap_new(&unit_file_list_hash_ops_free);
if (!h)
return -ENOMEM;
r = unit_file_get_list(m->runtime_scope, NULL, h, states, patterns);
if (r < 0)
- goto fail;
+ return r;
r = sd_bus_message_open_container(reply, 'a', "(ss)");
if (r < 0)
- goto fail;
+ return r;
HASHMAP_FOREACH(item, h) {
r = sd_bus_message_append(reply, "(ss)", item->path, unit_file_state_to_string(item->state));
if (r < 0)
- goto fail;
+ return r;
}
- unit_file_list_free(h);
-
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
-
-fail:
- unit_file_list_free(h);
- return r;
}
static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) {
InstallChange *changes,
size_t n_changes) {
- int r;
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
for (size_t i = 0; i < n_changes; i++)
case -EEXIST:
if (changes[i].source)
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
- "File %s already exists and is a symlink to %s.",
- changes[i].path, changes[i].source);
- else
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
- "File %s already exists.",
- changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
+ "File %s already exists and is a symlink to %s.",
+ changes[i].path, changes[i].source);
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
+ "File %s already exists.",
+ changes[i].path);
case -ERFKILL:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED,
- "Unit file %s is masked.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED,
+ "Unit file %s is masked.", changes[i].path);
case -EADDRNOTAVAIL:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED,
- "Unit %s is transient or generated.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED,
+ "Unit %s is transient or generated.", changes[i].path);
case -ETXTBSY:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_BAD_PATH,
- "File %s is under the systemd unit hierarchy already.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_BAD_PATH,
+ "File %s is under the systemd unit hierarchy already.", changes[i].path);
case -EBADSLT:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Invalid specifier in %s.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Invalid specifier in %s.", changes[i].path);
case -EIDRM:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Destination unit %s is a non-template unit.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Destination unit %s is a non-template unit.", changes[i].path);
case -EUCLEAN:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "\"%s\" is not a valid unit name.",
- changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "\"%s\" is not a valid unit name.",
+ changes[i].path);
case -ELOOP:
- r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED,
- "Refusing to operate on alias name or linked unit file: %s",
- changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED,
+ "Refusing to operate on alias name or linked unit file: %s",
+ changes[i].path);
case -EXDEV:
if (changes[i].source)
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Cannot alias %s as %s.",
- changes[i].source, changes[i].path);
- else
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Invalid unit reference %s.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Cannot alias %s as %s.",
+ changes[i].source, changes[i].path);
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Invalid unit reference %s.", changes[i].path);
case -ENOENT:
- r = sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
- "Unit file %s does not exist.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
+ "Unit file %s does not exist.", changes[i].path);
case -EUNATCH:
- r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
- "Cannot resolve specifiers in %s.", changes[i].path);
- goto found;
+ return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+ "Cannot resolve specifiers in %s.", changes[i].path);
default:
assert(changes[i].type < 0); /* other errors */
- r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path);
- goto found;
+ return sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path);
}
- r = c < 0 ? c : -EINVAL;
-
- found:
- install_changes_free(changes, n_changes);
- return r;
+ return c < 0 ? c : -EINVAL;
}
static int reply_install_changes_and_free(
const char *name;
int runtime, r;
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = sd_bus_message_read(message, "sb", &name, &runtime);
if (r < 0)
return r;
r = unit_file_disable(m->runtime_scope,
UNIT_FILE_DRY_RUN | (runtime ? UNIT_FILE_RUNTIME : 0),
NULL, STRV_MAKE(name), &changes, &n_changes);
- if (r < 0) {
- log_error_errno(r, "Failed to get file links for %s: %m", name);
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get file links for %s: %m", name);
for (i = 0; i < n_changes; i++)
if (changes[i].type == INSTALL_CHANGE_UNLINK) {
r = sd_bus_message_append(reply, "s", changes[i].path);
if (r < 0)
- goto finish;
+ return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
- goto finish;
-
- r = sd_bus_send(NULL, reply, NULL);
+ return r;
-finish:
- install_changes_free(changes, n_changes);
- return r;
+ return sd_bus_send(NULL, reply, NULL);
}
static int method_get_job_waiting(sd_bus_message *message, void *userdata, sd_bus_error *error) {
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_exit_type, service_exit_type, ServiceExitType);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart);
-static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess);
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_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);
propagate_directory = strjoina("/run/systemd/propagate/", u->id);
if (is_image)
- r = mount_image_in_namespace(unit_pid,
- propagate_directory,
- "/run/systemd/incoming/",
- src, dest, read_only, make_file_or_directory, options);
+ r = mount_image_in_namespace(
+ unit_pid,
+ propagate_directory,
+ "/run/systemd/incoming/",
+ src, dest,
+ read_only,
+ make_file_or_directory,
+ options,
+ c->mount_image_policy ?: &image_policy_service);
else
- r = bind_mount_in_namespace(unit_pid,
- propagate_directory,
- "/run/systemd/incoming/",
- src, dest, read_only, make_file_or_directory);
+ r = bind_mount_in_namespace(
+ unit_pid,
+ propagate_directory,
+ "/run/systemd/incoming/",
+ src, dest,
+ read_only,
+ make_file_or_directory);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);
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);
+}
+
+#if __SIZEOF_SIZE_T__ == 8
+static int property_get_size_as_uint32(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ size_t *value = ASSERT_PTR(userdata);
+ uint32_t sz = *value >= UINT32_MAX ? UINT32_MAX : (uint32_t) *value;
+
+ /* Returns a size_t as a D-Bus "u" type, i.e. as 32bit value, even if size_t is 64bit. We'll saturate if it doesn't fit. */
+
+ return sd_bus_message_append_basic(reply, 'u', &sz);
+}
+#elif __SIZEOF_SIZE_T__ == 4
+#define property_get_size_as_uint32 ((sd_bus_property_get_t) NULL)
+#else
+#error "Unexpected size of size_t"
+#endif
+
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("ExitType", "s", property_get_exit_type, offsetof(Service, exit_type), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
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("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),
SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Service, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("NFileDescriptorStore", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store), 0),
+ SD_BUS_PROPERTY("NFileDescriptorStore", "u", property_get_size_as_uint32, offsetof(Service, n_fd_store), 0),
+ SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0),
SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
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),
if (streq(name, "RestartUSec"))
return bus_set_transient_usec(u, name, &s->restart_usec, message, flags, error);
+ if (streq(name, "RestartSteps"))
+ return bus_set_transient_unsigned(u, name, &s->restart_steps, message, flags, error);
+
+ if (streq(name, "RestartUSecMax"))
+ return bus_set_transient_usec(u, name, &s->restart_usec_max, message, flags, error);
+
if (streq(name, "TimeoutStartUSec")) {
r = bus_set_transient_usec(u, name, &s->timeout_start_usec, message, flags, error);
if (r >= 0 && !UNIT_WRITE_FLAGS_NOOP(flags))
if (streq(name, "FileDescriptorStoreMax"))
return bus_set_transient_unsigned(u, name, &s->n_fd_store_max, message, flags, error);
+ if (streq(name, "FileDescriptorStorePreserve"))
+ return bus_set_transient_exec_preserve_mode(u, name, &s->fd_store_preserve_mode, message, flags, error);
+
if (streq(name, "NotifyAccess"))
return bus_set_transient_notify_access(u, name, &s->notify_access, message, flags, error);
return bus_set_transient_exit_status(u, name, &s->success_status, message, flags, error);
ci = service_exec_command_from_string(name);
- ci = (ci >= 0) ? ci : service_exec_ex_command_from_string(name);
+ if (ci < 0)
+ ci = service_exec_ex_command_from_string(name);
if (ci >= 0)
return bus_set_transient_exec_command(u, name, &s->exec_command[ci], message, flags, error);
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);
return r;
}
+ if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) {
+ r = sd_bus_message_append(reply, "s", "fdstore");
+ if (r < 0)
+ return r;
+ }
+
return sd_bus_message_close_container(reply);
}
r = unit_get_unit_file_preset(u);
- return sd_bus_message_append(reply, "s",
- r < 0 ? NULL:
- r > 0 ? "enabled" : "disabled");
+ return sd_bus_message_append(reply, "s", preset_action_past_tense_to_string(r));
}
static int property_get_job(
return r;
for (;;) {
+ ExecCleanMask m;
const char *i;
r = sd_bus_message_read(message, "s", &i);
if (r == 0)
break;
- if (streq(i, "all"))
- mask |= EXEC_CLEAN_ALL;
- else {
- ExecDirectoryType t;
-
- t = exec_resource_type_from_string(i);
- if (t < 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
+ m = exec_clean_mask_from_string(i);
+ if (m < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
- mask |= 1U << t;
- }
+ mask |= m;
}
r = sd_bus_message_exit_container(message);
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) {
static void device_unset_sysfs(Device *d) {
Hashmap *devices;
- Device *first;
assert(d);
if (!d->sysfs)
return;
- /* Remove this unit from the chain of devices which share the
- * same sysfs path. */
+ /* Remove this unit from the chain of devices which share the same sysfs path. */
+
devices = UNIT(d)->manager->devices_by_sysfs;
- first = hashmap_get(devices, d->sysfs);
- LIST_REMOVE(same_sysfs, first, d);
- if (first)
- hashmap_remove_and_replace(devices, d->sysfs, first->sysfs, first);
+ if (d->same_sysfs_prev)
+ /* If this is not the first unit, then simply remove this unit. */
+ d->same_sysfs_prev->same_sysfs_next = d->same_sysfs_next;
+ else if (d->same_sysfs_next)
+ /* If this is the first unit, replace with the next unit. */
+ assert_se(hashmap_replace(devices, d->same_sysfs_next->sysfs, d->same_sysfs_next) >= 0);
else
+ /* Otherwise, remove the entry. */
hashmap_remove(devices, d->sysfs);
+ if (d->same_sysfs_next)
+ d->same_sysfs_next->same_sysfs_prev = d->same_sysfs_prev;
+
+ d->same_sysfs_prev = d->same_sysfs_next = NULL;
+
d->sysfs = mfree(d->sysfs);
}
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);
#include "cap-list.h"
#include "capability-util.h"
#include "cgroup-setup.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "chown-recursive.h"
#include "constants.h"
#include "cpu-set-util.h"
#include "namespace.h"
#include "parse-util.h"
#include "path-util.h"
+#include "proc-cmdline.h"
#include "process-util.h"
#include "psi-util.h"
#include "random-util.h"
return "/dev/console";
}
+static int exec_context_tty_size(const ExecContext *context, unsigned *ret_rows, unsigned *ret_cols) {
+ _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
+ unsigned rows, cols;
+ const char *tty;
+ int r;
+
+ assert(context);
+ assert(ret_rows);
+ assert(ret_cols);
+
+ rows = context->tty_rows;
+ cols = context->tty_cols;
+
+ tty = exec_context_tty_path(context);
+ if (!tty || (rows != UINT_MAX && cols != UINT_MAX)) {
+ *ret_rows = rows;
+ *ret_cols = cols;
+ return 0;
+ }
+
+ tty = skip_dev_prefix(tty);
+ if (!in_charset(tty, ALPHANUMERICAL)) {
+ log_debug("%s contains non-alphanumeric characters, ignoring", tty);
+ *ret_rows = rows;
+ *ret_cols = cols;
+ return 0;
+ }
+
+ rowskey = strjoin("systemd.tty.rows.", tty);
+ if (!rowskey)
+ return -ENOMEM;
+
+ colskey = strjoin("systemd.tty.columns.", tty);
+ if (!colskey)
+ return -ENOMEM;
+
+ r = proc_cmdline_get_key_many(/* flags = */ 0,
+ rowskey, &rowsvalue,
+ colskey, &colsvalue);
+ if (r < 0)
+ log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline, ignoring: %m", tty);
+
+ if (rows == UINT_MAX && rowsvalue) {
+ r = safe_atou(rowsvalue, &rows);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse %s=%s, ignoring: %m", rowskey, rowsvalue);
+ }
+
+ if (cols == UINT_MAX && colsvalue) {
+ r = safe_atou(colsvalue, &cols);
+ if (r < 0)
+ log_debug_errno(r, "Failed to parse %s=%s, ignoring: %m", colskey, colsvalue);
+ }
+
+ *ret_rows = rows;
+ *ret_cols = cols;
+
+ return 0;
+}
+
static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) {
const char *path;
(void) reset_terminal(path);
}
- if (p && p->stdin_fd >= 0)
- (void) terminal_set_size_fd(p->stdin_fd, path, context->tty_rows, context->tty_cols);
+ if (p && p->stdin_fd >= 0) {
+ unsigned rows = context->tty_rows, cols = context->tty_cols;
+
+ (void) exec_context_tty_size(context, &rows, &cols);
+ (void) terminal_set_size_fd(p->stdin_fd, path, rows, cols);
+ }
if (context->tty_vt_disallocate && path)
(void) vt_disallocate(path);
/* Try to make this the controlling tty, if it is a tty, and reset it */
if (isatty(STDIN_FILENO)) {
+ unsigned rows = context->tty_rows, cols = context->tty_cols;
+
+ (void) exec_context_tty_size(context, &rows, &cols);
(void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE);
(void) reset_terminal_fd(STDIN_FILENO, true);
- (void) terminal_set_size_fd(STDIN_FILENO, NULL, context->tty_rows, context->tty_cols);
+ (void) terminal_set_size_fd(STDIN_FILENO, NULL, rows, cols);
}
return STDIN_FILENO;
case EXEC_INPUT_TTY:
case EXEC_INPUT_TTY_FORCE:
case EXEC_INPUT_TTY_FAIL: {
+ unsigned rows, cols;
int fd;
fd = acquire_terminal(exec_context_tty_path(context),
if (fd < 0)
return fd;
- r = terminal_set_size_fd(fd, exec_context_tty_path(context), context->tty_rows, context->tty_cols);
+ r = exec_context_tty_size(context, &rows, &cols);
+ if (r < 0)
+ return r;
+
+ r = terminal_set_size_fd(fd, exec_context_tty_path(context), rows, cols);
if (r < 0)
return r;
int *ret_saved_stdout) {
_cleanup_close_ int fd = -EBADF, saved_stdin = -EBADF, saved_stdout = -EBADF;
+ unsigned rows, cols;
int r;
assert(ret_saved_stdin);
if (r < 0)
return r;
- r = terminal_set_size_fd(fd, vc, context->tty_rows, context->tty_cols);
+ r = exec_context_tty_size(context, &rows, &cols);
+ if (r < 0)
+ return r;
+
+ r = terminal_set_size_fd(fd, vc, rows, cols);
if (r < 0)
return r;
_cleanup_strv_free_ char **our_env = NULL;
size_t n_env = 0;
char *x;
+ int r;
assert(u);
assert(c);
}
if (exec_context_needs_term(c)) {
+ _cleanup_free_ char *cmdline = NULL;
const char *tty_path, *term = NULL;
tty_path = exec_context_tty_path(c);
if (path_equal_ptr(tty_path, "/dev/console") && getppid() == 1)
term = getenv("TERM");
+ else if (tty_path && in_charset(skip_dev_prefix(tty_path), ALPHANUMERICAL)) {
+ _cleanup_free_ char *key = NULL;
+
+ key = strjoin("systemd.tty.term.", skip_dev_prefix(tty_path));
+ if (!key)
+ return -ENOMEM;
+
+ r = proc_cmdline_get_key(key, 0, &cmdline);
+ if (r < 0)
+ log_debug_errno(r, "Failed to read %s from kernel cmdline, ignoring: %m", key);
+ else if (r > 0)
+ term = cmdline;
+ }
if (!term)
term = default_term_for_tty(tty_path);
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;
* since they all support the private/ symlink logic at least in some
* configurations, see above. */
- r = chase_symlinks(target, NULL, 0, &target_resolved, NULL);
+ r = chase(target, NULL, 0, &target_resolved, NULL);
if (r < 0)
goto fail;
}
/* /var/lib or friends may be symlinks. So, let's chase them also. */
- r = chase_symlinks(q, NULL, CHASE_NONEXISTENT, &q_resolved, NULL);
+ r = chase(q, NULL, CHASE_NONEXISTENT, &q_resolved, NULL);
if (r < 0)
goto fail;
char ***ret_empty_directories) {
_cleanup_strv_free_ char **empty_directories = NULL;
- BindMount *bind_mounts;
+ BindMount *bind_mounts = NULL;
size_t n, h = 0;
int r;
assert(ret_n_bind_mounts);
assert(ret_empty_directories);
+ CLEANUP_ARRAY(bind_mounts, h, bind_mount_free_many);
+
n = context->n_bind_mounts;
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
if (!params->prefix[t])
for (size_t i = 0; i < context->n_bind_mounts; i++) {
BindMount *item = context->bind_mounts + i;
- char *s, *d;
+ _cleanup_free_ char *s = NULL, *d = NULL;
s = strdup(item->source);
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
d = strdup(item->destination);
- if (!d) {
- free(s);
- r = -ENOMEM;
- goto finish;
- }
+ if (!d)
+ return -ENOMEM;
bind_mounts[h++] = (BindMount) {
- .source = s,
- .destination = d,
+ .source = TAKE_PTR(s),
+ .destination = TAKE_PTR(d),
.read_only = item->read_only,
.recursive = item->recursive,
.ignore_enoent = item->ignore_enoent,
* tmpfs that makes it accessible and is empty except for the submounts we do this for. */
private_root = path_join(params->prefix[t], "private");
- if (!private_root) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!private_root)
+ return -ENOMEM;
r = strv_consume(&empty_directories, private_root);
if (r < 0)
- goto finish;
+ return r;
}
for (size_t i = 0; i < context->directories[t].n_items; i++) {
- char *s, *d;
+ _cleanup_free_ char *s = NULL, *d = NULL;
/* When one of the parent directories is in the list, we cannot create the symlink
* for the child directory. See also the comments in setup_exec_directory(). */
s = path_join(params->prefix[t], "private", context->directories[t].items[i].path);
else
s = path_join(params->prefix[t], context->directories[t].items[i].path);
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
if (exec_directory_is_private(context, t) &&
exec_context_with_rootfs(context))
d = path_join(params->prefix[t], context->directories[t].items[i].path);
else
d = strdup(s);
- if (!d) {
- free(s);
- r = -ENOMEM;
- goto finish;
- }
+ if (!d)
+ return -ENOMEM;
bind_mounts[h++] = (BindMount) {
- .source = s,
- .destination = d,
+ .source = TAKE_PTR(s),
+ .destination = TAKE_PTR(d),
.read_only = false,
.nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */
.recursive = true,
assert(h == n);
- *ret_bind_mounts = bind_mounts;
+ *ret_bind_mounts = TAKE_PTR(bind_mounts);
*ret_n_bind_mounts = n;
*ret_empty_directories = TAKE_PTR(empty_directories);
return (int) n;
-
-finish:
- bind_mount_free_many(bind_mounts, h);
- return r;
}
/* ret_symlinks will contain a list of pairs src:dest that describes
assert(context);
+ CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many);
+
if (params->flags & EXEC_APPLY_CHROOT) {
root_image = context->root_image;
/* Symlinks for exec dirs are set up after other mounts, before they are made read-only. */
r = compile_symlinks(context, params, &symlinks);
if (r < 0)
- goto finalize;
+ return r;
/* We need to make the pressure path writable even if /sys/fs/cgroups is made read-only, as the
* service will need to write to it in order to start the notifications. */
if (context->protect_control_groups && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) {
read_write_paths_cleanup = strv_copy(context->read_write_paths);
- if (!read_write_paths_cleanup) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!read_write_paths_cleanup)
+ return -ENOMEM;
r = strv_extend(&read_write_paths_cleanup, memory_pressure_path);
if (r < 0)
- goto finalize;
+ return r;
read_write_paths = read_write_paths_cleanup;
} else
* 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) {
params->prefix[EXEC_DIRECTORY_RUNTIME] &&
FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
creds_path = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id);
- if (!creds_path) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!creds_path)
+ return -ENOMEM;
}
if (MANAGER_IS_SYSTEM(u->manager)) {
propagate_dir = path_join("/run/systemd/propagate/", u->id);
- if (!propagate_dir) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!propagate_dir)
+ return -ENOMEM;
incoming_dir = strdup("/run/systemd/incoming");
- if (!incoming_dir) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!incoming_dir)
+ return -ENOMEM;
extension_dir = strdup("/run/systemd/unit-extensions");
- if (!extension_dir) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (!extension_dir)
+ return -ENOMEM;
} else
- if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0) {
- r = -ENOMEM;
- goto finalize;
- }
+ if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0)
+ return -ENOMEM;
- r = setup_namespace(root_dir, root_image, context->root_image_options,
- &ns_info, read_write_paths,
- needs_sandboxing ? context->read_only_paths : NULL,
- needs_sandboxing ? context->inaccessible_paths : NULL,
- needs_sandboxing ? context->exec_paths : NULL,
- needs_sandboxing ? context->no_exec_paths : NULL,
- empty_directories,
- symlinks,
- bind_mounts,
- n_bind_mounts,
- context->temporary_filesystems,
- context->n_temporary_filesystems,
- context->mount_images,
- context->n_mount_images,
- tmp_dir,
- var_tmp_dir,
- creds_path,
- context->log_namespace,
- context->mount_propagation_flag,
- context->root_hash, context->root_hash_size, context->root_hash_path,
- context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
- context->root_verity,
- context->extension_images,
- context->n_extension_images,
- context->extension_directories,
- propagate_dir,
- incoming_dir,
- extension_dir,
- root_dir || root_image ? params->notify_socket : NULL,
- error_path);
+ r = setup_namespace(
+ root_dir,
+ root_image,
+ context->root_image_options,
+ context->root_image_policy ?: &image_policy_service,
+ &ns_info,
+ read_write_paths,
+ needs_sandboxing ? context->read_only_paths : NULL,
+ needs_sandboxing ? context->inaccessible_paths : NULL,
+ needs_sandboxing ? context->exec_paths : NULL,
+ needs_sandboxing ? context->no_exec_paths : NULL,
+ empty_directories,
+ symlinks,
+ bind_mounts,
+ n_bind_mounts,
+ context->temporary_filesystems,
+ context->n_temporary_filesystems,
+ context->mount_images,
+ context->n_mount_images,
+ context->mount_image_policy ?: &image_policy_service,
+ tmp_dir,
+ var_tmp_dir,
+ creds_path,
+ context->log_namespace,
+ context->mount_propagation_flag,
+ context->root_hash, context->root_hash_size, context->root_hash_path,
+ context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
+ context->root_verity,
+ context->extension_images,
+ context->n_extension_images,
+ context->extension_image_policy ?: &image_policy_sysext,
+ context->extension_directories,
+ propagate_dir,
+ incoming_dir,
+ extension_dir,
+ root_dir || root_image ? params->notify_socket : NULL,
+ error_path);
/* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports
* that with a special, recognizable error ENOANO. In this case, silently proceed, but only if exclusively
context,
root_dir, root_image,
bind_mounts,
- n_bind_mounts)) {
- log_unit_debug(u, "Failed to set up namespace, and refusing to continue since the selected namespacing options alter mount environment non-trivially.\n"
- "Bind mounts: %zu, temporary filesystems: %zu, root directory: %s, root image: %s, dynamic user: %s",
- n_bind_mounts, context->n_temporary_filesystems, yes_no(root_dir), yes_no(root_image), yes_no(context->dynamic_user));
-
- r = -EOPNOTSUPP;
- } else {
- log_unit_debug(u, "Failed to set up namespace, assuming containerized execution and ignoring.");
- r = 0;
- }
+ n_bind_mounts))
+ return log_unit_debug_errno(u,
+ SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Failed to set up namespace, and refusing to continue since "
+ "the selected namespacing options alter mount environment non-trivially.\n"
+ "Bind mounts: %zu, temporary filesystems: %zu, root directory: %s, root image: %s, dynamic user: %s",
+ n_bind_mounts,
+ context->n_temporary_filesystems,
+ yes_no(root_dir),
+ yes_no(root_image),
+ yes_no(context->dynamic_user));
+
+ log_unit_debug(u, "Failed to set up namespace, assuming containerized execution and ignoring.");
+ return 0;
}
-finalize:
- bind_mount_free_many(bind_mounts, n_bind_mounts);
return r;
}
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 bool exec_context_need_unprivileged_private_users(const ExecContext *context, const Manager *manager) {
+ assert(context);
+ assert(manager);
+
+ /* These options require PrivateUsers= when used in user units, as we need to be in a user namespace
+ * to have permission to enable them when not running as root. If we have effective CAP_SYS_ADMIN
+ * (system manager) then we have privileges and don't need this. */
+ if (MANAGER_IS_SYSTEM(manager))
+ return false;
+
+ return context->private_users ||
+ context->private_tmp ||
+ context->private_devices ||
+ context->private_network ||
+ context->network_namespace_path ||
+ context->private_ipc ||
+ context->ipc_namespace_path ||
+ context->private_mounts ||
+ context->mount_apivfs ||
+ context->n_bind_mounts > 0 ||
+ context->n_temporary_filesystems > 0 ||
+ context->root_directory ||
+ !strv_isempty(context->extension_directories) ||
+ context->protect_system != PROTECT_SYSTEM_NO ||
+ context->protect_home != PROTECT_HOME_NO ||
+ context->protect_kernel_tunables ||
+ context->protect_kernel_modules ||
+ context->protect_kernel_logs ||
+ context->protect_control_groups ||
+ context->protect_clock ||
+ context->protect_hostname ||
+ !strv_isempty(context->read_write_paths) ||
+ !strv_isempty(context->read_only_paths) ||
+ !strv_isempty(context->inaccessible_paths) ||
+ !strv_isempty(context->exec_paths) ||
+ !strv_isempty(context->no_exec_paths);
+}
+
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],
log_forget_fds();
log_set_open_when_needed(true);
+ log_settle_target();
/* In case anything used libc syslog(), close this here, too */
closelog();
}
#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 (needs_sandboxing && context->private_users && have_effective_cap(CAP_SYS_ADMIN) <= 0) {
+ if (needs_sandboxing && exec_context_need_unprivileged_private_users(context, unit->manager)) {
/* If we're unprivileged, set up the user namespace first to enable use of the other namespaces.
* Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to
* set up the all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */
- userns_set_up = true;
r = setup_private_users(saved_uid, saved_gid, uid, gid);
- if (r < 0) {
+ /* If it was requested explicitly and we can't set it up, fail early. Otherwise, continue and let
+ * the actual requested operations fail (or silently continue). */
+ if (r < 0 && context->private_users) {
*exit_status = EXIT_USER;
return log_unit_error_errno(unit, r, "Failed to set up user namespacing for unprivileged user: %m");
}
+ if (r < 0)
+ log_unit_info_errno(unit, r, "Failed to set up user namespacing for unprivileged user, ignoring: %m");
+ else
+ userns_set_up = true;
}
- 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);
assert(params);
assert(params->fds || (params->n_socket_fds + params->n_storage_fds <= 0));
+ LOG_CONTEXT_PUSH_UNIT(unit);
+
if (context->std_input == EXEC_INPUT_SOCKET ||
context->std_output == EXEC_OUTPUT_SOCKET ||
context->std_error == EXEC_OUTPUT_SOCKET) {
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,
c->load_credentials = hashmap_free(c->load_credentials);
c->set_credentials = hashmap_free(c->set_credentials);
+
+ c->root_image_policy = image_policy_free(c->root_image_policy);
+ c->mount_image_policy = image_policy_free(c->mount_image_policy);
+ c->extension_image_policy = image_policy_free(c->extension_image_policy);
}
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
return 0;
}
+bool exec_context_has_encrypted_credentials(ExecContext *c) {
+ ExecLoadCredential *load_cred;
+ ExecSetCredential *set_cred;
+
+ assert(c);
+
+ HASHMAP_FOREACH(load_cred, c->load_credentials)
+ if (load_cred->encrypted)
+ return true;
+
+ HASHMAP_FOREACH(set_cred, c->set_credentials)
+ if (set_cred->encrypted)
+ return true;
+
+ return false;
+}
+
void exec_status_start(ExecStatus *s, pid_t pid) {
assert(s);
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) {
if (!p)
return;
}
}
+ExecCleanMask exec_clean_mask_from_string(const char *s) {
+ ExecDirectoryType t;
+
+ assert(s);
+
+ if (streq(s, "all"))
+ return EXEC_CLEAN_ALL;
+ if (streq(s, "fdstore"))
+ return EXEC_CLEAN_FDSTORE;
+
+ t = exec_resource_type_from_string(s);
+ if (t < 0)
+ return (ExecCleanMask) t;
+
+ return 1U << t;
+}
+
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
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,
EXEC_CLEAN_CACHE = 1U << EXEC_DIRECTORY_CACHE,
EXEC_CLEAN_LOGS = 1U << EXEC_DIRECTORY_LOGS,
EXEC_CLEAN_CONFIGURATION = 1U << EXEC_DIRECTORY_CONFIGURATION,
+ EXEC_CLEAN_FDSTORE = 1U << _EXEC_DIRECTORY_TYPE_MAX,
EXEC_CLEAN_NONE = 0,
- EXEC_CLEAN_ALL = (1U << _EXEC_DIRECTORY_TYPE_MAX) - 1,
+ EXEC_CLEAN_ALL = (1U << (_EXEC_DIRECTORY_TYPE_MAX+1)) - 1,
_EXEC_CLEAN_MASK_INVALID = -EINVAL,
} ExecCleanMask;
Hashmap *set_credentials; /* output id → ExecSetCredential */
Hashmap *load_credentials; /* output id → ExecLoadCredential */
+
+ ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy;
};
static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
const ExecContext *context,
const ExecParameters *exec_params,
ExecRuntime *runtime,
- DynamicCreds *dynamic_creds,
const CGroupContext *cgroup_context,
pid_t *ret);
bool exec_context_may_touch_console(const ExecContext *c);
bool exec_context_maintains_privileges(const ExecContext *c);
+bool exec_context_has_encrypted_credentials(ExecContext *c);
int exec_context_get_effective_ioprio(const ExecContext *c);
bool exec_context_get_effective_mount_apivfs(const ExecContext *c);
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);
int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink);
void exec_directory_sort(ExecDirectory *d);
+ExecCleanMask exec_clean_mask_from_string(const char *s);
+
extern const struct hash_ops exec_set_credential_hash_ops;
extern const struct hash_ops exec_load_credential_hash_ops;
job_add_to_gc_queue(other->job);
}
+ /* Ensure that when an upheld/unneeded/bound unit activation job fails we requeue it, if it still
+ * necessary. If there are no state changes in the triggerer, it would not be retried otherwise. */
+ unit_submit_to_start_when_upheld_queue(u);
+ unit_submit_to_stop_when_bound_queue(u);
+ unit_submit_to_stop_when_unneeded_queue(u);
+
manager_check_finished(u->manager);
return 0;
#include "macro.h"
#include "recurse-dir.h"
#include "string-util.h"
+#include "strv.h"
#include "virt.h"
#if HAVE_KMOD
REENABLE_WARNING;
}
-static int has_virtio_rng_recurse_dir_cb(
+static int match_modalias_recurse_dir_cb(
RecurseDirEvent event,
const char *path,
int dir_fd,
void *userdata) {
_cleanup_free_ char *alias = NULL;
+ char **modaliases = ASSERT_PTR(userdata);
int r;
if (event != RECURSE_DIR_ENTRY)
return RECURSE_DIR_LEAVE_DIRECTORY;
}
- if (startswith(alias, "pci:v00001AF4d00001005"))
- return 1;
-
- if (startswith(alias, "pci:v00001AF4d00001044"))
+ if (startswith_strv(alias, modaliases))
return 1;
return RECURSE_DIR_LEAVE_DIRECTORY;
static bool has_virtio_rng(void) {
int r;
+ /* Directory traversal might be slow, hence let's do a cheap check first if it's even worth it */
+ if (detect_vm() == VIRTUALIZATION_NONE)
+ return false;
+
r = recurse_dir_at(
AT_FDCWD,
"/sys/devices/pci0000:00",
/* statx_mask= */ 0,
/* n_depth_max= */ 2,
RECURSE_DIR_ENSURE_TYPE,
- has_virtio_rng_recurse_dir_cb,
- NULL);
+ match_modalias_recurse_dir_cb,
+ STRV_MAKE("pci:v00001AF4d00001005", "pci:v00001AF4d00001044"));
if (r < 0)
log_debug_errno(r, "Failed to determine whether host has virtio-rng device, ignoring: %m");
return r > 0;
}
+static bool has_virtio_console(void) {
+ int r;
+
+ /* Directory traversal might be slow, hence let's do a cheap check first if it's even worth it */
+ if (detect_vm() == VIRTUALIZATION_NONE)
+ return false;
+
+ r = recurse_dir_at(
+ AT_FDCWD,
+ "/sys/devices/pci0000:00",
+ /* statx_mask= */ 0,
+ /* n_depth_max= */ 3,
+ RECURSE_DIR_ENSURE_TYPE,
+ match_modalias_recurse_dir_cb,
+ STRV_MAKE("virtio:d00000003v", "virtio:d0000000Bv"));
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine whether host has virtio-console device, ignoring: %m");
+
+ return r > 0;
+}
+
static bool in_qemu(void) {
return IN_SET(detect_vm(), VIRTUALIZATION_KVM, VIRTUALIZATION_QEMU);
}
} kmod_table[] = {
/* This one we need to load explicitly, since auto-loading on use doesn't work
* before udev created the ghost device nodes, and we need it earlier than that. */
- { "autofs4", "/sys/class/misc/autofs", true, false, NULL },
+ { "autofs4", "/sys/class/misc/autofs", true, false, NULL },
/* This one we need to load explicitly, since auto-loading of IPv6 is not done when
* we try to configure ::1 on the loopback device. */
- { "ipv6", "/sys/module/ipv6", false, true, NULL },
+ { "ipv6", "/sys/module/ipv6", false, true, NULL },
/* This should never be a module */
- { "unix", "/proc/net/unix", true, true, NULL },
+ { "unix", "/proc/net/unix", true, true, NULL },
#if HAVE_LIBIPTC
/* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */
- { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL },
+ { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL },
#endif
/* virtio_rng would be loaded by udev later, but real entropy might be needed very early */
- { "virtio_rng", NULL, false, false, has_virtio_rng },
+ { "virtio_rng", NULL, false, false, has_virtio_rng },
+
+ /* we want early logging to hvc consoles if possible, and make sure systemd-getty-generator
+ * can rely on all consoles being probed already.*/
+ { "virtio_console", NULL, false, false, has_virtio_console },
/* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */
- { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu },
+ { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu },
/* dmi-sysfs is needed to import credentials from it super early */
- { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL },
+ { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL },
#if HAVE_TPM2
/* Make sure the tpm subsystem is available which ConditionSecurity=tpm2 depends on. */
- { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 },
+ { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 },
#endif
};
_cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
{{type}}.RootDirectory, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_directory)
{{type}}.RootImage, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_image)
{{type}}.RootImageOptions, config_parse_root_image_options, 0, offsetof({{type}}, exec_context)
+{{type}}.RootImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.root_image_policy)
{{type}}.RootHash, config_parse_exec_root_hash, 0, offsetof({{type}}, exec_context)
{{type}}.RootHashSignature, config_parse_exec_root_hash_sig, 0, offsetof({{type}}, exec_context)
{{type}}.RootVerity, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_verity)
{{type}}.ExtensionDirectories, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.extension_directories)
{{type}}.ExtensionImages, config_parse_extension_images, 0, offsetof({{type}}, exec_context)
+{{type}}.ExtensionImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.extension_image_policy)
{{type}}.MountImages, config_parse_mount_images, 0, offsetof({{type}}, exec_context)
+{{type}}.MountImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.mount_image_policy)
{{type}}.User, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.user)
{{type}}.Group, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.group)
{{type}}.SupplementaryGroups, config_parse_user_group_strv_compat, 0, offsetof({{type}}, exec_context.supplementary_groups)
{{type}}.MountFlags, config_parse_exec_mount_propagation_flag, 0, offsetof({{type}}, exec_context.mount_propagation_flag)
{{type}}.MountAPIVFS, config_parse_exec_mount_apivfs, 0, offsetof({{type}}, exec_context)
{{type}}.Personality, config_parse_personality, 0, offsetof({{type}}, exec_context.personality)
-{{type}}.RuntimeDirectoryPreserve, config_parse_runtime_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
+{{type}}.RuntimeDirectoryPreserve, config_parse_exec_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
{{type}}.RuntimeDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode)
{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME])
{{type}}.StateDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode)
#include <stddef.h>
#include "all-units.h"
#include "conf-parser.h"
+#include "image-policy.h"
#include "in-addr-prefix-util.h"
#include "load-fragment.h"
%}
Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command)
Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command)
Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec)
+Service.RestartSteps, config_parse_unsigned, 0, offsetof(Service, restart_steps)
+Service.RestartSecMax, config_parse_sec, 0, offsetof(Service, restart_usec_max)
Service.TimeoutSec, config_parse_service_timeout, 0, 0
Service.TimeoutStartSec, config_parse_service_timeout, 0, 0
Service.TimeoutStopSec, config_parse_sec_fix_0, 0, offsetof(Service, timeout_stop_usec)
Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking)
Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name)
Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max)
+Service.FileDescriptorStorePreserve, config_parse_exec_preserve_mode, 0, offsetof(Service, fd_store_preserve_mode)
Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access)
Service.Sockets, config_parse_service_sockets, 0, 0
Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0
DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome, "Failed to parse protect home value");
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem, "Failed to parse protect system value");
-DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse runtime directory preserve mode");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse resource preserve mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type");
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_exit_type, service_exit_type, ServiceExitType, "Failed to parse service exit type");
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier");
}
c->coredump_filter |= f;
- c->oom_score_adjust_set = true;
+ c->coredump_filter_set = true;
return 0;
}
CONFIG_PARSER_PROTOTYPE(config_parse_exec_apparmor_profile);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
-CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode);
+CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
+#include "getopt-defs.h"
#include "hexdecoct.h"
#include "hostname-setup.h"
#include "ima-setup.h"
static int parse_argv(int argc, char *argv[]) {
enum {
- ARG_LOG_LEVEL = 0x100,
- ARG_LOG_TARGET,
- ARG_LOG_COLOR,
- ARG_LOG_LOCATION,
- ARG_LOG_TIME,
- ARG_UNIT,
- ARG_SYSTEM,
- ARG_USER,
- ARG_TEST,
- ARG_NO_PAGER,
- ARG_VERSION,
- ARG_DUMP_CONFIGURATION_ITEMS,
- ARG_DUMP_BUS_PROPERTIES,
- ARG_BUS_INTROSPECT,
- ARG_DUMP_CORE,
- ARG_CRASH_CHVT,
- ARG_CRASH_SHELL,
- ARG_CRASH_REBOOT,
- ARG_CONFIRM_SPAWN,
- ARG_SHOW_STATUS,
- ARG_DESERIALIZE,
- ARG_SWITCHED_ROOT,
- ARG_DEFAULT_STD_OUTPUT,
- ARG_DEFAULT_STD_ERROR,
- ARG_MACHINE_ID,
- ARG_SERVICE_WATCHDOGS,
+ COMMON_GETOPT_ARGS,
+ SYSTEMD_GETOPT_ARGS,
};
static const struct option options[] = {
- { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
- { "log-target", required_argument, NULL, ARG_LOG_TARGET },
- { "log-color", optional_argument, NULL, ARG_LOG_COLOR },
- { "log-location", optional_argument, NULL, ARG_LOG_LOCATION },
- { "log-time", optional_argument, NULL, ARG_LOG_TIME },
- { "unit", required_argument, NULL, ARG_UNIT },
- { "system", no_argument, NULL, ARG_SYSTEM },
- { "user", no_argument, NULL, ARG_USER },
- { "test", no_argument, NULL, ARG_TEST },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS },
- { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES },
- { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT },
- { "dump-core", optional_argument, NULL, ARG_DUMP_CORE },
- { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT },
- { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL },
- { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT },
- { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN },
- { "show-status", optional_argument, NULL, ARG_SHOW_STATUS },
- { "deserialize", required_argument, NULL, ARG_DESERIALIZE },
- { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT },
- { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, },
- { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, },
- { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
- { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS },
+ COMMON_GETOPT_OPTIONS,
+ SYSTEMD_GETOPT_OPTIONS,
{}
};
if (getpid_cached() == 1)
opterr = 0;
- while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL)) >= 0)
switch (c) {
}
static int become_shutdown(int objective, int retval) {
-
static const char* const table[_MANAGER_OBJECTIVE_MAX] = {
[MANAGER_EXIT] = "exit",
[MANAGER_REBOOT] = "reboot",
[MANAGER_KEXEC] = "kexec",
};
- char log_level[DECIMAL_STR_MAX(int) + 1],
- exit_code[DECIMAL_STR_MAX(uint8_t) + 1],
- timeout[DECIMAL_STR_MAX(usec_t) + 1];
-
- const char* command_line[13] = {
- SYSTEMD_SHUTDOWN_BINARY_PATH,
- table[objective],
- "--timeout", timeout,
- "--log-level", log_level,
- "--log-target",
- };
+ char log_level[STRLEN("--log-level=") + DECIMAL_STR_MAX(int)],
+ timeout[STRLEN("--timeout=") + DECIMAL_STR_MAX(usec_t) + STRLEN("us")],
+ exit_code[STRLEN("--exit-code=") + DECIMAL_STR_MAX(uint8_t)];
_cleanup_strv_free_ char **env_block = NULL;
usec_t watchdog_timer = 0;
- size_t pos = 7;
int r;
assert(objective >= 0 && objective < _MANAGER_OBJECTIVE_MAX);
assert(table[objective]);
- assert(!command_line[pos]);
- env_block = strv_copy(environ);
- xsprintf(log_level, "%d", log_get_max_level());
- xsprintf(timeout, "%" PRI_USEC "us", arg_default_timeout_stop_usec);
+ xsprintf(log_level, "--log-level=%d", log_get_max_level());
+ xsprintf(timeout, "--timeout=%" PRI_USEC "us", arg_default_timeout_stop_usec);
+
+ const char* command_line[10] = {
+ SYSTEMD_SHUTDOWN_BINARY_PATH,
+ table[objective],
+ log_level,
+ timeout,
+ /* Note that the last position is a terminator and must contain NULL. */
+ };
+ size_t pos = 4;
+
+ assert(command_line[pos-1]);
+ assert(!command_line[pos]);
switch (log_get_target()) {
case LOG_TARGET_KMSG:
case LOG_TARGET_JOURNAL_OR_KMSG:
case LOG_TARGET_SYSLOG_OR_KMSG:
- command_line[pos++] = "kmsg";
+ command_line[pos++] = "--log-target=kmsg";
break;
case LOG_TARGET_NULL:
- command_line[pos++] = "null";
+ command_line[pos++] = "--log-target=null";
break;
case LOG_TARGET_CONSOLE:
default:
- command_line[pos++] = "console";
+ command_line[pos++] = "--log-target=console";
break;
};
command_line[pos++] = "--log-time";
if (objective == MANAGER_EXIT) {
- command_line[pos++] = "--exit-code";
+ xsprintf(exit_code, "--exit-code=%d", retval);
command_line[pos++] = exit_code;
- xsprintf(exit_code, "%d", retval);
}
assert(pos < ELEMENTSOF(command_line));
+ /* The watchdog: */
+
if (objective == MANAGER_REBOOT)
watchdog_timer = arg_reboot_watchdog;
else if (objective == MANAGER_KEXEC)
r = watchdog_setup(watchdog_timer);
watchdog_close(r < 0);
+ /* The environment block: */
+
+ env_block = strv_copy(environ);
+
/* Tell the binary how often to ping, ignore failure */
(void) strv_extendf(&env_block, "WATCHDOG_USEC="USEC_FMT, watchdog_timer);
}
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) {
log_error_errno(r, "Failed to switch root, trying to continue: %m");
}
- args_size = argc + 6;
+ args_size = argc + 5;
args = newa(const char*, args_size);
if (!switch_root_init) {
- char sfd[DECIMAL_STR_MAX(int)];
+ char sfd[STRLEN("--deserialize=") + DECIMAL_STR_MAX(int)];
/* First try to spawn ourselves with the right path, and with full serialization. We do this
* only if the user didn't specify an explicit init to spawn. */
assert(arg_serialization);
assert(fds);
- xsprintf(sfd, "%i", fileno(arg_serialization));
+ xsprintf(sfd, "--deserialize=%i", fileno(arg_serialization));
i = 1; /* Leave args[0] empty for now. */
filter_args(args, &i, argv, argc);
if (switch_root_dir)
args[i++] = "--switched-root";
args[i++] = runtime_scope_cmdline_option_to_string(arg_runtime_scope);
- args[i++] = "--deserialize";
args[i++] = sfd;
args[i++] = NULL;
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 */
error_message = "Failed to mount early API filesystems";
goto finish;
}
+ }
+
+ /* We might have just mounted /proc, so let's try to parse the kernel
+ * command line log arguments immediately. */
+ log_parse_environment();
- /* Let's open the log backend a second time, in case the first time didn't
- * work. Quite possibly we have mounted /dev just now, so /dev/kmsg became
- * available, and it previously wasn't. */
- log_open();
+ /* Let's open the log backend a second time, in case the first time didn't
+ * work. Quite possibly we have mounted /dev just now, so /dev/kmsg became
+ * available, and it previously wasn't. */
+ log_open();
+ if (!skip_setup) {
disable_printk_ratelimit();
r = initialize_security(
__lsan_do_leak_check();
#endif
+ if (r < 0)
+ (void) sd_notifyf(0, "ERRNO=%i", -r);
+
/* Try to invoke the shutdown binary unless we already failed.
* If we failed above, we want to freeze after finishing cleanup. */
if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM &&
error_message = "Failed to execute shutdown binary";
}
+ /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with
+ * a mechanism to pick up systemd's exit status in the VM. */
+ (void) sd_notifyf(0, "EXIT_STATUS=%i", retval);
+
watchdog_free_device();
arg_watchdog_device = mfree(arg_watchdog_device);
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)
return n;
}
+static unsigned manager_dispatch_release_resources_queue(Manager *m) {
+ unsigned n = 0;
+ Unit *u;
+
+ assert(m);
+
+ while ((u = m->release_resources_queue)) {
+ assert(u->in_release_resources_queue);
+
+ LIST_REMOVE(release_resources_queue, m->release_resources_queue, u);
+ u->in_release_resources_queue = false;
+
+ n++;
+
+ unit_release_resources(u);
+ }
+
+ return n;
+}
+
enum {
GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */
GC_OFFSET_UNSURE, /* No clue */
return n;
}
+static int manager_ratelimit_requeue(sd_event_source *s, uint64_t usec, void *userdata) {
+ Unit *u = userdata;
+
+ assert(u);
+ assert(s == u->auto_start_stop_event_source);
+
+ u->auto_start_stop_event_source = sd_event_source_unref(u->auto_start_stop_event_source);
+
+ /* Re-queue to all queues, if the rate limit hit we might have been throttled on any of them. */
+ unit_submit_to_stop_when_unneeded_queue(u);
+ unit_submit_to_start_when_upheld_queue(u);
+ unit_submit_to_stop_when_bound_queue(u);
+
+ return 0;
+}
+
+static int manager_ratelimit_check_and_queue(Unit *u) {
+ int r;
+
+ assert(u);
+
+ if (ratelimit_below(&u->auto_start_stop_ratelimit))
+ return 1;
+
+ /* Already queued, no need to requeue */
+ if (u->auto_start_stop_event_source)
+ return 0;
+
+ r = sd_event_add_time(
+ u->manager->event,
+ &u->auto_start_stop_event_source,
+ CLOCK_MONOTONIC,
+ ratelimit_end(&u->auto_start_stop_ratelimit),
+ 0,
+ manager_ratelimit_requeue,
+ u);
+ if (r < 0)
+ return log_unit_error_errno(u, r, "Failed to queue timer on event loop: %m");
+
+ return 0;
+}
+
static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
unsigned n = 0;
Unit *u;
/* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
* service being unnecessary after a while. */
- if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
- log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
+ r = manager_ratelimit_check_and_queue(u);
+ if (r <= 0) {
+ log_unit_warning(u,
+ "Unit not needed anymore, but not stopping since we tried this too often recently.%s",
+ r == 0 ? " Will retry later." : "");
continue;
}
/* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
* service being unnecessary after a while. */
- if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
- log_unit_warning(u, "Unit needs to be started because active unit %s upholds it, but not starting since we tried this too often recently.", culprit->id);
+ r = manager_ratelimit_check_and_queue(u);
+ if (r <= 0) {
+ log_unit_warning(u,
+ "Unit needs to be started because active unit %s upholds it, but not starting since we tried this too often recently.%s",
+ culprit->id,
+ r == 0 ? " Will retry later." : "");
continue;
}
/* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
* service being unnecessary after a while. */
- if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
- log_unit_warning(u, "Unit needs to be stopped because it is bound to inactive unit %s it, but not stopping since we tried this too often recently.", culprit->id);
+ r = manager_ratelimit_check_and_queue(u);
+ if (r <= 0) {
+ log_unit_warning(u,
+ "Unit needs to be stopped because it is bound to inactive unit %s it, but not stopping since we tried this too often recently.%s",
+ culprit->id,
+ r == 0 ? " Will retry later." : "");
continue;
}
assert(!m->stop_when_unneeded_queue);
assert(!m->start_when_upheld_queue);
assert(!m->stop_when_bound_queue);
+ assert(!m->release_resources_queue);
assert(hashmap_isempty(m->jobs));
assert(hashmap_isempty(m->units));
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)
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
assert(!fd_array);
- fd_array = (int*) CMSG_DATA(cmsg);
+ fd_array = CMSG_TYPED_DATA(cmsg, int);
n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
} else if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
assert(!ucred);
- ucred = (struct ucred*) CMSG_DATA(cmsg);
+ ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
}
}
if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
continue;
+ if (manager_dispatch_release_resources_queue(m) > 0)
+ continue;
+
if (manager_dispatch_dbus_queue(m) > 0)
continue;
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) {
/* Units that have BindsTo= another unit, and might need to be shutdown because the bound unit is not active. */
LIST_HEAD(Unit, stop_when_bound_queue);
+ /* Units that have resources open, and where it might be good to check if they can be released now */
+ LIST_HEAD(Unit, release_resources_queue);
+
sd_event *event;
/* This maps PIDs we care about to units that are interested in. We allow multiple units to be interested in
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;
)
endif
-subdir('bpf')
-
subdir('bpf/socket_bind')
-if conf.get('BPF_FRAMEWORK') == 1
- libcore_sources += [socket_bind_skel_h]
- subdir('bpf/restrict_fs')
- libcore_sources += [restrict_fs_skel_h]
-endif
-
+subdir('bpf/restrict_fs')
subdir('bpf/restrict_ifaces')
+
if conf.get('BPF_FRAMEWORK') == 1
- libcore_sources += [restrict_ifaces_skel_h]
+ libcore_sources += [
+ socket_bind_skel_h,
+ restrict_fs_skel_h,
+ restrict_ifaces_skel_h]
endif
load_fragment_gperf_gperf = custom_target(
libblkid,
libdl,
libkmod,
+ libm,
libmount,
libpam,
librt,
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 "alloc-util.h"
#include "base-filesystem.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dev-setup.h"
#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"
return 0;
}
-static int mount_image(const MountEntry *m, const char *root_directory) {
+static int mount_image(
+ const MountEntry *m,
+ const char *root_directory,
+ const ImagePolicy *image_policy) {
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
*host_os_release_sysext_level = NULL;
}
r = verity_dissect_and_mount(
- /* src_fd= */ -1, mount_entry_source(m), mount_entry_path(m), m->image_options,
- host_os_release_id, host_os_release_version_id, host_os_release_sysext_level, NULL);
+ /* src_fd= */ -1,
+ mount_entry_source(m),
+ mount_entry_path(m),
+ m->image_options,
+ image_policy,
+ host_os_release_id,
+ host_os_release_version_id,
+ host_os_release_sysext_level,
+ NULL);
if (r == -ENOENT && m->ignore)
return 0;
if (r == -ESTALE && host_os_release_id)
* a time by specifying CHASE_STEP. This function returns 0 if we resolved one step, and > 0 if we reached the
* end and already have a fully normalized name. */
- r = chase_symlinks(mount_entry_path(m), root_directory, CHASE_STEP|CHASE_NONEXISTENT, &target, NULL);
+ r = chase(mount_entry_path(m), root_directory, CHASE_STEP|CHASE_NONEXISTENT, &target, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to chase symlinks '%s': %m", mount_entry_path(m));
if (r > 0) /* Reached the end, nothing more to resolve */
return 1;
- if (m->n_followed >= CHASE_SYMLINKS_MAX) /* put a boundary on things */
+ if (m->n_followed >= CHASE_MAX) /* put a boundary on things */
return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
"Symlink loop on '%s'.",
mount_entry_path(m));
static int apply_one_mount(
const char *root_directory,
MountEntry *m,
+ const ImagePolicy *mount_image_policy,
+ const ImagePolicy *extension_image_policy,
const NamespaceInfo *ns_info) {
_cleanup_free_ char *inaccessible = NULL;
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)
/* Since mount() will always follow symlinks we chase the symlinks on our own first. Note
* that bind mount source paths are always relative to the host root, hence we pass NULL as
- * root directory to chase_symlinks() here. */
+ * root directory to chase() here. */
- r = chase_symlinks(mount_entry_source(m), NULL, CHASE_TRAIL_SLASH, &chased, NULL);
+ r = chase(mount_entry_source(m), NULL, CHASE_TRAIL_SLASH, &chased, NULL);
if (r == -ENOENT && m->ignore) {
log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_source(m));
return 0;
return mount_mqueuefs(m);
case MOUNT_IMAGES:
- return mount_image(m, NULL);
+ return mount_image(m, NULL, mount_image_policy);
case EXTENSION_IMAGES:
- return mount_image(m, root_directory);
+ return mount_image(m, root_directory, extension_image_policy);
case OVERLAY_MOUNT:
return mount_overlay(m);
static int apply_mounts(
const char *root,
+ const ImagePolicy *mount_image_policy,
+ const ImagePolicy *extension_image_policy,
const NamespaceInfo *ns_info,
MountEntry *mounts,
size_t *n_mounts,
break;
}
- r = apply_one_mount(root, m, ns_info);
+ r = apply_one_mount(root, m, mount_image_policy, extension_image_policy, ns_info);
if (r < 0) {
if (error_path && mount_entry_path(m))
*error_path = strdup(mount_entry_path(m));
int setup_namespace(
const char* root_directory,
const char* root_image,
- const MountOptions *root_image_options,
+ const MountOptions *root_image_mount_options,
+ const ImagePolicy *root_image_policy,
const NamespaceInfo *ns_info,
char** read_write_paths,
char** read_only_paths,
size_t n_temporary_filesystems,
const MountImage *mount_images,
size_t n_mount_images,
+ const ImagePolicy *mount_image_policy,
const char* tmp_dir,
const char* var_tmp_dir,
const char *creds_path,
const char *verity_data_path,
const MountImage *extension_images,
size_t n_extension_images,
+ const ImagePolicy *extension_image_policy,
char **extension_directories,
const char *propagate_dir,
const char *incoming_dir,
r = dissect_loop_device(
loop_device,
&verity,
- root_image_options,
+ root_image_mount_options,
+ root_image_policy,
dissect_image_flags,
&dissected_image);
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;
}
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
/* Now make the magic happen */
- r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
+ r = apply_mounts(root, mount_image_policy, extension_image_policy, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
if (r < 0)
goto finish;
const char *root_directory,
const char *root_image,
const MountOptions *root_image_options,
+ const ImagePolicy *root_image_policy,
const NamespaceInfo *ns_info,
char **read_write_paths,
char **read_only_paths,
size_t n_temporary_filesystems,
const MountImage *mount_images,
size_t n_mount_images,
+ const ImagePolicy *mount_image_policy,
const char *tmp_dir,
const char *var_tmp_dir,
const char *creds_path,
const char *root_verity,
const MountImage *extension_images,
size_t n_extension_images,
+ const ImagePolicy *extension_image_policy,
char **extension_directories,
const char *propagate_dir,
const char *incoming_dir,
old_state = s->state;
s->state = state;
- if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL, SCOPE_START_CHOWN))
+ if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL, SCOPE_START_CHOWN, SCOPE_RUNNING))
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
if (IN_SET(state, SCOPE_DEAD, SCOPE_FAILED)) {
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
+#include <math.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "bus-error.h"
#include "bus-kernel.h"
#include "bus-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#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_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE,
[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_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE,
[SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
[SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
s->timeout_abort_usec = u->manager->default_timeout_abort_usec;
s->timeout_abort_set = u->manager->default_timeout_abort_set;
s->restart_usec = u->manager->default_restart_usec;
+ s->restart_usec_max = USEC_INFINITY;
s->runtime_max_usec = USEC_INFINITY;
s->type = _SERVICE_TYPE_INVALID;
s->socket_fd = -EBADF;
s->exec_context.keyring_mode = MANAGER_IS_SYSTEM(u->manager) ?
EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT;
+ s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+
s->watchdog_original_usec = USEC_INFINITY;
s->oom_policy = _OOM_POLICY_INVALID;
s->reload_begin_usec = USEC_INFINITY;
s->reload_signal = SIGHUP;
+
+ s->fd_store_preserve_mode = EXEC_PRESERVE_RESTART;
}
static void service_unwatch_control_pid(Service *s) {
return 0;
}
-void service_close_socket_fd(Service *s) {
+void service_release_socket_fd(Service *s) {
assert(s);
+ if (s->socket_fd < 0 && !UNIT_ISSET(s->accept_socket) && !s->socket_peer)
+ return;
+
+ log_unit_debug(UNIT(s), "Closing connection socket.");
+
/* Undo the effect of service_set_socket_fd(). */
s->socket_fd = asynchronous_close(s->socket_fd);
s->socket_peer = socket_peer_unref(s->socket_peer);
}
+static void service_override_notify_access(Service *s, NotifyAccess notify_access_override) {
+ assert(s);
+
+ s->notify_access_override = notify_access_override;
+
+ log_unit_debug(UNIT(s), "notify_access=%s", notify_access_to_string(s->notify_access));
+ log_unit_debug(UNIT(s), "notify_access_override=%s", notify_access_to_string(s->notify_access_override));
+}
+
static void service_stop_watchdog(Service *s) {
assert(s);
log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m");
}
+usec_t service_restart_usec_next(Service *s) {
+ unsigned n_restarts_next;
+ usec_t value;
+
+ assert(s);
+
+ /* 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;
+
+ if (n_restarts_next <= 1 ||
+ s->restart_steps == 0 ||
+ s->restart_usec_max == USEC_INFINITY ||
+ 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);
+
+ /* ((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));
+ }
+
+ 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) {
usec_t current;
int r;
sd_event_source_disable_unref(fs->event_source);
free(fs->fdname);
- safe_close(fs->fd);
+ asynchronous_close(fs->fd);
free(fs);
}
static void service_release_fd_store(Service *s) {
assert(s);
- if (s->n_keep_fd_store > 0)
+ if (!s->fd_store)
return;
log_unit_debug(UNIT(s), "Releasing all stored fds");
+
while (s->fd_store)
service_fd_store_unlink(s->fd_store);
assert(s->n_fd_store == 0);
}
-static void service_release_resources(Unit *u) {
- Service *s = SERVICE(u);
-
+static void service_release_stdio_fd(Service *s) {
assert(s);
- if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
+ if (s->stdin_fd < 0 && s->stdout_fd < 0 && s->stdout_fd < 0)
return;
- log_unit_debug(u, "Releasing resources.");
+ log_unit_debug(UNIT(s), "Releasing stdin/stdout/stderr file descriptors.");
- s->stdin_fd = safe_close(s->stdin_fd);
- s->stdout_fd = safe_close(s->stdout_fd);
- s->stderr_fd = safe_close(s->stderr_fd);
-
- service_release_fd_store(s);
+ s->stdin_fd = asynchronous_close(s->stdin_fd);
+ s->stdout_fd = asynchronous_close(s->stdout_fd);
+ s->stderr_fd = asynchronous_close(s->stderr_fd);
}
-
static void service_done(Unit *u) {
Service *s = SERVICE(u);
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);
s->usb_function_descriptors = mfree(s->usb_function_descriptors);
s->usb_function_strings = mfree(s->usb_function_strings);
- 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);
s->bus_name_pid_lookup_slot = sd_bus_slot_unref(s->bus_name_pid_lookup_slot);
- service_release_resources(u);
+ service_release_socket_fd(s);
+ service_release_stdio_fd(s);
+ service_release_fd_store(s);
}
static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
}
static int service_add_fd_store(Service *s, int fd, const char *name, bool do_poll) {
+ struct stat st;
ServiceFDStore *fs;
int r;
assert(s);
assert(fd >= 0);
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ log_unit_debug(UNIT(s), "Trying to stash fd for dev=" DEVNUM_FORMAT_STR "/inode=%" PRIu64, DEVNUM_FORMAT_VAL(st.st_dev), (uint64_t) st.st_ino);
+
if (s->n_fd_store >= s->n_fd_store_max)
- return -EXFULL; /* Our store is full.
- * Use this errno rather than E[NM]FILE to distinguish from
- * the case where systemd itself hits the file limit. */
+ /* Our store is full. Use this errno rather than E[NM]FILE to distinguish from the case
+ * where systemd itself hits the file limit. */
+ return log_unit_debug_errno(UNIT(s), SYNTHETIC_ERRNO(EXFULL), "Hit fd store limit.");
LIST_FOREACH(fd_store, i, s->fd_store) {
r = same_fd(i->fd, fd);
if (r < 0)
return r;
if (r > 0) {
- safe_close(fd);
+ log_unit_debug(UNIT(s), "Suppressing duplicate fd in fd store.");
+ asynchronous_close(fd);
return 0; /* fd already included */
}
}
assert(s);
while (fdset_size(fds) > 0) {
- _cleanup_close_ int fd = -EBADF;
+ _cleanup_(asynchronous_closep) int fd = -EBADF;
fd = fdset_steal_first(fds);
if (fd < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m");
if (r > 0)
log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fd, strna(name));
- fd = -EBADF;
+
+ TAKE_FD(fd);
}
return 0;
if (s->exit_type == SERVICE_EXIT_CGROUP && cg_unified() < CGROUP_UNIFIED_SYSTEMD)
log_unit_warning(UNIT(s), "Service has ExitType=cgroup set, but we are running with legacy cgroups v1, which might not work correctly. Continuing.");
+ if (s->restart_usec_max == USEC_INFINITY && s->restart_steps > 0)
+ log_unit_warning(UNIT(s), "Service has RestartSteps= but no RestartSecMax= setting. Ignoring.");
+
+ if (s->restart_usec_max != USEC_INFINITY && s->restart_steps == 0)
+ log_unit_warning(UNIT(s), "Service has RestartSecMax= but no RestartSteps= setting. Ignoring.");
+
+ if (s->restart_usec_max < s->restart_usec) {
+ log_unit_warning(UNIT(s), "RestartSecMax= has a value smaller than RestartSec=, resetting RestartSec= to RestartSecMax=.");
+ s->restart_usec = s->restart_usec_max;
+ }
+
return 0;
}
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;
prefix, yes_no(s->guess_main_pid),
prefix, service_type_to_string(s->type),
prefix, service_restart_to_string(s->restart),
- prefix, notify_access_to_string(s->notify_access),
+ prefix, notify_access_to_string(service_get_notify_access(s)),
prefix, notify_state_to_string(s->notify_state),
prefix, oom_policy_to_string(s->oom_policy),
prefix, signal_to_string(s->reload_signal));
fprintf(f,
"%sRestartSec: %s\n"
+ "%sRestartSteps: %u\n"
+ "%sRestartSecMax: %s\n"
"%sTimeoutStartSec: %s\n"
"%sTimeoutStopSec: %s\n"
"%sTimeoutStartFailureMode: %s\n"
"%sTimeoutStopFailureMode: %s\n",
prefix, FORMAT_TIMESPAN(s->restart_usec, USEC_PER_SEC),
+ prefix, s->restart_steps,
+ prefix, FORMAT_TIMESPAN(s->restart_usec_max, USEC_PER_SEC),
prefix, FORMAT_TIMESPAN(s->timeout_start_usec, USEC_PER_SEC),
prefix, FORMAT_TIMESPAN(s->timeout_stop_usec, USEC_PER_SEC),
prefix, service_timeout_failure_mode_to_string(s->timeout_start_failure_mode),
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;
if (s->n_fd_store_max > 0)
fprintf(f,
"%sFile Descriptor Store Max: %u\n"
+ "%sFile Descriptor Store Pin: %s\n"
"%sFile Descriptor Store Current: %zu\n",
prefix, s->n_fd_store_max,
+ prefix, exec_preserve_mode_to_string(s->fd_store_preserve_mode),
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;
prio = may_warn ? LOG_INFO : LOG_DEBUG;
- r = chase_symlinks(s->pid_file, NULL, CHASE_SAFE, NULL, &fd);
+ r = chase(s->pid_file, NULL, CHASE_SAFE, NULL, &fd);
if (r == -ENOLINK) {
log_unit_debug_errno(UNIT(s), r,
"Potentially unsafe symlink chain, will now retry with relaxed checks: %s", s->pid_file);
questionable_pid_file = true;
- r = chase_symlinks(s->pid_file, NULL, 0, NULL, &fd);
+ r = chase(s->pid_file, NULL, 0, NULL, &fd);
}
if (r < 0)
return log_unit_full_errno(UNIT(s), prio, fd,
"Can't open PID file %s (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state));
/* Let's read the PID file now that we chased it down. But we need to convert the O_PATH fd
- * chase_symlinks() returned us into a proper fd first. */
+ * chase() returned us into a proper fd first. */
r = read_one_line_file(FORMAT_PROC_FD_PATH(fd), &k);
if (r < 0)
return log_unit_error_errno(UNIT(s), r,
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,
+ SERVICE_DEAD_RESOURCES_PINNED)) {
unit_unwatch_all_pids(UNIT(s));
unit_dequeue_rewatch_pids(UNIT(s));
}
- if (!IN_SET(state,
- SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
- SERVICE_RUNNING,
- SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
- SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
- SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) &&
- !(state == SERVICE_DEAD && UNIT(s)->job))
- service_close_socket_fd(s);
-
if (state != SERVICE_START)
s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
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, s->restart_usec);
+ 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,
+ SERVICE_DEAD_RESOURCES_PINNED)) {
(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)
if (flags & EXEC_IS_CONTROL)
/* A control process */
- return IN_SET(s->notify_access, NOTIFY_EXEC, NOTIFY_ALL);
+ return IN_SET(service_get_notify_access(s), NOTIFY_EXEC, NOTIFY_ALL);
/* We only spawn main processes and control processes, so any
* process that is not a control process is a main process */
- return s->notify_access != NOTIFY_NONE;
+ return service_get_notify_access(s) != NOTIFY_NONE;
}
static Service *service_get_triggering_service(Service *s) {
if (r < 0)
return r;
- our_env = new0(char*, 12);
+ our_env = new0(char*, 13);
if (!our_env)
return -ENOMEM;
return -ENOMEM;
exec_params.notify_socket = UNIT(s)->manager->notify_socket;
+
+ if (s->n_fd_store_max > 0)
+ if (asprintf(our_env + n_env++, "FDSTORE=%u", s->n_fd_store_max) < 0)
+ return -ENOMEM;
}
if (s->main_pid > 0)
&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 ServiceState service_determine_dead_state(Service *s) {
+ assert(s);
+
+ return s->fd_store && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD;
+}
+
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;
+ end_state = service_determine_dead_state(s);
+ 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;
+ end_state = service_determine_dead_state(s);
+ 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, s->restart_usec);
- 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->forbid_restart = false;
+ /* Reset NotifyAccess override */
+ 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);
+ /* Also get rid of the fd store, if that's configured. */
+ if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO)
+ service_release_fd_store(s);
+
/* 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. */
s->n_restarts ++;
s->flush_n_restarts = false;
+ s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+
log_unit_struct(UNIT(s), LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_UNIT_RESTART_SCHEDULED_STR,
LOG_UNIT_INVOCATION_ID(UNIT(s)),
s->control_command,
timeout,
EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|
+ (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD) ? EXEC_WRITE_CREDENTIALS : 0)|
(IN_SET(s->control_command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
(IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0)|
(IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_START) ? EXEC_SETENV_MONITOR_RESULT : 0)|
r = service_spawn(s,
s->main_command,
s->timeout_start_usec,
- EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_SETENV_MONITOR_RESULT,
+ EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_SETENV_MONITOR_RESULT|EXEC_WRITE_CREDENTIALS,
&pid);
if (r < 0)
goto fail;
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));
+ assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED));
r = unit_acquire_invocation_id(u);
if (r < 0)
s->status_text = mfree(s->status_text);
s->status_errno = 0;
+ s->notify_access_override = _NOTIFY_ACCESS_INVALID;
s->notify_state = NOTIFY_UNKNOWN;
s->watchdog_original_usec = s->watchdog_usec;
/* 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) {
- service_set_state(s, SERVICE_DEAD);
+ case SERVICE_AUTO_RESTART:
+ /* A restart will be scheduled or is in progress. */
+ service_set_state(s, service_determine_dead_state(s));
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:
+ case SERVICE_DEAD_RESOURCES_PINNED:
+ default:
+ /* Unknown state, or unit_stop() should already have handled these */
+ assert_not_reached();
+ }
}
static int service_reload(Unit *u) {
}
}
+ if (s->notify_access_override >= 0)
+ (void) serialize_item(f, "notify-access-override", notify_access_to_string(s->notify_access_override));
+
(void) serialize_dual_timestamp(f, "watchdog-timestamp", &s->watchdog_timestamp);
(void) serialize_bool(f, "forbid-restart", s->forbid_restart);
deserialize_dual_timestamp(value, &s->main_exec_status.start_timestamp);
else if (streq(key, "main-exec-status-exit"))
deserialize_dual_timestamp(value, &s->main_exec_status.exit_timestamp);
- else if (streq(key, "watchdog-timestamp"))
+ else if (streq(key, "notify-access-override")) {
+ NotifyAccess notify_access;
+
+ notify_access = notify_access_from_string(value);
+ if (notify_access < 0)
+ log_unit_debug(u, "Failed to parse notify-access-override value: %s", value);
+ else
+ s->notify_access_override = notify_access;
+ } else if (streq(key, "watchdog-timestamp"))
deserialize_dual_timestamp(value, &s->watchdog_timestamp);
else if (streq(key, "forbid-restart")) {
int b;
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, SERVICE_DEAD_RESOURCES_PINNED))
+ 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:
+ case SERVICE_DEAD_RESOURCES_PINNED:
unit_prune_cgroup(u);
break;
* has been received */
if (f != SERVICE_SUCCESS)
service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
- else if (!s->remain_after_exit || s->notify_access == NOTIFY_MAIN)
+ else if (!s->remain_after_exit || service_get_notify_access(s) == NOTIFY_MAIN)
/* The service has never been and will never be active */
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL);
break;
case SERVICE_AUTO_RESTART:
if (s->restart_usec > 0)
log_unit_debug(UNIT(s),
- "Service RestartSec=%s expired, scheduling restart.",
- FORMAT_TIMESPAN(s->restart_usec, USEC_PER_SEC));
+ "Service restart interval %s expired, scheduling restart.",
+ 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.");
static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds) {
assert(s);
- if (s->notify_access == NOTIFY_NONE) {
+ NotifyAccess notify_access = service_get_notify_access(s);
+
+ if (notify_access == NOTIFY_NONE) {
log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception is disabled.", pid);
return false;
}
- if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) {
+ if (notify_access == NOTIFY_MAIN && pid != s->main_pid) {
if (s->main_pid != 0)
log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
else
return false;
}
- if (s->notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
+ if (notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
if (s->main_pid != 0 && s->control_pid != 0)
log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT,
pid, s->main_pid, s->control_pid);
assert(u);
assert(ucred);
- if (!service_notify_message_authorized(SERVICE(u), ucred->pid, fds))
+ if (!service_notify_message_authorized(s, ucred->pid, fds))
return;
if (DEBUG_LOGGING) {
}
}
+ /* Interpret NOTIFYACCESS= */
+ e = strv_find_startswith(tags, "NOTIFYACCESS=");
+ if (e) {
+ NotifyAccess notify_access;
+
+ notify_access = notify_access_from_string(e);
+ if (notify_access < 0)
+ log_unit_warning_errno(u, notify_access,
+ "Failed to parse NOTIFYACCESS= field value '%s' in notification message, ignoring: %m", e);
+
+ /* We don't need to check whether the new access mode is more strict than what is
+ * already in use, since only the privileged process is allowed to change it
+ * in the first place. */
+ if (service_get_notify_access(s) != notify_access) {
+ service_override_notify_access(s, notify_access);
+ notify_dbus = true;
+ }
+ }
+
/* Interpret ERRNO= */
e = strv_find_startswith(tags, "ERRNO=");
if (e) {
assert(!s->socket_peer);
- if (s->state != SERVICE_DEAD)
+ if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED))
return -EAGAIN;
if (getpeername_pretty(fd, true, &peer_text) >= 0) {
assert(s);
if (s->state == SERVICE_FAILED)
- service_set_state(s, SERVICE_DEAD);
+ service_set_state(s, service_determine_dead_state(s));
s->result = SERVICE_SUCCESS;
s->reload_result = SERVICE_SUCCESS;
static int service_clean(Unit *u, ExecCleanMask mask) {
_cleanup_strv_free_ char **l = NULL;
+ bool may_clean_fdstore = false;
Service *s = SERVICE(u);
int r;
assert(s);
assert(mask != 0);
- if (s->state != SERVICE_DEAD)
+ if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED))
return -EBUSY;
+ /* Determine if there's anything we could potentially clean */
r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l);
if (r < 0)
return r;
- if (strv_isempty(l))
- return -EUNATCH;
+ if (mask & EXEC_CLEAN_FDSTORE)
+ may_clean_fdstore = s->n_fd_store > 0 || s->n_fd_store_max > 0;
+
+ if (strv_isempty(l) && !may_clean_fdstore)
+ return -EUNATCH; /* Nothing to potentially clean */
+
+ /* Let's clean the stuff we can clean quickly */
+ if (may_clean_fdstore)
+ service_release_fd_store(s);
+ /* If we are done, leave quickly */
+ if (strv_isempty(l)) {
+ if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store)
+ service_set_state(s, SERVICE_DEAD);
+ return 0;
+ }
+
+ /* We need to clean disk stuff. This is slow, hence do it out of process, and change state */
service_unwatch_control_pid(s);
s->clean_result = SERVICE_SUCCESS;
s->control_command = NULL;
static int service_can_clean(Unit *u, ExecCleanMask *ret) {
Service *s = SERVICE(u);
+ ExecCleanMask mask = 0;
+ int r;
assert(s);
+ assert(ret);
+
+ r = exec_context_get_clean_mask(&s->exec_context, &mask);
+ if (r < 0)
+ return r;
- return exec_context_get_clean_mask(&s->exec_context, ret);
+ if (s->n_fd_store_max > 0)
+ mask |= EXEC_CLEAN_FDSTORE;
+
+ *ret = mask;
+ return 0;
}
static const char *service_finished_job(Unit *u, JobType t, JobResult result) {
return 1;
}
+static void service_release_resources(Unit *u) {
+ Service *s = SERVICE(ASSERT_PTR(u));
+
+ /* Invoked by the unit state engine, whenever it realizes that unit is dead and there's no job
+ * anymore for it, and it hence is a good idea to release resources */
+
+ /* Don't release resources if this is a transitionary failed/dead state
+ * (i.e. SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART), insist on a permanent
+ * failure state. */
+ if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED))
+ return;
+
+ log_unit_debug(u, "Releasing resources...");
+
+ service_release_socket_fd(s);
+ service_release_stdio_fd(s);
+
+ if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES)
+ service_release_fd_store(s);
+
+ if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store)
+ service_set_state(s, SERVICE_DEAD);
+}
+
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
[SERVICE_RESTART_NO] = "no",
[SERVICE_RESTART_ON_SUCCESS] = "on-success",
.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"
char *pid_file;
usec_t restart_usec;
+ unsigned restart_steps;
+ usec_t restart_usec_max;
usec_t timeout_start_usec;
usec_t timeout_stop_usec;
usec_t timeout_abort_usec;
/* Runtime data of the execution context */
ExecRuntime *exec_runtime;
- DynamicCreds dynamic_creds;
pid_t main_pid, control_pid;
PathSpec *pid_file_pathspec;
NotifyAccess notify_access;
+ NotifyAccess notify_access_override;
NotifyState notify_state;
sd_bus_slot *bus_name_pid_lookup_slot;
ServiceFDStore *fd_store;
size_t n_fd_store;
unsigned n_fd_store_max;
- unsigned n_keep_fd_store;
+ ExecPreserveMode fd_store_preserve_mode;
char *usb_function_descriptors;
char *usb_function_strings;
return s->timeout_abort_set ? s->timeout_abort_usec : s->timeout_stop_usec;
}
+static inline NotifyAccess service_get_notify_access(Service *s) {
+ assert(s);
+ return s->notify_access_override < 0 ? s->notify_access : s->notify_access_override;
+}
+
static inline usec_t service_get_watchdog_usec(Service *s) {
assert(s);
return s->watchdog_override_enable ? s->watchdog_override_usec : s->watchdog_original_usec;
extern const UnitVTable service_vtable;
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);
+void service_release_socket_fd(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_;
#include "bpf-firewall.h"
#include "bus-error.h"
#include "bus-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "constants.h"
#include "copy.h"
#include "dbus-socket.h"
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 (!c)
goto no_label;
- r = chase_symlinks(c->path, SERVICE(service)->exec_context.root_directory, CHASE_PREFIX_ROOT, &path, NULL);
+ r = chase(c->path, SERVICE(service)->exec_context.root_directory, CHASE_PREFIX_ROOT, &path, NULL);
if (r < 0)
goto no_label;
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 (r < 0) {
/* We failed to activate the new service, but it still exists. Let's make sure the
* service closes and forgets the connection fd again, immediately. */
- service_close_socket_fd(SERVICE(service));
+ service_release_socket_fd(SERVICE(service));
goto fail;
}
/* 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;
Timer *t = TIMER(u);
assert(t);
+ assert(ret);
*ret = t->persistent ? EXEC_CLEAN_STATE : 0;
return 0;
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;
#include "bus-util.h"
#include "cgroup-setup.h"
#include "cgroup-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "core-varlink.h"
#include "dbus-unit.h"
#include "dbus.h"
#include "load-dropin.h"
#include "load-fragment.h"
#include "log.h"
+#include "logarithm.h"
#include "macro.h"
#include "missing_audit.h"
#include "mkdir-label.h"
return false;
}
+void unit_release_resources(Unit *u) {
+ UnitActiveState state;
+ ExecContext *ec;
+
+ assert(u);
+
+ if (u->job || u->nop_job)
+ return;
+
+ if (u->perpetual)
+ return;
+
+ state = unit_active_state(u);
+ if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED))
+ return;
+
+ if (unit_will_restart(u))
+ return;
+
+ ec = unit_get_exec_context(u);
+ if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART)
+ exec_context_destroy_runtime_directory(ec, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
+
+ if (UNIT_VTABLE(u)->release_resources)
+ UNIT_VTABLE(u)->release_resources(u);
+}
+
bool unit_may_gc(Unit *u) {
UnitActiveState state;
int r;
assert(u);
- /* Checks whether the unit is ready to be unloaded for garbage collection.
- * Returns true when the unit may be collected, and false if there's some
- * reason to keep it loaded.
+ /* Checks whether the unit is ready to be unloaded for garbage collection. Returns true when the
+ * unit may be collected, and false if there's some reason to keep it loaded.
*
- * References from other units are *not* checked here. Instead, this is done
- * in unit_gc_sweep(), but using markers to properly collect dependency loops.
+ * References from other units are *not* checked here. Instead, this is done in unit_gc_sweep(), but
+ * using markers to properly collect dependency loops.
*/
if (u->job || u->nop_job)
return false;
- state = unit_active_state(u);
-
- /* If the unit is inactive and failed and no job is queued for it, then release its runtime resources */
- if (UNIT_IS_INACTIVE_OR_FAILED(state) &&
- UNIT_VTABLE(u)->release_resources)
- UNIT_VTABLE(u)->release_resources(u);
-
if (u->perpetual)
return false;
if (sd_bus_track_count(u->bus_track) > 0)
return false;
- /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
+ state = unit_active_state(u);
+
+ /* But we keep the unit object around for longer when it is referenced or configured to not be
+ * gc'ed */
switch (u->collect_mode) {
case COLLECT_INACTIVE:
return false;
}
- if (UNIT_VTABLE(u)->may_gc && !UNIT_VTABLE(u)->may_gc(u))
- return false;
+ if (!UNIT_VTABLE(u)->may_gc)
+ return true;
- return true;
+ return UNIT_VTABLE(u)->may_gc(u);
}
void unit_add_to_load_queue(Unit *u) {
u->in_stop_when_bound_queue = true;
}
+static bool unit_can_release_resources(Unit *u) {
+ ExecContext *ec;
+
+ assert(u);
+
+ if (UNIT_VTABLE(u)->release_resources)
+ return true;
+
+ ec = unit_get_exec_context(u);
+ if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART)
+ return true;
+
+ return false;
+}
+
+void unit_submit_to_release_resources_queue(Unit *u) {
+ assert(u);
+
+ if (u->in_release_resources_queue)
+ return;
+
+ if (u->job || u->nop_job)
+ return;
+
+ if (u->perpetual)
+ return;
+
+ if (!unit_can_release_resources(u))
+ return;
+
+ LIST_PREPEND(release_resources_queue, u->manager->release_resources_queue, u);
+ u->in_release_resources_queue = true;
+}
+
static void unit_clear_dependencies(Unit *u) {
assert(u);
if (!u)
return NULL;
+ sd_event_source_disable_unref(u->auto_start_stop_event_source);
+
u->transient_file = safe_fclose(u->transient_file);
if (!MANAGER_IS_RELOADING(u->manager))
if (u->in_stop_when_bound_queue)
LIST_REMOVE(stop_when_bound_queue, u->manager->stop_when_bound_queue, u);
+ if (u->in_release_resources_queue)
+ LIST_REMOVE(release_resources_queue, u->manager->release_resources_queue, u);
+
bpf_firewall_close(u);
hashmap_free(u->bpf_foreign_by_key);
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);
}
if (UNIT_VTABLE(u)->once_only && dual_timestamp_is_set(&u->inactive_enter_timestamp))
return -ESTALE;
- /* If the conditions failed, don't do anything at all. If we already are activating this call might
+ /* If the conditions were unmet, don't do anything at all. If we already are activating this call might
* still be useful to speed up activation in case there is some hold-off time, but we don't want to
* recheck the condition in that case. */
if (state != UNIT_ACTIVATING &&
!unit_test_condition(u))
- return log_unit_debug_errno(u, SYNTHETIC_ERRNO(ECOMM), "Starting requested but condition failed. Not starting unit.");
+ return log_unit_debug_errno(u, SYNTHETIC_ERRNO(ECOMM), "Starting requested but condition not met. Not starting unit.");
/* If the asserts failed, fail the entire job */
if (state != UNIT_ACTIVATING &&
assert(j);
if (j->state == JOB_WAITING)
-
/* So we reached a different state for this job. Let's see if we can run it now if it failed previously
* due to EAGAIN. */
job_add_to_run_queue(j);
/* Maybe the unit should be GC'ed now? */
unit_add_to_gc_queue(u);
+
+ /* Maybe we can release some resources now? */
+ unit_submit_to_release_resources_queue(u);
}
if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) {
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 u->unit_file_state;
}
-int unit_get_unit_file_preset(Unit *u) {
+PresetAction unit_get_unit_file_preset(Unit *u) {
int r;
assert(u);
}
/* If there are encrypted credentials we might need to access the TPM. */
- bool allow_tpm = false;
- ExecLoadCredential *load_cred;
- ExecSetCredential *set_cred;
- HASHMAP_FOREACH(load_cred, ec->load_credentials)
- if ((allow_tpm |= load_cred->encrypted))
- break;
- HASHMAP_FOREACH(set_cred, ec->set_credentials)
- if ((allow_tpm |= set_cred->encrypted))
- break;
-
- if (allow_tpm) {
- r = cgroup_add_device_allow(cc, "/dev/tpmrm0", "rw");
+ if (exec_context_has_encrypted_credentials(ec)) {
+ r = cgroup_add_device_allow(cc, "char-tpm", "rw");
if (r < 0)
return r;
}
return NULL;
}
-char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
- assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
+const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
+ assert(s);
+ assert(popcount(flags & (UNIT_ESCAPE_EXEC_SYNTAX_ENV | UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C)) <= 1);
+ 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 quotes. */
+
+ if (flags & (UNIT_ESCAPE_EXEC_SYNTAX_ENV | UNIT_ESCAPE_EXEC_SYNTAX)) {
+ char *t2;
+
+ if (flags & UNIT_ESCAPE_EXEC_SYNTAX_ENV) {
+ t2 = strreplace(s, "$", "$$");
+ if (!t2)
+ return NULL;
+ free_and_replace(t, t2);
+ }
- if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
- char *t2 = shell_escape(s, "$;'\"");
+ t2 = shell_escape(t ?: s, "\"");
if (!t2)
return NULL;
free_and_replace(t, t2);
s = t;
} else if (flags & UNIT_ESCAPE_C) {
- char *t2 = cescape(s);
+ char *t2;
+
+ t2 = cescape(s);
if (!t2)
return NULL;
free_and_replace(t, t2);
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) {
assert(u);
assert(where);
- r = chase_symlinks(where, NULL, CHASE_NONEXISTENT, &canonical_where, NULL);
+ r = chase(where, NULL, CHASE_NONEXISTENT, &canonical_where, NULL);
if (r < 0) {
log_unit_debug_errno(u, r, "Failed to check %s for symlinks, ignoring: %m", where);
return 0;
if (r < 0)
return r;
- r = unit_setup_dynamic_creds(u);
- if (r < 0)
- return r;
-
return 0;
}
assert(u);
assert(context);
- if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
- (context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u)))
+ /* EXEC_PRESERVE_RESTART is handled via unit_release_resources()! */
+ if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO)
exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id);
#include "bpf-program.h"
#include "condition.h"
#include "emergency-action.h"
+#include "install.h"
#include "list.h"
#include "show-status.h"
#include "set.h"
/* Queue of units that have a BindTo= dependency on some other unit, and should possibly be shut down */
LIST_FIELDS(Unit, stop_when_bound_queue);
+ /* Queue of units that should be checked if they can release resources now */
+ LIST_FIELDS(Unit, release_resources_queue);
+
/* PIDs we keep an eye on. Note that a unit might have many
* more, but these are the ones we care enough about to
* process SIGCHLD for */
/* Make sure we never enter endless loops with the StopWhenUnneeded=, BindsTo=, Uphold= logic */
RateLimit auto_start_stop_ratelimit;
+ sd_event_source *auto_start_stop_event_source;
/* Reference to a specific UID/GID */
uid_t ref_uid;
/* Cached unit file state and preset */
UnitFileState unit_file_state;
- int unit_file_preset;
+ PresetAction unit_file_preset;
/* Where the cpu.stat or cpuacct.usage was at the time the unit was started */
nsec_t cpu_usage_base;
bool in_stop_when_unneeded_queue:1;
bool in_start_when_upheld_queue:1;
bool in_stop_when_bound_queue:1;
+ bool in_release_resources_queue:1;
bool sent_dbus_new_signal:1;
/* Flags used when writing drop-in files or transient unit files */
typedef enum UnitWriteFlags {
/* Write a runtime unit file or drop-in (i.e. one below /run) */
- UNIT_RUNTIME = 1 << 0,
+ UNIT_RUNTIME = 1 << 0,
/* Write a persistent drop-in (i.e. one below /etc) */
- UNIT_PERSISTENT = 1 << 1,
+ UNIT_PERSISTENT = 1 << 1,
/* Place this item in the per-unit-type private section, instead of [Unit] */
- UNIT_PRIVATE = 1 << 2,
+ UNIT_PRIVATE = 1 << 2,
+
+ /* Apply specifier escaping */
+ UNIT_ESCAPE_SPECIFIERS = 1 << 3,
- /* Apply specifier escaping before writing */
- UNIT_ESCAPE_SPECIFIERS = 1 << 3,
+ /* Escape elements of ExecStart= syntax, incl. prevention of variable expansion */
+ UNIT_ESCAPE_EXEC_SYNTAX_ENV = 1 << 4,
- /* Escape elements of ExecStart= syntax before writing */
- UNIT_ESCAPE_EXEC_SYNTAX = 1 << 4,
+ /* Escape elements of ExecStart=: syntax (no variable expansion) */
+ UNIT_ESCAPE_EXEC_SYNTAX = 1 << 5,
/* Apply C escaping before writing */
- UNIT_ESCAPE_C = 1 << 5,
+ UNIT_ESCAPE_C = 1 << 6,
} UnitWriteFlags;
/* Returns true if neither persistent, nor runtime storage is requested, i.e. this is a check invocation only */
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;
int unit_choose_id(Unit *u, const char *name);
int unit_set_description(Unit *u, const char *description);
+void unit_release_resources(Unit *u);
+
bool unit_may_gc(Unit *u);
static inline bool unit_is_extrinsic(Unit *u) {
void unit_submit_to_stop_when_unneeded_queue(Unit *u);
void unit_submit_to_start_when_upheld_queue(Unit *u);
void unit_submit_to_stop_when_bound_queue(Unit *u);
+void unit_submit_to_release_resources_queue(Unit *u);
int unit_merge(Unit *u, Unit *other);
int unit_merge_by_name(Unit *u, const char *other);
void unit_trigger_notify(Unit *u);
UnitFileState unit_get_unit_file_state(Unit *u);
-int unit_get_unit_file_preset(Unit *u);
+PresetAction unit_get_unit_file_preset(Unit *u);
Unit* unit_ref_set(UnitRef *ref, Unit *source, Unit *target);
void unit_ref_unset(UnitRef *ref);
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);
/* Note: this matches deps that have *any* of the atoms specified in match_atom set */
#define UNIT_FOREACH_DEPENDENCY(other, u, match_atom) \
_UNIT_FOREACH_DEPENDENCY(other, u, match_atom, UNIQ_T(data, UNIQ))
+
+#define _LOG_CONTEXT_PUSH_UNIT(unit, u, c) \
+ const Unit *u = (unit); \
+ const ExecContext *c = unit_get_exec_context(u); \
+ LOG_CONTEXT_PUSH_KEY_VALUE(u->manager->unit_log_field, u->id); \
+ LOG_CONTEXT_PUSH_KEY_VALUE(u->manager->invocation_log_field, u->invocation_id_string); \
+ LOG_CONTEXT_PUSH_IOV(c ? c->log_extra_fields : NULL, c ? c->n_log_extra_fields : 0)
+
+#define LOG_CONTEXT_PUSH_UNIT(unit) \
+ _LOG_CONTEXT_PUSH_UNIT(unit, UNIQ_T(u, UNIQ), UNIQ_T(c, UNIQ))
return 0;
}
-static int parse_auxv64(
- const uint64_t *auxv,
- size_t size_bytes,
- int *at_secure,
- uid_t *uid,
- uid_t *euid,
- gid_t *gid,
- gid_t *egid) {
-
- assert(auxv || size_bytes == 0);
-
- if (size_bytes % (2 * sizeof(uint64_t)) != 0)
- return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes);
-
- size_t words = size_bytes / sizeof(uint64_t);
-
- /* Note that we set output variables even on error. */
-
- for (size_t i = 0; i + 1 < words; i += 2)
- switch (auxv[i]) {
- case AT_SECURE:
- *at_secure = auxv[i + 1] != 0;
- break;
- case AT_UID:
- *uid = auxv[i + 1];
- break;
- case AT_EUID:
- *euid = auxv[i + 1];
- break;
- case AT_GID:
- *gid = auxv[i + 1];
- break;
- case AT_EGID:
- *egid = auxv[i + 1];
- break;
- case AT_NULL:
- if (auxv[i + 1] != 0)
- goto error;
- return 0;
- }
- error:
- return log_warning_errno(SYNTHETIC_ERRNO(ENODATA),
- "AT_NULL terminator not found, cannot parse auxv structure.");
-}
-
-static int parse_auxv32(
- const uint32_t *auxv,
- size_t size_bytes,
- int *at_secure,
- uid_t *uid,
- uid_t *euid,
- gid_t *gid,
- gid_t *egid) {
-
- assert(auxv || size_bytes == 0);
-
- size_t words = size_bytes / sizeof(uint32_t);
-
- if (size_bytes % (2 * sizeof(uint32_t)) != 0)
- return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes);
-
- /* Note that we set output variables even on error. */
-
- for (size_t i = 0; i + 1 < words; i += 2)
- switch (auxv[i]) {
- case AT_SECURE:
- *at_secure = auxv[i + 1] != 0;
- break;
- case AT_UID:
- *uid = auxv[i + 1];
- break;
- case AT_EUID:
- *euid = auxv[i + 1];
- break;
- case AT_GID:
- *gid = auxv[i + 1];
- break;
- case AT_EGID:
- *egid = auxv[i + 1];
- break;
- case AT_NULL:
- if (auxv[i + 1] != 0)
- goto error;
- return 0;
- }
- error:
- return log_warning_errno(SYNTHETIC_ERRNO(ENODATA),
- "AT_NULL terminator not found, cannot parse auxv structure.");
-}
-
static int grant_user_access(int core_fd, const Context *context) {
int at_secure = -1;
uid_t uid = UID_INVALID, euid = UID_INVALID;
return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Core file has non-native endianness, not adjusting permissions.");
- if (elf[EI_CLASS] == ELFCLASS64)
- r = parse_auxv64((const uint64_t*) context->meta[META_PROC_AUXV],
- context->meta_size[META_PROC_AUXV],
- &at_secure, &uid, &euid, &gid, &egid);
- else
- r = parse_auxv32((const uint32_t*) context->meta[META_PROC_AUXV],
- context->meta_size[META_PROC_AUXV],
- &at_secure, &uid, &euid, &gid, &egid);
+ r = parse_auxv(LOG_WARNING,
+ /* elf_class= */ elf[EI_CLASS],
+ context->meta[META_PROC_AUXV],
+ context->meta_size[META_PROC_AUXV],
+ &at_secure, &uid, &euid, &gid, &egid);
if (r < 0)
return r;
}
assert(input_fd < 0);
- input_fd = *(int*) CMSG_DATA(found);
+ input_fd = *CMSG_TYPED_DATA(found, int);
break;
} else
cmsg_close_all(&mh);
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "compress.h"
#include "constants.h"
#include "dissect-image.h"
static bool arg_reverse = false;
static bool arg_quiet = false;
static bool arg_all = false;
+static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int add_match(sd_journal *j, const char *match) {
_cleanup_free_ char *p = NULL;
" --all Look at all journal files instead of local ones\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ARG_FILE,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_ALL,
};
int c, r;
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version" , no_argument, NULL, ARG_VERSION },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
- { "debugger", required_argument, NULL, ARG_DEBUGGER },
- { "debugger-arguments", required_argument, NULL, 'A' },
- { "output", required_argument, NULL, 'o' },
- { "field", required_argument, NULL, 'F' },
- { "file", required_argument, NULL, ARG_FILE },
- { "directory", required_argument, NULL, 'D' },
- { "reverse", no_argument, NULL, 'r' },
- { "since", required_argument, NULL, 'S' },
- { "until", required_argument, NULL, 'U' },
- { "quiet", no_argument, NULL, 'q' },
- { "json", required_argument, NULL, ARG_JSON },
- { "root", required_argument, NULL, ARG_ROOT },
- { "image", required_argument, NULL, ARG_IMAGE },
- { "all", no_argument, NULL, ARG_ALL },
+ { "help", no_argument, NULL, 'h' },
+ { "version" , no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "debugger", required_argument, NULL, ARG_DEBUGGER },
+ { "debugger-arguments", required_argument, NULL, 'A' },
+ { "output", required_argument, NULL, 'o' },
+ { "field", required_argument, NULL, 'F' },
+ { "file", required_argument, NULL, ARG_FILE },
+ { "directory", required_argument, NULL, 'D' },
+ { "reverse", no_argument, NULL, 'r' },
+ { "since", required_argument, NULL, 'S' },
+ { "until", required_argument, NULL, 'U' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "all", no_argument, NULL, ARG_ALL },
{}
};
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case 'r':
arg_reverse = true;
break;
if (!*p)
return 0;
- r = chase_symlinks(*p, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+ r = chase(*p, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve \"%s%s\": %m", strempty(root), *p);
free_and_replace(*p, resolved);
- /* chase_symlinks() with flag CHASE_NONEXISTENT will return 0 if the file doesn't exist and 1 if it does.
+ /* chase() with flag CHASE_NONEXISTENT will return 0 if the file doesn't exist and 1 if it does.
* Return that to the caller
*/
return r;
return r;
assert(r > 0);
- r = chase_symlinks_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved);
+ r = chase_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved);
if (r < 0)
return log_error_errno(r, "Cannot access \"%s%s\": %m", strempty(arg_root), filename);
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
if (base64_size < 0)
return base64_size;
- if (arg_pretty) {
+ /* Pretty print makes sense only if we're printing stuff to stdout
+ * and if a cred name is provided via --name= (since we can't use
+ * the output file name as the cred name here) */
+ if (arg_pretty && !output_path && name) {
_cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
- if (name) {
- escaped = cescape(name);
- if (!escaped)
- return log_oom();
- }
+ escaped = cescape(name);
+ if (!escaped)
+ return log_oom();
indented = strreplace(base64_buf, "\n", " \\\n ");
if (!indented)
return log_oom();
- j = strjoin("SetCredentialEncrypted=", name, ": \\\n ", indented, "\n");
+ j = strjoin("SetCredentialEncrypted=", escaped, ": \\\n ", indented, "\n");
if (!j)
return log_oom();
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;
#include "format-table.h"
#include "parse-util.h"
-int list_enrolled(struct crypt_device *cd) {
+struct keyslot_metadata {
+ int slot;
+ const char *type;
+};
- struct keyslot_metadata {
- int slot;
- const char *type;
- } *keyslot_metadata = NULL;
+int list_enrolled(struct crypt_device *cd) {
+ _cleanup_free_ struct keyslot_metadata *keyslot_metadata = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
size_t n_keyslot_metadata = 0;
int slot_max, r;
_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 "alloc-util.h"
#include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fs-util.h"
_cleanup_free_ char *x = NULL, *y = NULL;
int r;
- r = chase_symlinks(a, NULL, CHASE_TRAIL_SLASH, &x, NULL);
+ r = chase(a, NULL, CHASE_TRAIL_SLASH, &x, NULL);
if (r < 0)
return r;
- r = chase_symlinks(b, NULL, CHASE_TRAIL_SLASH, &y, NULL);
+ r = chase(b, NULL, CHASE_TRAIL_SLASH, &y, NULL);
if (r < 0)
return r;
if (!dirname)
return -ENOMEM;
- if (chase_symlinks(dirname, NULL, 0, &target, NULL) < 0)
+ if (chase(dirname, NULL, 0, &target, NULL) < 0)
return false;
NULSTR_FOREACH(p, prefixes) {
#include "architecture.h"
#include "blockdev-util.h"
#include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "copy.h"
#include "device-util.h"
#include "devnum-util.h"
ACTION_COPY_FROM,
ACTION_COPY_TO,
ACTION_DISCOVER,
+ ACTION_VALIDATE,
} arg_action = ACTION_DISSECT;
static char *arg_image = NULL;
static char *arg_path = NULL;
static bool arg_in_memory = false;
static char **arg_argv = NULL;
static char *arg_loop_ref = NULL;
+static ImagePolicy* arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
" 'base64:'\n"
" --verity-data=PATH Specify data file with hash tree for verity if it is\n"
" not embedded in IMAGE\n"
+ " --image-policy=POLICY\n"
+ " Specify image dissection policy\n"
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --loop-ref=NAME Set reference string for loopback device\n"
" -x --copy-from Copy files from image to host\n"
" -a --copy-to Copy files from host to image\n"
" --discover Discover DDIs in well known directories\n"
+ " --validate Validate image and image policy\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ARG_ATTACH,
ARG_DETACH,
ARG_LOOP_REF,
+ ARG_IMAGE_POLICY,
+ ARG_VALIDATE,
};
static const struct option options[] = {
{ "json", required_argument, NULL, ARG_JSON },
{ "discover", no_argument, NULL, ARG_DISCOVER },
{ "loop-ref", required_argument, NULL, ARG_LOOP_REF },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "validate", no_argument, NULL, ARG_VALIDATE },
{}
};
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_VALIDATE:
+ arg_action = ACTION_VALIDATE;
+ break;
+
case '?':
return -EINVAL;
if (r < 0)
return r;
- arg_flags |= DISSECT_IMAGE_READ_ONLY;
+ /* when dumping image info be even more liberal than otherwise, do not even require a single valid partition */
+ arg_flags |= DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ALLOW_EMPTY;
break;
case ACTION_MOUNT:
if (optind != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected no argument.");
+ break;
+ case ACTION_VALIDATE:
+ if (optind + 1 != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Expected an image file path as only argument.");
+
+ r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image);
+ if (r < 0)
+ return r;
+
+ arg_flags |= DISSECT_IMAGE_READ_ONLY;
+ arg_flags &= ~(DISSECT_IMAGE_PIN_PARTITION_DEVICES|DISSECT_IMAGE_ADD_PARTITION_DEVICES);
break;
default:
if (uid_is_system(uid))
return ansi_normal(); /* files in disk images are typically owned by root and other system users, no issue there */
if (uid_is_dynamic(uid))
- return ansi_highlight_red(); /* files should never be owned persistently by dynamic users, and there are just no execuses */
+ return ansi_highlight_red(); /* files should never be owned persistently by dynamic users, and there are just no excuses */
if (uid_is_container(uid))
return ansi_highlight_cyan();
case ACTION_COPY_FROM: {
_cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF;
- source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ source_fd = chase_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (source_fd < 0)
return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target);
is_dir = r == O_DIRECTORY;
- r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
+ r = chase(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
if (r < 0)
return log_error_errno(r, "Failed to open '%s': %m", dn);
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
int r;
- fd = chase_symlinks_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
+ fd = chase_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
if (fd == -ENOTDIR)
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
if (fd < 0)
return 0;
}
+static int action_validate(void) {
+ int r;
+
+ r = dissect_image_file_and_warn(
+ arg_image,
+ &arg_verity_settings,
+ NULL,
+ arg_image_policy,
+ arg_flags,
+ NULL);
+ if (r < 0)
+ return r;
+
+ if (isatty(STDOUT_FILENO) && emoji_enabled())
+ printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES));
+
+ printf("%sOK%s", ansi_highlight_green(), ansi_normal());
+
+ if (isatty(STDOUT_FILENO) && emoji_enabled())
+ printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES));
+
+ putc('\n', stdout);
+ return 0;
+}
+
static int run(int argc, char *argv[]) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
* available we turn off partition table
* support */
+ if (arg_action == ACTION_VALIDATE)
+ return action_validate();
+
open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
r = dissect_loop_device_and_warn(
d,
&arg_verity_settings,
- NULL,
+ /* mount_options= */ NULL,
+ arg_image_policy,
arg_flags,
&m);
if (r < 0)
#include "alloc-util.h"
#include "ask-password-api.h"
#include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "copy.h"
#include "creds-util.h"
#include "dissect-image.h"
#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 ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static bool press_any_key(void) {
char k = 0;
return k != 'q';
}
-static void print_welcome(void) {
+static void print_welcome(int rfd) {
_cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL;
static bool done = false;
const char *pn, *ac;
int r;
+ assert(rfd >= 0);
+
if (!arg_welcome)
return;
if (done)
return;
- r = parse_os_release(
- arg_root,
- "PRETTY_NAME", &pretty_name,
- "NAME", &os_name,
- "ANSI_COLOR", &ansi_color);
+ r = parse_os_release_at(rfd,
+ "PRETTY_NAME", &pretty_name,
+ "NAME", &os_name,
+ "ANSI_COLOR", &ansi_color);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
"Failed to read os-release file, ignoring: %m");
}
}
-static bool locale_is_ok(const char *name) {
+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 */
+ }
- if (arg_root)
- return locale_is_valid(name);
+ return arg_force; /* exists, but if --force was given we should still configure the file. */
+}
+static bool locale_is_installed_bool(const char *name) {
return locale_is_installed(name) > 0;
}
-static int prompt_locale(void) {
+static bool locale_is_ok(int rfd, const char *name) {
+ assert(rfd >= 0);
+
+ return dir_fd_is_root(rfd) ? locale_is_installed_bool(name) : locale_is_valid(name);
+}
+
+static int prompt_locale(int rfd) {
_cleanup_strv_free_ char **locales = NULL;
bool acquired_from_creds = false;
int r;
+ assert(rfd >= 0);
+
if (arg_locale || arg_locale_messages)
return 0;
/* Not setting arg_locale_message here, since it defaults to LANG anyway */
}
} else {
- print_welcome();
+ bool (*is_valid)(const char *name) = dir_fd_is_root(rfd) ? locale_is_installed_bool
+ : locale_is_valid;
+
+ print_welcome(rfd);
r = prompt_loop("Please enter system locale name or number",
- locales, 60, locale_is_ok, &arg_locale);
+ locales, 60, is_valid, &arg_locale);
if (r < 0)
return r;
return 0;
r = prompt_loop("Please enter system message locale name or number",
- locales, 60, locale_is_ok, &arg_locale_messages);
+ locales, 60, is_valid, &arg_locale_messages);
if (r < 0)
return r;
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");
- if (arg_copy_locale && arg_root) {
+ 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;
- (void) mkdir_parents(etc_localeconf, 0755);
- r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, 0, COPY_REFLINK);
+ 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_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;
}
}
- r = prompt_locale();
+ r = prompt_locale(rfd);
if (r < 0)
return r;
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;
}
-static int prompt_keymap(void) {
+static int prompt_keymap(int rfd) {
_cleanup_strv_free_ char **kmaps = NULL;
int r;
+ assert(rfd >= 0);
+
if (arg_keymap)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to read keymaps: %m");
- print_welcome();
+ print_welcome(rfd);
return prompt_loop("Please enter system keymap name or number",
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);
+
+ 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");
- if (arg_copy_keymap && arg_root) {
+ r = should_configure(pfd, f);
+ if (r == 0)
+ log_debug("Found /etc/vconsole.conf, assuming console has been configured.");
+ if (r <= 0)
+ return r;
- (void) mkdir_parents(etc_vconsoleconf, 0755);
- r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, 0, COPY_REFLINK);
+ 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;
}
}
- r = prompt_keymap();
+ r = prompt_keymap(rfd);
if (r == -ENOENT)
return 0; /* don't fail if no keymaps are installed */
if (r < 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 timezone_is_valid(name, LOG_ERR);
}
-static int prompt_timezone(void) {
+static int prompt_timezone(int rfd) {
_cleanup_strv_free_ char **zones = NULL;
int r;
+ assert(rfd >= 0);
+
if (arg_timezone)
return 0;
if (r < 0)
return log_error_errno(r, "Cannot query timezone list: %m");
- print_welcome();
+ print_welcome(rfd);
r = prompt_loop("Please enter timezone name or number",
zones, 30, timezone_is_valid_log_error, &arg_timezone);
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 = 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", &p);
+ 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;
}
}
- r = prompt_timezone();
+ r = prompt_timezone(rfd);
if (r < 0)
return r;
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;
}
-static int prompt_hostname(void) {
+static int prompt_hostname(int rfd) {
int r;
+ assert(rfd >= 0);
+
if (arg_hostname)
return 0;
return 0;
}
- print_welcome();
+ print_welcome(rfd);
putchar('\n');
for (;;) {
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 = prompt_hostname();
+ 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(rfd);
if (r < 0)
return r;
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;
}
-static int prompt_root_password(void) {
+static int prompt_root_password(int rfd) {
const char *msg1, *msg2;
int r;
+ assert(rfd >= 0);
+
if (arg_root_password)
return 0;
return 0;
}
- print_welcome();
+ print_welcome(rfd);
putchar('\n');
msg1 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip):");
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_symlinks(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;
+ assert(rfd >= 0);
+
if (arg_root_shell)
return 0;
return 0;
}
- print_welcome();
+ print_welcome(rfd);
putchar('\n');
for (;;) {
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 rfd, 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;
.pw_gid = 0,
.pw_gecos = (char *) "Super User",
.pw_dir = (char *) "/root",
- .pw_shell = (char *) (shell ?: default_root_shell(arg_root)),
+ .pw_shell = (char *) (shell ?: default_root_shell_at(rfd)),
};
if (errno != ENOENT)
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;
+
+ assert(rfd >= 0);
- etc_passwd = prefix_roota(arg_root, "/etc/passwd");
- etc_shadow = prefix_roota(arg_root, "/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");
- 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);
+ /* 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;
arg_root_password_is_hashed = true;
}
- r = prompt_root_password();
+ r = prompt_root_password(rfd);
if (r < 0)
return r;
else
password = hashed_password = PASSWORD_LOCKED_AND_INVALID;
- r = write_root_passwd(etc_passwd, password, arg_root_shell);
+ r = write_root_passwd(rfd, 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;
}
" -h --help Show this help\n"
" --version Show package version\n"
" --root=PATH Operate on an alternate filesystem root\n"
- " --image=PATH Operate on an alternate filesystem image\n"
+ " --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
" --locale=LOCALE Set primary locale (LANG=)\n"
" --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
" --keymap=KEYMAP Set keymap\n"
" --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_VERSION = 0x100,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_LOCALE,
ARG_LOCALE_MESSAGES,
ARG_KEYMAP,
ARG_FORCE,
ARG_DELETE_ROOT_PASSWORD,
ARG_WELCOME,
+ ARG_RESET,
};
static const struct option options[] = {
{ "version", no_argument, NULL, ARG_VERSION },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "locale", required_argument, NULL, ARG_LOCALE },
{ "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
{ "keymap", required_argument, NULL, ARG_KEYMAP },
{ "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 },
{}
};
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_LOCALE:
r = free_and_strdup(&arg_locale, optarg);
if (r < 0)
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;
assert_not_reached();
}
- /* We check if the specified locale strings are valid down here, so that we can take --root= into
- * account when looking for the locale files. */
-
- if (arg_locale && !locale_is_ok(arg_locale))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
- if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
-
if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--delete-root-password cannot be combined with other root password options");
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);
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |
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));
}
- r = process_locale();
+ LOG_SET_PREFIX(arg_image ?: arg_root);
+
+ /* We check these conditions here instead of in parse_argv() so that we can take the root directory
+ * into account. */
+
+ if (arg_locale && !locale_is_ok(rfd, arg_locale))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
+ if (arg_locale_messages && !locale_is_ok(rfd, arg_locale_messages))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
+
+ if (arg_root_shell) {
+ r = find_shell(rfd, arg_root_shell);
+ if (r < 0)
+ return r;
+ }
+
+ 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;
} else
dash_c[0] = 0;
- cmdline[i++] = "/sbin/fsck";
+ cmdline[i++] = "fsck";
cmdline[i++] = arg_repair;
cmdline[i++] = "-T";
cmdline[i++] = device;
cmdline[i++] = NULL;
- execv(cmdline[0], (char**) cmdline);
+ execvp(cmdline[0], (char**) cmdline);
_exit(FSCK_OPERATIONAL_ERROR);
}
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-locator.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "efi-loader.h"
#include "env-util.h"
#include "fd-util.h"
}
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)) {
* where a symlink refers to another mount target; this works assuming the sub-mountpoint
* target is the final directory.
*
- * FIXME: when chase_symlinks() learns to chase non-existent paths, use this here and
+ * FIXME: when chase() learns to chase non-existent paths, use this here and
* drop the prefixing with /sysroot on error below.
*/
- k = chase_symlinks(where, initrd ? "/sysroot" : NULL,
- CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
- &canonical_where, NULL);
+ k = chase(where, initrd ? "/sysroot" : NULL, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
+ &canonical_where, NULL);
if (k < 0) {
/* If we can't canonicalize, continue as if it wasn't a symlink */
log_debug_errno(k, "Failed to read symlink target for %s, using as-is: %m", where);
/* OK, so we didn't write anything out for /sysusr/usr/ nor /sysroot/usr/. In this case, let's make
* sure that initrd-usr-fs.target is at least ordered after sysroot.mount so that services that order
- * themselves get the guarantee that /usr/ is definitely mounted somewhere. */
+ * themselves after it get the guarantee that /usr/ is definitely mounted somewhere. */
return generator_add_symlink(
arg_dest,
#include <stdint.h>
-#include "macro.h"
-
/* Note: log2(0) == log2(1) == 0 here and below. */
#define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
#endif
}
+#define popcount(n) \
+ _Generic((n), \
+ unsigned char: __builtin_popcount(n), \
+ unsigned short: __builtin_popcount(n), \
+ unsigned: __builtin_popcount(n), \
+ unsigned long: __builtin_popcountl(n), \
+ unsigned long long: __builtin_popcountll(n))
+
#define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0)
#define NONCONST_LOG2U(x) ({ \
unsigned _x = (x); \
#endif
#include <limits.h>
+#include <stdalign.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define _align_(x) __attribute__((__aligned__(x)))
-#define _alignas_(x) __attribute__((__aligned__(__alignof__(x))))
+#define _alignas_(x) __attribute__((__aligned__(alignof(x))))
#define _alignptr_ __attribute__((__aligned__(sizeof(void *))))
#define _cleanup_(x) __attribute__((__cleanup__(x)))
#define _const_ __attribute__((__const__))
#define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p)))
/* Checks if the specified pointer is aligned as appropriate for the specific type */
-#define IS_ALIGNED16(p) (((uintptr_t) p) % __alignof__(uint16_t) == 0)
-#define IS_ALIGNED32(p) (((uintptr_t) p) % __alignof__(uint32_t) == 0)
-#define IS_ALIGNED64(p) (((uintptr_t) p) % __alignof__(uint64_t) == 0)
+#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0)
+#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0)
+#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0)
/* Same as ALIGN_TO but callable in constant contexts. */
#define CONST_ALIGN_TO(l, ali) \
#define CAST_ALIGN_PTR(t, p) \
({ \
const void *_p = (p); \
- assert(((uintptr_t) _p) % __alignof__(t) == 0); \
+ assert(((uintptr_t) _p) % alignof(t) == 0); \
(t *) _p; \
})
}
size_t csize;
- r = compress_blob_explicit(alg, h->data, data_len, buf, size, &csize);
+ r = compress_blob(alg, h->data, data_len, buf, size, &csize);
if (r < 0) {
log_error_errno(r, "Compression failed: %m");
return 0;
#include "fstab-util.h"
#include "generator.h"
#include "gpt.h"
+#include "image-policy.h"
#include "initrd-util.h"
#include "mkdir.h"
#include "mountpoint-util.h"
static char *arg_root_fstype = NULL;
static char *arg_root_options = NULL;
static int arg_root_rw = -1;
+static ImagePolicy *arg_image_policy = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_fstype, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
r = dissect_loop_device(
loop,
- NULL, NULL,
+ /* verity= */ NULL,
+ /* mount_options= */ NULL,
+ arg_image_policy ?: &image_policy_host,
DISSECT_IMAGE_GPT_ONLY|
DISSECT_IMAGE_USR_NO_ROOT|
- DISSECT_IMAGE_DISKSEQ_DEVNODE,
+ DISSECT_IMAGE_DISKSEQ_DEVNODE|
+ DISSECT_IMAGE_ALLOW_EMPTY,
/* NB! Unlike most other places where we dissect block devices we do not use
* DISSECT_IMAGE_ADD_PARTITION_DEVICES here: we want that the kernel finds the
* devices, and udev probes them before we mount them via .mount units much later
* on. And thus we also don't set DISSECT_IMAGE_PIN_PARTITION_DEVICES here, because
* we don't actually mount anything immediately. */
&m);
- if (r == -ENOPKG) {
- log_debug_errno(r, "No suitable partition table found on block device %s, ignoring.", devname);
- return 0;
+ if (r < 0) {
+ bool ok = r == -ENOPKG;
+ dissect_log_error(ok ? LOG_DEBUG : LOG_ERR, r, devname, NULL);
+ return ok ? 0 : r;
}
- if (r < 0)
- return log_error_errno(r, "Failed to dissect partition table of block device %s: %m", devname);
if (m->partitions[PARTITION_SWAP].found) {
k = add_partition_swap(m->partitions + PARTITION_SWAP);
arg_root_rw = true;
else if (proc_cmdline_key_streq(key, "ro") && !value)
arg_root_rw = false;
+ else if (proc_cmdline_key_streq(key, "systemd.image_policy"))
+ return parse_image_policy_argument(optarg, &arg_image_policy);
return 0;
}
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;
cmsg->cmsg_type == SCM_CREDENTIALS &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
assert(!sender);
- sender = (struct ucred*) CMSG_DATA(cmsg);
+ sender = CMSG_TYPED_DATA(cmsg, struct ucred);
}
if (cmsg->cmsg_level == SOL_SOCKET &&
}
assert(passed_fd < 0);
- passed_fd = *(int*) CMSG_DATA(cmsg);
+ passed_fd = *CMSG_TYPED_DATA(cmsg, int);
}
}
return r;
if (r == 0) {
/* Child */
- execl("/sbin/fsck", "/sbin/fsck", "-aTl", node, NULL);
+ execlp("fsck", "fsck", "-aTl", node, NULL);
log_open();
log_error_errno(errno, "Failed to execute fsck: %m");
_exit(FSCK_OPERATIONAL_ERROR);
exclude + 1, exclude + 1, next_start - exclude - 1);
}
-static int make_userns(uid_t stored_uid, uid_t exposed_uid) {
+static int make_home_userns(uid_t stored_uid, uid_t exposed_uid) {
_cleanup_free_ char *text = NULL;
_cleanup_close_ int userns_fd = -EBADF;
int r;
return log_error_errno(errno, "Failed to open tree of home directory: %m");
}
- userns_fd = make_userns(stored_uid, exposed_uid);
+ userns_fd = make_home_userns(stored_uid, exposed_uid);
if (userns_fd < 0)
return userns_fd;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *generic_field = NULL, *json_copy = NULL;
- r = pam_acquire_bus_connection(handle, &bus);
+ r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
if (r != PAM_SUCCESS)
return r;
if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0)
return PAM_SUCCESS;
- r = pam_acquire_bus_connection(handle, &bus);
+ r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
if (r != PAM_SUCCESS)
return r;
r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug);
if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
- return PAM_SUCCESS;
+ goto success; /* Need to free the bus resource, as acquire_home() takes a reference. */
if (r != PAM_SUCCESS)
return r;
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
+success:
/* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
* not going to process the bus connection in that time, so let's better close before the daemon
* kicks us off because we are not processing anything. */
- (void) pam_release_bus_connection(handle);
+ (void) pam_release_bus_connection(handle, "pam-systemd-home");
return PAM_SUCCESS;
}
if (r != PAM_SUCCESS)
return r;
- r = pam_acquire_bus_connection(handle, &bus);
+ r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
if (r != PAM_SUCCESS)
return r;
if (debug)
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
- r = pam_acquire_bus_connection(handle, &bus);
+ r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
if (r != PAM_SUCCESS)
return r;
};
struct ucred *ucred;
Manager *m = userdata;
- char *p, *e;
Transfer *t;
ssize_t n;
+ char *p;
int r;
n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
buf[n] = 0;
- p = startswith(buf, "X_IMPORT_PROGRESS=");
- if (!p) {
- p = strstr(buf, "\nX_IMPORT_PROGRESS=");
- if (!p)
- return 0;
-
- p += 19;
- }
+ p = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
+ if (!p)
+ return 0;
- e = strchrnul(p, '\n');
- *e = 0;
+ truncate_nl(p);
r = parse_percent(p);
if (r < 0) {
*path,
local,
0644,
- 0, 0,
COPY_REFLINK |
(FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) |
(FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0));
i->settings_path,
local_settings,
0664,
- 0, 0,
COPY_REFLINK |
(FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) |
(FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0));
static char** arg_gnutls_log = NULL;
static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID;
-static const char* arg_output = NULL;
+static char *arg_output = NULL;
static char *arg_key = NULL;
static char *arg_cert = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_cert, freep);
STATIC_DESTRUCTOR_REGISTER(arg_trust, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_output, freep);
static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
[JOURNAL_WRITE_SPLIT_NONE] = "none",
create output as expected. */
r = journal_remote_get_writer(s, NULL, &s->_single_writer);
if (r < 0)
- return r;
+ return log_warning_errno(r, "Failed to get writer: %m");
}
return 0;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"cannot use --output/-o more than once");
- arg_output = optarg;
+ r = parse_path_argument(optarg, /* suppress_root = */ false, &arg_output);
+ if (r < 0)
+ return r;
break;
case ARG_SPLIT_MODE:
return r;
}
-Writer* writer_new(RemoteServer *server) {
+int writer_new(RemoteServer *server, Writer **ret) {
_cleanup_(writer_unrefp) Writer *w = NULL;
int r;
+ assert(server);
+ assert(ret);
+
w = new0(Writer, 1);
if (!w)
- return NULL;
+ return -ENOMEM;
w->metrics = server->metrics;
w->mmap = mmap_cache_new();
if (!w->mmap)
- return NULL;
+ return -ENOMEM;
w->n_ref = 1;
w->server = server;
if (is_dir(server->output, /* follow = */ true) > 0) {
w->output = strdup(server->output);
if (!w->output)
- return NULL;
+ return -ENOMEM;
} else {
r = path_extract_directory(server->output, &w->output);
- if (r < 0) {
- log_error_errno(r, "Failed to find directory of file \"%s\": %m", server->output);
- return NULL;
- }
+ if (r < 0)
+ return r;
}
- return TAKE_PTR(w);
+ *ret = TAKE_PTR(w);
+ return 0;
}
static Writer* writer_free(Writer *w) {
unsigned n_ref;
} Writer;
-Writer* writer_new(RemoteServer* server);
+int writer_new(RemoteServer *server, Writer **ret);
Writer* writer_ref(Writer *w);
Writer* writer_unref(Writer *w);
if (w)
writer_ref(w);
else {
- w = writer_new(s);
- if (!w)
- return log_oom();
+ r = writer_new(s, &w);
+ if (r < 0)
+ return r;
if (s->split_mode == JOURNAL_WRITE_SPLIT_HOST) {
w->hashmap_key = strdup(key);
if (!w->hashmap_key)
- return log_oom();
+ return -ENOMEM;
}
r = open_output(s, w, host);
}
*writer = TAKE_PTR(w);
-
return 0;
}
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0)
switch (c) {
#include "bus-error.h"
#include "bus-util.h"
#include "catalog.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "chattr-util.h"
#include "constants.h"
+#include "devnum-util.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "fileio.h"
static const char *arg_pattern = NULL;
static pcre2_code *arg_compiled_pattern = NULL;
static PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
+ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static enum {
ACTION_SHOW,
r = sd_device_new_from_stat_rdev(&device, &st);
if (r < 0)
- return log_error_errno(r, "Failed to get device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev));
+ return log_error_errno(r, "Failed to get device from devnum " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(st.st_rdev));
for (d = device; d; ) {
_cleanup_free_ char *match = NULL;
if (r < 0)
return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode);
- r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev));
+ r = asprintf(&match1, "_KERNEL_DEVICE=%c" DEVNUM_FORMAT_STR, S_ISBLK(st.st_mode) ? 'b' : 'c', DEVNUM_FORMAT_VAL(st.st_rdev));
if (r < 0)
return log_oom();
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
" --file=PATH Show journal file\n"
- " --root=ROOT Operate on files below a root directory\n"
- " --image=IMAGE Operate on files in filesystem image\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ " --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
" --namespace=NAMESPACE Show journal data from specified journal namespace\n"
"\n%3$sFiltering Options:%4$s\n"
" -S --since=DATE Show entries not older than the specified date\n"
ARG_SYSTEM,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_HEADER,
ARG_FACILITY,
ARG_SETUP_KEYS,
{ "file", required_argument, NULL, ARG_FILE },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "header", no_argument, NULL, ARG_HEADER },
{ "identifier", required_argument, NULL, 't' },
{ "priority", required_argument, NULL, 'p' },
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case 'c':
arg_cursor = optarg;
break;
break;
}
-
case '?':
return -EINVAL;
return r;
/* When --grep is used along with --lines, we don't know how many lines we can print.
- * So we search backwards and count until enough lines have been printed or we hit the head. */
- if (arg_lines >= 0)
+ * So we search backwards and count until enough lines have been printed or we hit the head.
+ * An exception is that --follow might set arg_lines, so let's not imply --reverse
+ * if that is specified. */
+ if (arg_lines >= 0 && !arg_follow)
arg_reverse = true;
}
_cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
struct stat st;
- r = chase_symlinks(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL);
+ r = chase(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL);
if (r < 0)
return log_error_errno(r, "Couldn't canonicalize path: %m");
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |
if (!f)
return;
- if (journal_file_rotate_suggested(f->file, s->max_file_usec, LOG_INFO)) {
- log_ratelimit_info(JOURNAL_LOG_RATELIMIT,
- "%s: Journal header limits reached or header out-of-date, rotating.",
- f->file->path);
+ if (journal_file_rotate_suggested(f->file, s->max_file_usec, LOG_DEBUG)) {
+ log_debug("%s: Journal header limits reached or header out-of-date, rotating.",
+ f->file->path);
rotate = true;
}
}
size_t label_len = 0, m;
Server *s = ASSERT_PTR(userdata);
struct ucred *ucred = NULL;
- struct timeval *tv = NULL;
+ struct timeval tv_buf, *tv = NULL;
struct cmsghdr *cmsg;
char *label = NULL;
struct iovec iovec;
cmsg->cmsg_type == SCM_CREDENTIALS &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
assert(!ucred);
- ucred = (struct ucred*) CMSG_DATA(cmsg);
+ ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
} else if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_SECURITY) {
assert(!label);
- label = (char*) CMSG_DATA(cmsg);
+ label = CMSG_TYPED_DATA(cmsg, char);
label_len = cmsg->cmsg_len - CMSG_LEN(0);
} else if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_type == SCM_TIMESTAMP &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) {
assert(!tv);
- tv = (struct timeval*) CMSG_DATA(cmsg);
+ tv = memcpy(&tv_buf, CMSG_DATA(cmsg), sizeof(struct timeval));
} else if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
assert(!fds);
- fds = (int*) CMSG_DATA(cmsg);
+ fds = CMSG_TYPED_DATA(cmsg, int);
n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
}
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
log_debug_errno(r, "Failed to re-enable copy-on-write for %s: %m, rewriting file", f->file->path);
- r = copy_file_atomic(FORMAT_PROC_FD_PATH(f->file->fd), f->file->path, f->file->mode,
- 0,
- FS_NOCOW_FL,
- COPY_REPLACE | COPY_FSYNC | COPY_HOLES | COPY_ALL_XATTRS);
+ r = copy_file_atomic_full(FORMAT_PROC_FD_PATH(f->file->fd), f->file->path, f->file->mode,
+ 0,
+ FS_NOCOW_FL,
+ COPY_REPLACE | COPY_FSYNC | COPY_HOLES | COPY_ALL_XATTRS,
+ NULL, NULL);
if (r < 0) {
log_debug_errno(r, "Failed to rewrite %s: %m", f->file->path);
continue;
{
'sources' : files('test-journal-verify.c'),
'base' : test_journal_base,
+ 'timeout' : 90,
},
{
'sources' : files('test-journal.c'),
#include "managed-journal-file.h"
#include "mmap-cache.h"
#include "rm-rf.h"
+#include "strv.h"
#include "terminal-util.h"
#include "tests.h"
m = mmap_cache_new();
assert_se(m != NULL);
- r = journal_file_open(-1, fn, O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), 0666, UINT64_MAX, NULL, m, NULL, &f);
+ r = journal_file_open(
+ /* fd= */ -1,
+ fn,
+ O_RDONLY,
+ JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+ 0666,
+ /* compress_threshold_bytes= */ UINT64_MAX,
+ /* metrics= */ NULL,
+ m,
+ /* template= */ NULL,
+ &f);
if (r < 0)
return r;
return r;
}
-static int run_test(int argc, char *argv[]) {
+static int run_test(const char *verification_key, ssize_t max_iterations) {
_cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
char t[] = "/var/tmp/journal-XXXXXX";
- unsigned n;
+ struct stat st;
JournalFile *f;
ManagedJournalFile *df;
- const char *verification_key = argv[1];
usec_t from = 0, to = 0, total = 0;
- struct stat st;
- uint64_t p;
+ uint64_t start, end;
+ int r;
m = mmap_cache_new();
assert_se(m != NULL);
assert_se(chdir(t) >= 0);
(void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
- log_info("Generating...");
-
- assert_se(managed_journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), 0666, UINT64_MAX, NULL, m, NULL, NULL, &df) == 0);
-
- for (n = 0; n < N_ENTRIES; n++) {
+ log_info("Generating a test journal");
+
+ assert_se(managed_journal_file_open(
+ /* fd= */ -1,
+ "test.journal",
+ O_RDWR|O_CREAT,
+ JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+ 0666,
+ /* compress_threshold_bytes= */ UINT64_MAX,
+ /* metrics= */ NULL,
+ m,
+ /* deferred_closes= */ NULL,
+ /* template= */ NULL,
+ &df) == 0);
+
+ for (size_t n = 0; n < N_ENTRIES; n++) {
+ _cleanup_free_ char *test = NULL;
struct iovec iovec;
struct dual_timestamp ts;
- char *test;
dual_timestamp_get(&ts);
-
assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE));
-
iovec = IOVEC_MAKE_STRING(test);
-
- assert_se(journal_file_append_entry(df->file, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0);
-
- free(test);
+ assert_se(journal_file_append_entry(
+ df->file,
+ &ts,
+ /* boot_id= */ NULL,
+ &iovec,
+ /* n_iovec= */ 1,
+ /* seqnum= */ NULL,
+ /* seqnum_id= */ NULL,
+ /* ret_object= */ NULL,
+ /* ret_offset= */ NULL) == 0);
}
(void) managed_journal_file_close(df);
- log_info("Verifying...");
-
- assert_se(journal_file_open(-1, "test.journal", O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL: 0), 0666, UINT64_MAX, NULL, m, NULL, &f) == 0);
- /* journal_file_print_header(f); */
+ log_info("Verifying with key: %s", strna(verification_key));
+
+ assert_se(journal_file_open(
+ /* fd= */ -1,
+ "test.journal",
+ O_RDONLY,
+ JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+ 0666,
+ /* compress_threshold_bytes= */ UINT64_MAX,
+ /* metrics= */ NULL,
+ m,
+ /* template= */ NULL,
+ &f) == 0);
+ journal_file_print_header(f);
journal_file_dump(f);
assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0);
FORMAT_TIMESPAN(total > to ? total - to : 0, 0));
(void) journal_file_close(f);
+ assert_se(stat("test.journal", &st) >= 0);
- if (verification_key) {
- log_info("Toggling bits...");
-
- assert_se(stat("test.journal", &st) >= 0);
+ start = 38448 * 8 + 0;
+ end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations;
+ log_info("Toggling bits %"PRIu64 " to %"PRIu64, start, end);
- for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) {
- bit_toggle("test.journal", p);
+ for (uint64_t p = start; p < end; p++) {
+ bit_toggle("test.journal", p);
+ if (max_iterations < 0)
log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8);
- if (raw_verify("test.journal", verification_key) >= 0)
- log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8);
+ r = raw_verify("test.journal", verification_key);
+ /* Suppress the notice when running in the limited (CI) mode */
+ if (verification_key && max_iterations < 0 && r >= 0)
+ log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8);
- bit_toggle("test.journal", p);
- }
+ bit_toggle("test.journal", p);
}
- log_info("Exiting...");
-
assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
return 0;
}
int main(int argc, char *argv[]) {
+ const char *verification_key = NULL;
+ int max_iterations = 512;
+
+ if (argc > 1) {
+ /* Don't limit the number of iterations when the verification key
+ * is provided on the command line, we want to do that only in CIs */
+ verification_key = argv[1];
+ max_iterations = -1;
+ }
+
assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
- run_test(argc, argv);
+ run_test(verification_key, max_iterations);
assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
- run_test(argc, argv);
+ run_test(verification_key, max_iterations);
+
+#if HAVE_GCRYPT
+ /* If we're running without any arguments and we're compiled with gcrypt
+ * check the journal verification stuff with a valid key as well */
+ if (argc <= 1) {
+ verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900";
+
+ assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
+ run_test(verification_key, max_iterations);
+
+ assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
+ run_test(verification_key, max_iterations);
+ }
+#endif
return 0;
}
#layout=bls|other|...
#initrd_generator=dracut|...
+#uki_generator=ukify|...
shift
fi
-# These two settings are only settable via install.conf
+# These three settings are only settable via install.conf
layout=
initrd_generator=
+uki_generator=
# These two settings can be inherited from the environment
_MACHINE_ID_SAVED="$MACHINE_ID"
_BOOT_ROOT_SAVED="$BOOT_ROOT"
[ -n "$layout" ] && log_verbose "$install_conf configures layout=$layout"
[ -n "$initrd_generator" ] && \
log_verbose "$install_conf configures initrd_generator=$initrd_generator"
+[ -n "$uki_generator" ] && \
+ log_verbose "$install_conf configures uki_generator=$uki_generator"
if [ -n "$_MACHINE_ID_SAVED" ]; then
MACHINE_ID="$_MACHINE_ID_SAVED"
export KERNEL_INSTALL_BOOT_ROOT="$BOOT_ROOT"
export KERNEL_INSTALL_LAYOUT="$layout"
export KERNEL_INSTALL_INITRD_GENERATOR="$initrd_generator"
+export KERNEL_INSTALL_UKI_GENERATOR="$uki_generator"
export KERNEL_INSTALL_STAGING_AREA
MAKE_ENTRY_DIR_ABS=0
echo "KERNEL_INSTALL_BOOT_ROOT: $KERNEL_INSTALL_BOOT_ROOT"
echo "KERNEL_INSTALL_LAYOUT: $KERNEL_INSTALL_LAYOUT"
echo "KERNEL_INSTALL_INITRD_GENERATOR: $KERNEL_INSTALL_INITRD_GENERATOR"
+ echo "KERNEL_INSTALL_UKI_GENERATOR: $KERNEL_INSTALL_UKI_GENERATOR"
echo "ENTRY_DIR_ABS: $KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN/\$KERNEL_VERSION"
# Assert that ENTRY_DIR_ABS actually matches what we are printing here
#!/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"
if (cmsg->cmsg_level == SOL_IPV6 &&
cmsg->cmsg_type == IPV6_HOPLIMIT &&
cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
- int hops = *(int*) CMSG_DATA(cmsg);
+ int hops = *CMSG_TYPED_DATA(cmsg, int);
if (hops != 255)
return -EMULTIHOP;
}
if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SO_TIMESTAMP &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
- triple_timestamp_from_realtime(&t, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
+ cmsg->cmsg_type == SCM_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) {
+ struct timeval *tv = memcpy(&(struct timeval) {}, CMSG_DATA(cmsg), sizeof(struct timeval));
+ triple_timestamp_from_realtime(&t, timeval_load(tv));
+ }
}
if (!triple_timestamp_is_set(&t))
cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata)));
if (cmsg) {
- struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg);
+ struct tpacket_auxdata *aux = CMSG_TYPED_DATA(cmsg, struct tpacket_auxdata);
checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
}
rather than binding the socket. This will be mostly useful
when we gain support for arbitrary number of server addresses
*/
- pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
+ pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
assert(pktinfo);
pktinfo->ipi_ifindex = server->ifindex;
return true;
}
+static int server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req, DHCPLease **ret) {
+ DHCPLease *static_lease;
+ _cleanup_free_ uint8_t *data = NULL;
+
+ assert(server);
+ assert(req);
+ assert(ret);
+
+ static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+ if (static_lease) {
+ *ret = static_lease;
+ return 0;
+ }
+
+ /* when no lease is found based on the client id fall back to chaddr */
+ data = new(uint8_t, req->message->hlen + 1);
+ if (!data)
+ return -ENOMEM;
+
+ /* set client id type to 1: Ethernet Link-Layer (RFC 2132) */
+ data[0] = 0x01;
+ memcpy(data + 1, req->message->chaddr, req->message->hlen);
+
+ static_lease = hashmap_get(server->static_leases_by_client_id,
+ &(DHCPClientId) {
+ .length = req->message->hlen + 1,
+ .data = data,
+ });
+
+ *ret = static_lease;
+
+ return 0;
+}
+
#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length) {
return r;
existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
- static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+ r = server_get_static_lease(server, req, &static_lease);
+ if (r < 0)
+ return r;
switch (type) {
.msg_control = &control,
.msg_controllen = sizeof(control),
};
- struct cmsghdr *cmsg;
ssize_t datagram_size, len;
int r;
if ((size_t) len < sizeof(DHCPMessage))
return 0;
- CMSG_FOREACH(cmsg, &msg)
- if (cmsg->cmsg_level == IPPROTO_IP &&
- cmsg->cmsg_type == IP_PKTINFO &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
- struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
-
- /* TODO figure out if this can be done as a filter on
- * the socket, like for IPv6 */
- if (server->ifindex != info->ipi_ifindex)
- return 0;
-
- break;
- }
+ /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
+ struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo);
+ if (info && info->ipi_ifindex != server->ifindex)
+ return 0;
if (sd_dhcp_server_is_in_relay_mode(server)) {
r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen);
.msg_control = &control,
.msg_controllen = sizeof(control),
};
- struct cmsghdr *cmsg;
triple_timestamp t = {};
_cleanup_free_ DHCP6Message *message = NULL;
struct in6_addr *server_address = NULL;
server_address = &sa.in6.sin6_addr;
}
- CMSG_FOREACH(cmsg, &msg) {
- if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SO_TIMESTAMP &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
- triple_timestamp_from_realtime(&t, timeval_load(CMSG_TYPED_DATA(cmsg, struct timeval)));
- }
+ struct timeval *tv = CMSG_FIND_AND_COPY_DATA(&msg, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
+ if (tv)
+ triple_timestamp_from_realtime(&t, timeval_load(tv));
if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
return 0;
sd_session_get_username;
sd_session_get_start_time;
sd_uid_get_login_time;
+ sd_pid_notifyf_with_fds;
} LIBSYSTEMD_253;
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"
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(unique, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!bus->bus_client)
return -EINVAL;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(name, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
r = validate_request_name_parameters(bus, name, flags, ¶m);
if (r < 0)
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(name, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
r = validate_request_name_parameters(bus, name, flags, ¶m);
if (r < 0)
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(name, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
r = validate_release_name_parameters(bus, name);
if (r < 0)
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(name, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
r = validate_release_name_parameters(bus, name);
if (r < 0)
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(acquired || activatable, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!bus->bus_client)
return -EINVAL;
assert_return(name, -EINVAL);
assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
assert_return(mask == 0 || creds, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(service_name_is_valid(name), -EINVAL);
if (!bus->bus_client)
"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 */
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
assert_return(ret, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(name, -EINVAL);
assert_return(machine, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(service_name_is_valid(name), -EINVAL);
if (!bus->bus_client)
_public_ int sd_bus_message_send(sd_bus_message *reply) {
assert_return(reply, -EINVAL);
assert_return(reply->bus, -EINVAL);
- assert_return(!bus_pid_changed(reply->bus), -ECHILD);
+ assert_return(!bus_origin_changed(reply->bus), -ECHILD);
return sd_bus_send(reply->bus, reply, NULL);
}
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
bus_assert_return(bus, -EINVAL, error);
bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
assert_return(call->sealed, -EPERM);
assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
if (!BUS_IS_OPEN(call->bus->state))
return -ENOTCONN;
assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
assert_return(sd_bus_error_is_set(e), -EINVAL);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
if (!BUS_IS_OPEN(call->bus->state))
return -ENOTCONN;
assert_return(call->sealed, -EPERM);
assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
if (!BUS_IS_OPEN(call->bus->state))
return -ENOTCONN;
assert_return(call->sealed, -EPERM);
assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
if (!BUS_IS_OPEN(call->bus->state))
return -ENOTCONN;
assert_return(call->sealed, -EPERM);
assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
if (!BUS_IS_OPEN(call->bus->state))
return -ENOTCONN;
bus_assert_return(member_name_is_valid(member), -EINVAL, error);
bus_assert_return(reply, -EINVAL, error);
bus_assert_return(signature_is_single(type, false), -EINVAL, error);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
bus_assert_return(member_name_is_valid(member), -EINVAL, error);
bus_assert_return(bus_type_is_trivial(type), -EINVAL, error);
bus_assert_return(ptr, -EINVAL, error);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
bus_assert_return(member_name_is_valid(member), -EINVAL, error);
bus_assert_return(ret, -EINVAL, error);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
bus_assert_return(member_name_is_valid(member), -EINVAL, error);
bus_assert_return(ret, -EINVAL, error);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
bus_assert_return(member_name_is_valid(member), -EINVAL, error);
bus_assert_return(signature_is_single(type, false), -EINVAL, error);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
assert_return(call, -EINVAL);
assert_return(call->sealed, -EPERM);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
assert_return(ret, -EINVAL);
if (!BUS_IS_OPEN(call->bus->state))
assert_return(call, -EINVAL);
assert_return(call->sealed, -EPERM);
assert_return(call->bus, -EINVAL);
- assert_return(!bus_pid_changed(call->bus), -ECHILD);
+ assert_return(!bus_origin_changed(call->bus), -ECHILD);
if (!BUS_IS_OPEN(call->bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(!sender || service_name_is_valid(sender), -EINVAL);
assert_return(!path || object_path_is_valid(path), -EINVAL);
assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(!sender || service_name_is_valid(sender), -EINVAL);
assert_return(!path || object_path_is_valid(path), -EINVAL);
assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
#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
};
struct memfd_cache memfd_cache[MEMFD_CACHE_MAX];
unsigned n_memfd_cache;
- pid_t original_pid;
+ uint64_t origin_id;
pid_t busexec_pid;
unsigned iteration_counter;
int bus_rqueue_make_room(sd_bus *bus);
-bool bus_pid_changed(sd_bus *bus);
+bool bus_origin_changed(sd_bus *bus);
char *bus_address_escape(const char *v);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(callback, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
n = bus_node_allocate(bus, path);
if (!n)
assert_return(vtable[0].x.start.element_size == VTABLE_ELEMENT_SIZE_221 ||
vtable[0].x.start.element_size >= VTABLE_ELEMENT_SIZE_242,
-EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(!streq(interface, "org.freedesktop.DBus.Properties") &&
!streq(interface, "org.freedesktop.DBus.Introspectable") &&
!streq(interface, "org.freedesktop.DBus.Peer") &&
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(callback, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
n = bus_node_allocate(bus, path);
if (!n)
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(interface_name_is_valid(interface), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(interface_name_is_valid(interface), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
n = bus_node_allocate(bus, path);
if (!n)
* protocol? Somebody is playing games with
* us. Close them all, and fail */
j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
- close_many((int*) CMSG_DATA(cmsg), j);
+ close_many(CMSG_TYPED_DATA(cmsg, int), j);
return -EIO;
} else
log_debug("Got unexpected auxiliary data with level=%d and type=%d",
* isn't actually enabled? Close them,
* and fail */
- close_many((int*) CMSG_DATA(cmsg), n);
+ close_many(CMSG_TYPED_DATA(cmsg, int), n);
return -EIO;
}
f = reallocarray(bus->fds, bus->n_fds + n, sizeof(int));
if (!f) {
- close_many((int*) CMSG_DATA(cmsg), n);
+ close_many(CMSG_TYPED_DATA(cmsg, int), n);
return -ENOMEM;
}
for (i = 0; i < n; i++)
- f[bus->n_fds++] = fd_move_above_stdio(((int*) CMSG_DATA(cmsg))[i]);
+ f[bus->n_fds++] = fd_move_above_stdio(CMSG_TYPED_DATA(cmsg, int)[i]);
bus->fds = f;
} else
log_debug("Got unexpected auxiliary data with level=%d and type=%d",
#include "memory-util.h"
#include "missing_syscall.h"
#include "missing_threads.h"
+#include "origin-id.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
DEFINE_TRIVIAL_CLEANUP_FUNC(sd_bus*, bus_free);
+DEFINE_ORIGIN_ID_HELPERS(sd_bus, bus);
+
_public_ int sd_bus_new(sd_bus **ret) {
_cleanup_free_ sd_bus *b = NULL;
.message_version = 1,
.creds_mask = SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME,
.accept_fd = true,
- .original_pid = getpid_cached(),
+ .origin_id = origin_id_query(),
.n_groups = SIZE_MAX,
.close_on_exit = true,
.ucred = UCRED_INVALID,
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
assert_return(address, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return free_and_strdup(&bus->address, address);
}
assert_return(bus->state == BUS_UNSET, -EPERM);
assert_return(input_fd >= 0, -EBADF);
assert_return(output_fd >= 0, -EBADF);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->input_fd = input_fd;
bus->output_fd = output_fd;
assert_return(bus->state == BUS_UNSET, -EPERM);
assert_return(path, -EINVAL);
assert_return(!strv_isempty(argv), -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
a = strv_copy(argv);
if (!a)
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
assert_return(!bus->patch_sender, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->bus_client = !!b;
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->is_monitor = !!b;
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->accept_fd = !!b;
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
/* This is not actually supported by any of our transports these days, but we do honour it for synthetic
* replies, and maybe one day classic D-Bus learns this too */
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL);
assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
SET_FLAG(bus->creds_mask, mask, b);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->is_server = !!b;
bus->server_id = server_id;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->anonymous_auth = !!b;
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->trusted = !!b;
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return free_and_strdup(&bus->description, description);
}
_public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->allow_interactive_authorization = !!b;
return 0;
_public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->allow_interactive_authorization;
}
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->watch_bind = !!b;
return 0;
_public_ int sd_bus_get_watch_bind(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->watch_bind;
}
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus->connected_signal = !!b;
return 0;
_public_ int sd_bus_get_connected_signal(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->connected_signal;
}
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state == BUS_UNSET, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
bus_set_state(bus, BUS_OPENING);
return;
if (bus->state == BUS_CLOSED)
return;
- if (bus_pid_changed(bus))
+ if (bus_origin_changed(bus))
return;
/* Don't leave ssh hanging around */
_public_ sd_bus *sd_bus_close_unref(sd_bus *bus) {
if (!bus)
return NULL;
+ if (bus_origin_changed(bus))
+ return NULL;
sd_bus_close(bus);
_public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) {
if (!bus)
return NULL;
+ if (bus_origin_changed(bus))
+ return NULL;
/* Have to do this before flush() to prevent hang */
bus_kill_exec(bus);
bus_set_state(bus, BUS_CLOSING);
}
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus, sd_bus, bus_free);
+/* Define manually so we can add the PID check */
+_public_ sd_bus *sd_bus_ref(sd_bus *bus) {
+ if (!bus)
+ return NULL;
+ if (bus_origin_changed(bus))
+ return NULL;
+
+ bus->n_ref++;
+
+ return bus;
+}
+
+_public_ sd_bus* sd_bus_unref(sd_bus *bus) {
+ if (!bus)
+ return NULL;
+ if (bus_origin_changed(bus))
+ return NULL;
+
+ assert(bus->n_ref > 0);
+ if (--bus->n_ref > 0)
+ return NULL;
+
+ return bus_free(bus);
+}
_public_ int sd_bus_is_open(sd_bus *bus) {
if (!bus)
return 0;
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return BUS_IS_OPEN(bus->state);
}
return 0;
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->state == BUS_RUNNING;
}
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->state != BUS_UNSET, -ENOTCONN);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->is_monitor)
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(id, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
r = bus_ensure_running(bus);
if (r < 0)
assert_return(bus = bus_resolve(bus), -ENOPKG);
else
assert_return(bus = m->bus, -ENOTCONN);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus = bus_resolve(bus), -ENOPKG);
else
assert_return(bus = m->bus, -ENOTCONN);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus = bus_resolve(bus), -ENOPKG);
else
assert_return(bus = m->bus, -ENOTCONN);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
assert_return(bus = bus_resolve(bus), -ENOPKG);
else
assert_return(bus = m->bus, -ENOTCONN);
- bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+ bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
if (!BUS_IS_OPEN(bus->state)) {
r = -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(bus->input_fd == bus->output_fd, -EPERM);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->state == BUS_CLOSED)
return -ENOTCONN;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
switch (bus->state) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(timeout_usec, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING)
return -ENOTCONN;
}
static int process_message(sd_bus *bus, sd_bus_message *m) {
- _unused_ _cleanup_(log_context_freep) LogContext *c = NULL;
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = NULL;
int r;
assert(bus);
bus->iteration_counter++;
if (log_context_enabled())
- c = log_context_new_consume(bus_message_make_log_fields(m));
+ c = log_context_new_strv_consume(bus_message_make_log_fields(m));
log_debug_bus_message(m);
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
/* We don't allow recursively invoking sd_bus_process(). */
assert_return(!bus->current_message, -EBUSY);
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->state == BUS_CLOSING)
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->state == BUS_CLOSING)
return 0;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(callback, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata);
if (!s)
struct bus_match_component *components = NULL;
size_t n_components = 0;
- sd_bus_slot *s = NULL;
- int r = 0;
+ _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL;
+ int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(match, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
+
+ CLEANUP_ARRAY(components, n_components, bus_match_parse_free);
r = bus_match_parse(match, &components, &n_components);
if (r < 0)
- goto finish;
+ return r;
s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata);
- if (!s) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s)
+ return -ENOMEM;
s->match_callback.callback = callback;
s->match_callback.install_callback = install_callback;
/* We store the original match string, so that we can use it to remove the match again. */
s->match_callback.match_string = strdup(match);
- if (!s->match_callback.match_string) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!s->match_callback.match_string)
+ return -ENOMEM;
if (asynchronous) {
r = bus_add_match_internal_async(bus,
s);
if (r < 0)
- goto finish;
+ return r;
/* Make the slot of the match call floating now. We need the reference, but we don't
* want that this match pins the bus object, hence we first create it non-floating, but
} else
r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
if (r < 0)
- goto finish;
+ return r;
s->match_added = true;
}
bus->match_callbacks_modified = true;
r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback);
if (r < 0)
- goto finish;
+ return r;
if (slot)
*slot = s;
s = NULL;
-finish:
- bus_match_parse_free(components, n_components);
- sd_bus_slot_unref(s);
-
- return r;
+ return 0;
}
_public_ int sd_bus_add_match(
return bus_add_match_full(bus, slot, true, match, callback, install_callback, userdata);
}
-bool bus_pid_changed(sd_bus *bus) {
- assert(bus);
-
- /* We don't support people creating a bus connection and
- * keeping it around over a fork(). Let's complain. */
-
- return bus->original_pid != getpid_cached();
-}
-
static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
int r;
_public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) {
assert_return(b, -EINVAL);
assert_return(tid, -EINVAL);
- assert_return(!bus_pid_changed(b), -ECHILD);
+ assert_return(!bus_origin_changed(b), -ECHILD);
if (b->tid != 0) {
*tid = b->tid;
_public_ int sd_bus_try_close(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return -EOPNOTSUPP;
}
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(description, -EINVAL);
assert_return(bus->description, -ENXIO);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->description)
*description = bus->description;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(scope, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->runtime_scope < 0)
return -ENODATA;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(address, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (bus->address) {
*address = bus->address;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(mask, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
*mask = bus->creds_mask;
return 0;
_public_ int sd_bus_is_bus_client(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->bus_client;
}
_public_ int sd_bus_is_server(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->is_server;
}
_public_ int sd_bus_is_anonymous(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->anonymous_auth;
}
_public_ int sd_bus_is_trusted(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->trusted;
}
_public_ int sd_bus_is_monitor(sd_bus *bus) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
return bus->is_monitor;
}
_public_ int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(ret, -EINVAL);
*ret = bus->rqueue_size;
_public_ int sd_bus_get_n_queued_write(sd_bus *bus, uint64_t *ret) {
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
assert_return(ret, -EINVAL);
*ret = bus->wqueue_size;
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(m, -EINVAL);
assert_return(m->sealed, -EINVAL);
- assert_return(!bus_pid_changed(bus), -ECHILD);
+ assert_return(!bus_origin_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdio.h>
+#include <unistd.h>
#include "sd-bus.h"
#include "bus-internal.h"
#include "bus-message.h"
+#include "process-util.h"
#include "tests.h"
static bool use_system_bus = false;
assert_se(bus->n_ref == 1);
}
+static void test_bus_fork(void) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ assert_se(sd_bus_new(&bus) == 0);
+ assert_se(bus->n_ref == 1);
+
+ /* Check that after a fork the cleanup functions return NULL */
+ r = safe_fork("(bus-fork-test)", FORK_WAIT|FORK_LOG, NULL);
+ if (r == 0) {
+ assert_se(bus);
+ assert_se(sd_bus_is_ready(bus) == -ECHILD);
+ assert_se(sd_bus_flush_close_unref(bus) == NULL);
+ assert_se(sd_bus_close_unref(bus) == NULL);
+ assert_se(sd_bus_unref(bus) == NULL);
+ sd_bus_close(bus);
+ assert_se(bus->n_ref == 1);
+ _exit(EXIT_SUCCESS);
+ }
+
+ assert_se(r >= 0);
+ assert_se(bus->n_ref == 1);
+}
+
static int test_bus_open(void) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
test_setup_logging(LOG_INFO);
test_bus_new();
+ test_bus_fork();
if (test_bus_open() < 0)
return log_tests_skipped("Failed to connect to bus");
cmsg->cmsg_type = SCM_CREDENTIALS;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
- ucred = (struct ucred*) CMSG_DATA(cmsg);
+ ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
ucred->pid = pid != 0 ? pid : getpid_cached();
ucred->uid = getuid();
ucred->gid = getgid();
return sd_pid_notify(0, unset_environment, p);
}
+int sd_pid_notifyf_with_fds(
+ pid_t pid,
+ int unset_environment,
+ const int *fds, size_t n_fds,
+ const char *format, ...) {
+
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ /* Paranoia check: we traditionally used 'unsigned' as array size, but we nowadays more correctly use
+ * 'size_t'. sd_pid_notifyf_with_fds() and sd_pid_notify_with_fds() are from different eras, hence
+ * differ in this. Let's catch resulting incompatibilites early, even though they are pretty much
+ * theoretic only */
+ if (n_fds > UINT_MAX)
+ return -E2BIG;
+
+ if (format) {
+ va_list ap;
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0 || !p)
+ return -ENOMEM;
+ }
+
+ return sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds);
+}
+
_public_ int sd_booted(void) {
/* We test whether the runtime unit file directory has been
* created. This takes place in mount-setup.c, so is
* kernel makes this guarantee when creating those devices, and hence we should too when
* enumerating them. */
- sound_a = strstr(devpath_a, "/sound/card");
+ sound_a = strstrafter(devpath_a, "/sound/card");
if (!sound_a)
return 0;
- sound_a += STRLEN("/sound/card");
sound_a = strchr(devpath_a, '/');
if (!sound_a)
return 0;
static int device_monitor_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
- _unused_ _cleanup_(log_context_freep) LogContext *c = NULL;
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = NULL;
sd_device_monitor *m = ASSERT_PTR(userdata);
if (device_monitor_receive_device(m, &device) <= 0)
return 0;
if (log_context_enabled())
- c = log_context_new_consume(device_make_log_fields(device));
+ c = log_context_new_strv_consume(device_make_log_fields(device));
if (m->callback)
return m->callback(m, device, m->userdata);
.msg_name = &snl,
.msg_namelen = sizeof(snl),
};
- struct cmsghdr *cmsg;
struct ucred *cred;
size_t offset;
ssize_t n;
snl.nl.nl_pid);
}
- cmsg = CMSG_FIRSTHDR(&smsg);
- if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS)
+ cred = CMSG_FIND_DATA(&smsg, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
+ if (!cred)
return log_monitor_errno(m, SYNTHETIC_ERRNO(EAGAIN),
"No sender credentials received, ignoring message.");
- cred = (struct ucred*) CMSG_DATA(cmsg);
if (!check_sender_uid(m, cred->uid))
return log_monitor_errno(m, SYNTHETIC_ERRNO(EAGAIN),
"Sender uid="UID_FMT", message ignored.", cred->uid);
return r;
if (r == 0)
break;
+ if (isempty(word))
+ continue;
r = device_add_tag(device, word, streq(key, "CURRENT_TAGS"));
if (r < 0)
void device_set_devlink_priority(sd_device *device, int priority);
int device_ensure_usec_initialized(sd_device *device, sd_device *device_old);
int device_add_devlink(sd_device *device, const char *devlink);
-void device_remove_devlink(sd_device *device, const char *devlink);
+int device_remove_devlink(sd_device *device, const char *devlink);
bool device_has_devlink(sd_device *device, const char *devlink);
int device_add_property(sd_device *device, const char *property, const char *value);
int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
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 "sd-device.h"
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "device-internal.h"
#include "device-private.h"
#include "device-util.h"
/* The input path maybe a symlink located outside of /sys. Let's try to chase the symlink at first.
* The primary usecase is that e.g. /proc/device-tree is a symlink to /sys/firmware/devicetree/base.
* By chasing symlinks in the path at first, we can call sd_device_new_from_path() with such path. */
- r = chase_symlinks(_syspath, NULL, 0, &syspath, &fd);
+ r = chase(_syspath, NULL, 0, &syspath, &fd);
if (r == -ENOENT)
/* the device does not exist (any more?) */
return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
char *p;
/* /sys is a symlink to somewhere sysfs is mounted on? In that case, we convert the path to real sysfs to "/sys". */
- r = chase_symlinks("/sys", NULL, 0, &real_sys, NULL);
+ r = chase("/sys", NULL, 0, &real_sys, NULL);
if (r < 0)
return log_debug_errno(r, "sd-device: Failed to chase symlink /sys: %m");
if (major(devnum) == 0)
return -ENODEV;
- if (asprintf(&syspath, "/sys/dev/%s/%u:%u", t, major(devnum), minor(devnum)) < 0)
+ if (asprintf(&syspath, "/sys/dev/%s/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0)
return -ENOMEM;
r = sd_device_new_from_syspath(&dev, syspath);
return 0;
}
+static int mangle_devname(const char *p, char **ret) {
+ char *q;
+
+ assert(p);
+ assert(ret);
+
+ if (!path_is_safe(p))
+ return -EINVAL;
+
+ /* When the path is absolute, it must start with "/dev/", but ignore "/dev/" itself. */
+ if (path_is_absolute(p)) {
+ if (isempty(path_startswith(p, "/dev/")))
+ return -EINVAL;
+
+ q = strdup(p);
+ } else
+ q = path_join("/dev/", p);
+ if (!q)
+ return -ENOMEM;
+
+ path_simplify(q);
+
+ *ret = q;
+ return 0;
+}
+
int device_set_devname(sd_device *device, const char *devname) {
_cleanup_free_ char *t = NULL;
int r;
assert(device);
assert(devname);
- if (devname[0] != '/')
- t = strjoin("/dev/", devname);
- else
- t = strdup(devname);
- if (!t)
- return -ENOMEM;
+ r = mangle_devname(devname, &t);
+ if (r < 0)
+ return r;
r = device_add_property_internal(device, "DEVNAME", t);
if (r < 0)
if (!device->devname)
return -ENOENT;
- assert(path_startswith(device->devname, "/dev/"));
+ assert(!isempty(path_startswith(device->devname, "/dev/")));
if (devname)
*devname = device->devname;
static bool is_valid_tag(const char *tag) {
assert(tag);
- return !strchr(tag, ':') && !strchr(tag, ' ');
+ return in_charset(tag, ALPHANUMERICAL "-_") && filename_is_valid(tag);
}
int device_add_tag(sd_device *device, const char *tag, bool both) {
}
int device_add_devlink(sd_device *device, const char *devlink) {
+ char *p;
int r;
assert(device);
assert(devlink);
- r = set_put_strdup(&device->devlinks, devlink);
+ r = mangle_devname(devlink, &p);
+ if (r < 0)
+ return r;
+
+ r = set_ensure_consume(&device->devlinks, &path_hash_ops_free, p);
if (r < 0)
return r;
device->devlinks_generation++;
device->property_devlinks_outdated = true;
- return 0;
+ return r; /* return 1 when newly added, 0 when already exists */
}
-void device_remove_devlink(sd_device *device, const char *devlink) {
- _cleanup_free_ char *s = NULL;
+int device_remove_devlink(sd_device *device, const char *devlink) {
+ _cleanup_free_ char *p = NULL, *s = NULL;
+ int r;
assert(device);
assert(devlink);
- s = set_remove(device->devlinks, devlink);
+ r = mangle_devname(devlink, &p);
+ if (r < 0)
+ return r;
+
+ s = set_remove(device->devlinks, p);
if (!s)
- return;
+ return 0; /* does not exist */
device->devlinks_generation++;
device->property_devlinks_outdated = true;
+ return 1; /* removed */
}
bool device_has_devlink(sd_device *device, const char *devlink) {
if (sd_device_get_devnum(device, &devnum) >= 0) {
/* use dev_t — b259:131072, c254:0 */
- if (asprintf(&id, "%c%u:%u",
+ if (asprintf(&id, "%c" DEVNUM_FORMAT_STR,
streq(subsystem, "block") ? 'b' : 'c',
- major(devnum), minor(devnum)) < 0)
+ DEVNUM_FORMAT_VAL(devnum)) < 0)
return -ENOMEM;
} else if (sd_device_get_ifindex(device, &ifindex) >= 0) {
/* use netdev ifindex — n3 */
return -ENOMEM;
}
- r = hashmap_ensure_put(&device->sysattr_values, &string_hash_ops_free_free, new_key, value);
+ r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value);
if (r < 0)
return r;
#include "missing_magic.h"
#include "missing_syscall.h"
#include "missing_threads.h"
+#include "origin-id.h"
#include "path-util.h"
#include "prioq.h"
#include "process-util.h"
/* A list of memory pressure event sources that still need their subscription string written */
LIST_HEAD(sd_event_source, memory_pressure_write_list);
- pid_t original_pid;
+ uint64_t origin_id;
uint64_t iteration;
triple_timestamp timestamp;
unsigned delays[sizeof(usec_t) * 8];
};
+DEFINE_PRIVATE_ORIGIN_ID_HELPERS(sd_event, event);
+
static thread_local sd_event *default_event = NULL;
static void source_disconnect(sd_event_source *s);
.boottime_alarm.fd = -EBADF,
.boottime_alarm.next = USEC_INFINITY,
.perturb = USEC_INFINITY,
- .original_pid = getpid_cached(),
+ .origin_id = origin_id_query(),
};
r = prioq_ensure_allocated(&e->pending, pending_prioq_compare);
return r;
}
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
+/* Define manually so we can add the origin check */
+_public_ sd_event *sd_event_ref(sd_event *e) {
+ if (!e)
+ return NULL;
+ if (event_origin_changed(e))
+ return NULL;
+
+ e->n_ref++;
+
+ return e;
+}
+
+_public_ sd_event* sd_event_unref(sd_event *e) {
+ if (!e)
+ return NULL;
+ if (event_origin_changed(e))
+ return NULL;
+
+ assert(e->n_ref > 0);
+ if (--e->n_ref > 0)
+ return NULL;
+
+ return event_free(e);
+}
+
#define PROTECT_EVENT(e) \
_unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e);
return sd_event_source_unref(s);
}
-static bool event_pid_changed(sd_event *e) {
- assert(e);
-
- /* We don't support people creating an event loop and keeping
- * it around over a fork(). Let's complain. */
-
- return e->original_pid != getpid_cached();
-}
-
static void source_io_unregister(sd_event_source *s) {
assert(s);
assert(s->type == SOURCE_IO);
- if (event_pid_changed(s->event))
+ if (event_origin_changed(s->event))
return;
if (!s->io.registered)
assert(s);
assert(s->type == SOURCE_CHILD);
- if (event_pid_changed(s->event))
+ if (event_origin_changed(s->event))
return;
if (!s->child.registered)
assert(s);
assert(s->type == SOURCE_MEMORY_PRESSURE);
- if (event_pid_changed(s->event))
+ if (event_origin_changed(s->event))
return;
if (!s->memory_pressure.registered)
assert(e);
- if (event_pid_changed(e))
+ if (event_origin_changed(e))
return -ECHILD;
if (e->signal_sources && e->signal_sources[sig])
return;
}
- if (event_pid_changed(e))
+ if (event_origin_changed(e))
return;
assert(d->fd >= 0);
break;
case SOURCE_CHILD:
- if (event_pid_changed(s->event))
+ if (event_origin_changed(s->event))
s->child.process_owned = false;
if (s->child.pid > 0) {
assert_return(fd >= 0, -EBADF);
assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = io_exit_callback;
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(accuracy != UINT64_MAX, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */
return -EOPNOTSUPP;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
/* Let's make sure our special flag stays outside of the valid signal range */
assert_cc(_NSIG < SD_EVENT_SIGNAL_PROCMASK);
assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
assert_return(options != 0, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = child_exit_callback;
assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
assert_return(options != 0, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = child_exit_callback;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = generic_exit_callback;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = generic_exit_callback;
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(callback, -EINVAL);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
r = prioq_ensure_allocated(&e->exit, exit_prioq_compare);
if (r < 0)
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = memory_pressure_callback;
assert_se(hashmap_remove(e->inotify_data, &d->priority) == d);
if (d->fd >= 0) {
- if (!event_pid_changed(e) &&
+ if (!event_origin_changed(e) &&
epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0)
log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m");
if (d->inotify_data) {
if (d->wd >= 0) {
- if (d->inotify_data->fd >= 0 && !event_pid_changed(e)) {
+ if (d->inotify_data->fd >= 0 && !event_origin_changed(e)) {
/* So here's a problem. At the time this runs the watch descriptor might already be
* invalidated, because an IN_IGNORED event might be queued right the moment we enter
* the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(fd >= 0, -EBADF);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!callback)
callback = inotify_exit_callback;
_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) {
assert_return(s, -EINVAL);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return free_and_strdup(&s->description, description);
}
_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) {
assert_return(s, -EINVAL);
assert_return(description, -EINVAL);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (!s->description)
return -ENXIO;
_public_ sd_event *sd_event_source_get_event(sd_event_source *s) {
assert_return(s, NULL);
+ assert_return(!event_origin_changed(s->event), NULL);
return s->event;
}
assert_return(s, -EINVAL);
assert_return(s->type != SOURCE_EXIT, -EDOM);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->pending;
}
_public_ int sd_event_source_get_io_fd(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->io.fd;
}
assert_return(s, -EINVAL);
assert_return(fd >= 0, -EBADF);
assert_return(s->type == SOURCE_IO, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->io.fd == fd)
return 0;
_public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->io.owned;
}
_public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
s->io.owned = own;
return 0;
assert_return(s, -EINVAL);
assert_return(events, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*events = s->io.events;
return 0;
assert_return(s->type == SOURCE_IO, -EDOM);
assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
/* edge-triggered updates are never skipped, so we can reset edges */
if (s->io.events == events && !(events & EPOLLET))
assert_return(revents, -EINVAL);
assert_return(s->type == SOURCE_IO, -EDOM);
assert_return(s->pending, -ENODATA);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*revents = s->io.revents;
return 0;
_public_ int sd_event_source_get_signal(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_SIGNAL, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->signal.sig;
}
_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) {
assert_return(s, -EINVAL);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*priority = s->priority;
return 0;
assert_return(s, -EINVAL);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->priority == priority)
return 0;
return false;
assert_return(s, -EINVAL);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (ret)
*ret = s->enabled;
return 0;
assert_return(s, -EINVAL);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
/* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */
if (s->event->state == SD_EVENT_FINISHED)
assert_return(s, -EINVAL);
assert_return(usec, -EINVAL);
assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*usec = s->time.next;
return 0;
assert_return(s, -EINVAL);
assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
r = source_set_pending(s, false);
if (r < 0)
assert_return(s, -EINVAL);
assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (usec == USEC_INFINITY)
return sd_event_source_set_time(s, USEC_INFINITY);
assert_return(s, -EINVAL);
assert_return(usec, -EINVAL);
assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*usec = s->time.accuracy;
return 0;
assert_return(usec != UINT64_MAX, -EINVAL);
assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
r = source_set_pending(s, false);
if (r < 0)
assert_return(s, -EINVAL);
assert_return(clock, -EINVAL);
assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*clock = event_source_type_to_clock(s->type);
return 0;
assert_return(s, -EINVAL);
assert_return(pid, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*pid = s->child.pid;
return 0;
_public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->child.pidfd < 0)
return -EOPNOTSUPP;
_public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
assert_return(SIGNAL_VALID(sig), -EINVAL);
/* If we already have seen indication the process exited refuse sending a signal early. This way we
_public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->child.pidfd < 0)
return -EOPNOTSUPP;
_public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->child.pidfd < 0)
return -EOPNOTSUPP;
_public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->child.process_owned;
}
_public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) {
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_CHILD, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
s->child.process_owned = own;
return 0;
assert_return(s, -EINVAL);
assert_return(mask, -EINVAL);
assert_return(s->type == SOURCE_INOTIFY, -EDOM);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
*mask = s->inotify.mask;
return 0;
assert_return(s, -EINVAL);
assert_return(s->type != SOURCE_EXIT, -EDOM);
assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(s->event), -ECHILD);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->prepare == callback)
return 0;
_public_ void* sd_event_source_get_userdata(sd_event_source *s) {
assert_return(s, NULL);
+ assert_return(!event_origin_changed(s->event), NULL);
return s->userdata;
}
void *ret;
assert_return(s, NULL);
+ assert_return(!event_origin_changed(s->event), NULL);
ret = s->userdata;
s->userdata = userdata;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(e->state == SD_EVENT_ARMED, -EBUSY);
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(e->state == SD_EVENT_PENDING, -EBUSY);
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
+
PROTECT_EVENT(e);
while (e->state != SD_EVENT_FINISHED) {
_public_ int sd_event_get_fd(sd_event *e) {
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
return e->epoll_fd;
}
_public_ int sd_event_get_state(sd_event *e) {
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
return e->state;
}
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(code, -EINVAL);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!e->exit_requested)
return -ENODATA;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
e->exit_requested = true;
e->exit_code = code;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(usec, -EINVAL);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
return -EOPNOTSUPP;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
assert_return(tid, -EINVAL);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (e->tid != 0) {
*tid = e->tid;
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
if (e->watchdog == !!b)
return e->watchdog;
_public_ int sd_event_get_watchdog(sd_event *e) {
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
return e->watchdog;
}
_public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
assert_return(e, -EINVAL);
assert_return(e = event_resolve(e), -ENOPKG);
- assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(!event_origin_changed(e), -ECHILD);
*ret = e->iteration;
return 0;
_public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) {
assert_return(s, -EINVAL);
+ assert_return(s->event, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
s->destroy_callback = callback;
return 0;
_public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) {
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (ret)
*ret = s->destroy_callback;
_public_ int sd_event_source_get_floating(sd_event_source *s) {
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->floating;
}
_public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->floating == !!b)
return 0;
_public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
assert_return(s, -EINVAL);
assert_return(s->type != SOURCE_EXIT, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
return s->exit_on_failure;
}
_public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) {
assert_return(s, -EINVAL);
assert_return(s->type != SOURCE_EXIT, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (s->exit_on_failure == !!b)
return 0;
int r;
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
/* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing
* so is a programming error. */
_public_ int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback) {
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
s->ratelimit_expire_callback = callback;
return 0;
_public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) {
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
/* Querying whether an event source has ratelimiting configured is not a loggable offense, hence
* don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error. */
_public_ int sd_event_source_is_ratelimited(sd_event_source *s) {
assert_return(s, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
return false;
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM);
assert_return(ty, -EINVAL);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (!STR_IN_SET(ty, "some", "full"))
return -EINVAL;
assert_return(s, -EINVAL);
assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM);
+ assert_return(!event_origin_changed(s->event), -ECHILD);
if (threshold_usec <= 0 || threshold_usec >= UINT64_MAX)
return -ERANGE;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/wait.h>
+#include <unistd.h>
#include "sd-event.h"
assert_se(sd_event_wait(e, 0) == 0);
}
+TEST(fork) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ int r;
+
+ assert_se(sd_event_default(&e) >= 0);
+ assert_se(sd_event_prepare(e) == 0);
+
+ /* Check that after a fork the cleanup functions return NULL */
+ r = safe_fork("(bus-fork-test)", FORK_WAIT|FORK_LOG, NULL);
+ if (r == 0) {
+ assert_se(e);
+ assert_se(sd_event_ref(e) == NULL);
+ assert_se(sd_event_unref(e) == NULL);
+ _exit(EXIT_SUCCESS);
+ }
+
+ assert_se(r >= 0);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
#include <unistd.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 *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;
- fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, 0);
if (fd < 0)
- return -errno;
+ 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);
}
/* 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 *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("/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("/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(
return 0;
}
-static Compression maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
- Compression compression = COMPRESSION_NONE;
-
+static int maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
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;
- }
-#endif
+ Compression c;
+ int r;
+
+ c = JOURNAL_FILE_COMPRESSION(f);
+ if (c == COMPRESSION_NONE || size < f->compress_threshold_bytes)
+ return 0;
+
+ r = compress_blob(c, src, size, dst, size - 1, rsize);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to compress data object using %s, ignoring: %m", compression_to_string(c));
- return compression;
+ log_debug("Compressed data object %"PRIu64" -> %zu using %s", size, *rsize, compression_to_string(c));
+
+ return 1; /* compressed */
+#else
+ return 0;
+#endif
}
static int journal_file_append_data(
uint64_t hash, p, osize;
Object *o, *fo;
size_t rsize = 0;
- Compression c;
const void *eq;
int r;
o->data.hash = htole64(hash);
- c = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize);
+ r = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize);
+ if (r <= 0)
+ /* We don't really care failures, let's continue without compression */
+ memcpy_safe(journal_file_data_payload_field(f, o), data, size);
+ else {
+ Compression c = JOURNAL_FILE_COMPRESSION(f);
+
+ assert(c >= 0 && c < _COMPRESSION_MAX && c != COMPRESSION_NONE);
- if (c != COMPRESSION_NONE) {
o->object.size = htole64(journal_file_data_payload_offset(f) + rsize);
o->object.flags |= COMPRESSION_TO_OBJECT_FLAG(c);
- } else
- memcpy_safe(journal_file_data_payload_field(f, o), data, size);
+ }
r = journal_file_link_data(f, o, p, hash);
if (r < 0)
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);
Match *level0, *level1, *level2;
- pid_t original_pid;
+ uint64_t origin_id;
int inotify_fd;
unsigned current_invalidate_counter, last_invalidate_counter;
#include "time-util.h"
#include "xattr-util.h"
-struct vacuum_info {
+typedef struct vacuum_info {
uint64_t usage;
char *filename;
sd_id128_t seqnum_id;
uint64_t seqnum;
bool have_seqnum;
-};
+} vacuum_info;
-static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info *b) {
+static int vacuum_info_compare(const vacuum_info *a, const vacuum_info *b) {
int r;
if (a->have_seqnum && b->have_seqnum &&
return strcmp(a->filename, b->filename);
}
+static void vacuum_info_array_free(vacuum_info *list, size_t n) {
+ if (!list)
+ return;
+
+ FOREACH_ARRAY(i, list, n)
+ free(i->filename);
+
+ free(list);
+}
+
static void patch_realtime(
int fd,
const char *fn,
uint64_t sum = 0, freed = 0, n_active_files = 0;
size_t n_list = 0, i;
_cleanup_closedir_ DIR *d = NULL;
- struct vacuum_info *list = NULL;
+ vacuum_info *list = NULL;
usec_t retention_limit = 0;
int r;
+ CLEANUP_ARRAY(list, n_list, vacuum_info_array_free);
+
assert(directory);
if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
if (!d)
return -errno;
- FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
+ FOREACH_DIRENT_ALL(de, d, return -errno) {
unsigned long long seqnum = 0, realtime;
_cleanup_free_ char *p = NULL;
sd_id128_t seqnum_id;
if (!S_ISREG(st.st_mode))
continue;
+ size = 512UL * (uint64_t) st.st_blocks;
+
q = strlen(de->d_name);
if (endswith(de->d_name, ".journal")) {
if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
n_active_files++;
+ sum += size;
continue;
}
de->d_name[q-8-16-1-16-1] != '-' ||
de->d_name[q-8-16-1-16-1-32-1] != '@') {
n_active_files++;
+ sum += size;
continue;
}
p = strdup(de->d_name);
- if (!p) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!p)
+ return -ENOMEM;
de->d_name[q-8-16-1-16-1] = 0;
if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
n_active_files++;
+ sum += size;
continue;
}
if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
n_active_files++;
+ sum += size;
continue;
}
if (q < 1 + 16 + 1 + 16 + 8 + 1) {
n_active_files++;
+ sum += size;
continue;
}
if (de->d_name[q-1-8-16-1] != '-' ||
de->d_name[q-1-8-16-1-16-1] != '@') {
n_active_files++;
+ sum += size;
continue;
}
p = strdup(de->d_name);
- if (!p) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!p)
+ return -ENOMEM;
if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
n_active_files++;
+ sum += size;
continue;
}
continue;
}
- size = 512UL * (uint64_t) st.st_blocks;
-
r = journal_file_empty(dirfd(d), p);
if (r < 0) {
log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
patch_realtime(dirfd(d), p, &st, &realtime);
- if (!GREEDY_REALLOC(list, n_list + 1)) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!GREEDY_REALLOC(list, n_list + 1))
+ return -ENOMEM;
- list[n_list++] = (struct vacuum_info) {
+ list[n_list++] = (vacuum_info) {
.filename = TAKE_PTR(p),
.usage = size,
.seqnum = seqnum,
sum += size;
}
- typesafe_qsort(list, n_list, vacuum_compare);
+ typesafe_qsort(list, n_list, vacuum_info_compare);
for (i = 0; i < n_list; i++) {
uint64_t left;
if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
*oldest_usec = list[i].realtime;
- r = 0;
-
-finish:
- for (i = 0; i < n_list; i++)
- free(list[i].filename);
- free(list);
-
log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.",
FORMAT_BYTES(freed), directory);
- return r;
+ return 0;
}
#include "list.h"
#include "lookup3.h"
#include "nulstr-util.h"
+#include "origin-id.h"
#include "path-util.h"
#include "prioq.h"
#include "process-util.h"
#define DEFAULT_DATA_THRESHOLD (64*1024)
+DEFINE_PRIVATE_ORIGIN_ID_HELPERS(sd_journal, journal);
+
static void remove_file_real(sd_journal *j, JournalFile *f);
static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f);
static void journal_file_unlink_newest_by_bood_id(sd_journal *j, JournalFile *f);
-static bool journal_pid_changed(sd_journal *j) {
- assert(j);
-
- /* We don't support people creating a journal object and
- * keeping it around over a fork(). Let's complain. */
-
- return j->original_pid != getpid_cached();
-}
-
static int journal_put_error(sd_journal *j, int r, const char *path) {
_cleanup_free_ char *copy = NULL;
int k;
uint64_t hash;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(data, -EINVAL);
if (size == 0)
_public_ int sd_journal_add_conjunction(sd_journal *j) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
if (!j->level0)
return 0;
_public_ int sd_journal_add_disjunction(sd_journal *j) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
if (!j->level0)
return 0;
}
_public_ void sd_journal_flush_matches(sd_journal *j) {
- if (!j)
+ if (!j || journal_origin_changed(j))
return;
if (j->level0)
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
r = iterated_cache_get(j->files_cache, NULL, &files, &n_files);
if (r < 0)
int c = 0, r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(skip <= INT_MAX, -ERANGE);
if (skip == 0) {
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(cursor, -EINVAL);
if (!j->current_file || j->current_file->current_offset <= 0)
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(!isempty(cursor), -EINVAL);
for (const char *p = cursor;;) {
Object *o;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(!isempty(cursor), -EINVAL);
if (!j->current_file || j->current_file->current_offset <= 0)
_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
detach_location(j);
_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
detach_location(j);
_public_ int sd_journal_seek_head(sd_journal *j) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
detach_location(j);
_public_ int sd_journal_seek_tail(sd_journal *j) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
detach_location(j);
return NULL;
*j = (sd_journal) {
- .original_pid = getpid_cached(),
+ .origin_id = origin_id_query(),
.toplevel_fd = -EBADF,
.inotify_fd = -EBADF,
.flags = flags,
Directory *d;
Prioq *p;
- if (!j)
+ if (!j || journal_origin_changed(j))
return;
while ((p = hashmap_first(j->newest_by_boot_id)))
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
f = j->current_file;
if (!f)
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
f = j->current_file;
if (!f)
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
f = j->current_file;
if (!f)
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(field, -EINVAL);
assert_return(data, -EINVAL);
assert_return(size, -EINVAL);
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(data, -EINVAL);
assert_return(size, -EINVAL);
}
_public_ void sd_journal_restart_data(sd_journal *j) {
- if (!j)
+ if (!j || journal_origin_changed(j))
return;
j->current_field = 0;
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
if (j->no_inotify)
return -EMEDIUMTYPE;
int fd;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
fd = sd_journal_get_fd(j);
if (fd < 0)
int fd;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(timeout_usec, -EINVAL);
fd = sd_journal_get_fd(j);
bool got_something = false;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
if (j->inotify_fd < 0) /* We have no inotify fd yet? Then there's noting to process. */
return 0;
uint64_t t;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
if (j->inotify_fd < 0) {
JournalFile *f;
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(from || to, -EINVAL);
assert_return(from != to, -EINVAL);
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(ret_from != ret_to, -EINVAL);
ORDERED_HASHMAP_FOREACH(f, j->files) {
uint64_t sum = 0;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(ret, -EINVAL);
ORDERED_HASHMAP_FOREACH(f, j->files) {
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(!isempty(field), -EINVAL);
assert_return(field_is_valid(field), -EINVAL);
size_t k;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(j->unique_field, -EINVAL);
k = strlen(j->unique_field);
}
_public_ void sd_journal_restart_unique(sd_journal *j) {
- if (!j)
+ if (!j || journal_origin_changed(j))
return;
j->unique_file = NULL;
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(field, -EINVAL);
if (!j->fields_file) {
}
_public_ void sd_journal_restart_fields(sd_journal *j) {
- if (!j)
+ if (!j || journal_origin_changed(j))
return;
j->fields_file = NULL;
_public_ int sd_journal_reliable_fd(sd_journal *j) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
return !j->on_network;
}
int r;
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(ret, -EINVAL);
r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size);
_public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
j->data_threshold = sz;
return 0;
_public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) {
assert_return(j, -EINVAL);
- assert_return(!journal_pid_changed(j), -ECHILD);
+ assert_return(!journal_origin_changed(j), -ECHILD);
assert_return(sz, -EINVAL);
*sz = j->data_threshold;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <unistd.h>
+
#include "sd-journal.h"
#include "chattr-util.h"
+#include "journal-internal.h"
#include "log.h"
#include "parse-util.h"
+#include "process-util.h"
#include "rm-rf.h"
#include "tests.h"
r = sd_journal_open_directory(&j, t, 0);
assert_se(r == 0);
+ assert_se(sd_journal_seek_head(j) == 0);
+ assert_se(j->current_location.type == LOCATION_HEAD);
+
+ r = safe_fork("(journal-fork-test)", FORK_WAIT|FORK_LOG, NULL);
+ if (r == 0) {
+ assert_se(j);
+ assert_se(sd_journal_get_realtime_usec(j, NULL) == -ECHILD);
+ assert_se(sd_journal_seek_tail(j) == -ECHILD);
+ assert_se(j->current_location.type == LOCATION_HEAD);
+ sd_journal_close(j);
+ _exit(EXIT_SUCCESS);
+ }
+
+ assert_se(r >= 0);
+
sd_journal_close(j);
j = NULL;
#include "string-util.h"
#include "strv.h"
#include "tmpfile-util.h"
+#include "xkbcommon-util.h"
static bool startswith_comma(const char *s, const char *prefix) {
assert(s);
assert(src);
x11_context_clear(dest);
- *dest = *src;
- *src = (X11Context) {};
+ *dest = TAKE_STRUCT(*src);
}
bool x11_context_isempty(const X11Context *xc) {
return modified;
}
+int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error) {
+ int r;
+
+ assert(xc);
+
+ if (!x11_context_is_safe(xc)) {
+ if (error)
+ sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid X11 keyboard layout.");
+ return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL), "Invalid X11 keyboard layout.");
+ }
+
+ r = verify_xkb_rmlvo(xc->model, xc->layout, xc->variant, xc->options);
+ if (r == -EOPNOTSUPP) {
+ log_full_errno(MAX(log_level, LOG_NOTICE), r,
+ "Cannot verify if new keymap is correct, libxkbcommon.so unavailable.");
+ return 0;
+ }
+ if (r < 0) {
+ if (error)
+ sd_bus_error_set_errnof(error, r, "Specified keymap cannot be compiled, refusing as invalid.");
+ return log_full_errno(log_level, r,
+ "Cannot compile XKB keymap for x11 keyboard layout "
+ "(model='%s' / layout='%s' / variant='%s' / options='%s'): %m",
+ strempty(xc->model), strempty(xc->layout), strempty(xc->variant), strempty(xc->options));
+ }
+
+ return 0;
+}
+
void vc_context_clear(VCContext *vc) {
assert(vc);
assert(src);
vc_context_clear(dest);
- *dest = *src;
- *src = (VCContext) {};
+ *dest = TAKE_STRUCT(*src);
}
bool vc_context_isempty(const VCContext *vc) {
return modified;
}
+static int verify_keymap(const char *keymap, int log_level, sd_bus_error *error) {
+ int r;
+
+ assert(keymap);
+
+ r = keymap_exists(keymap); /* This also verifies that the keymap name is kosher. */
+ if (r < 0) {
+ if (error)
+ sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", keymap);
+ return log_full_errno(log_level, r, "Failed to check keymap %s: %m", keymap);
+ }
+ if (r == 0) {
+ if (error)
+ sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", keymap);
+ return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOENT), "Keymap %s is not installed.", keymap);
+ }
+
+ return 0;
+}
+
+int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error) {
+ int r;
+
+ assert(vc);
+
+ if (vc->keymap) {
+ r = verify_keymap(vc->keymap, log_level, error);
+ if (r < 0)
+ return r;
+ }
+
+ if (vc->toggle) {
+ r = verify_keymap(vc->toggle, log_level, error);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
void context_clear(Context *c) {
assert(c);
int vconsole_read_data(Context *c, sd_bus_message *m) {
_cleanup_close_ int fd = -EBADF;
struct stat st;
+ int r;
assert(c);
vc_context_clear(&c->vc);
x11_context_clear(&c->x11_from_vc);
- return parse_env_file_fd(fd, "/etc/vconsole.conf",
- "KEYMAP", &c->vc.keymap,
- "KEYMAP_TOGGLE", &c->vc.toggle,
- "XKBLAYOUT", &c->x11_from_vc.layout,
- "XKBMODEL", &c->x11_from_vc.model,
- "XKBVARIANT", &c->x11_from_vc.variant,
- "XKBOPTIONS", &c->x11_from_vc.options);
+ r = parse_env_file_fd(
+ fd, "/etc/vconsole.conf",
+ "KEYMAP", &c->vc.keymap,
+ "KEYMAP_TOGGLE", &c->vc.toggle,
+ "XKBLAYOUT", &c->x11_from_vc.layout,
+ "XKBMODEL", &c->x11_from_vc.model,
+ "XKBVARIANT", &c->x11_from_vc.variant,
+ "XKBOPTIONS", &c->x11_from_vc.options);
+ if (r < 0)
+ return r;
+
+ if (vc_context_verify(&c->vc) < 0)
+ vc_context_clear(&c->vc);
+
+ if (x11_context_verify(&c->x11_from_vc) < 0)
+ x11_context_clear(&c->x11_from_vc);
+
+ return 0;
}
int x11_read_data(Context *c, sd_bus_message *m) {
- _cleanup_close_ int fd = -EBADF, fd_ro = -EBADF;
+ _cleanup_close_ int fd = -EBADF;
_cleanup_fclose_ FILE *f = NULL;
bool in_section = false;
struct stat st;
c->x11_stat = st;
x11_context_clear(&c->x11_from_xorg);
- fd_ro = fd_reopen(fd, O_CLOEXEC | O_RDONLY);
- if (fd_ro < 0)
- return fd_ro;
-
- f = fdopen(fd_ro, "re");
- if (!f)
- return -errno;
-
- TAKE_FD(fd_ro);
+ r = fdopen_independent(fd, "re", &f);
+ if (r < 0)
+ return r;
for (;;) {
_cleanup_free_ char *line = NULL;
in_section = false;
}
+ if (x11_context_verify(&c->x11_from_xorg) < 0)
+ x11_context_clear(&c->x11_from_xorg);
+
return 0;
}
for (unsigned n = 0;;) {
_cleanup_strv_free_ char **a = NULL;
+ X11Context xc;
r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
if (r < 0)
if (!streq(vc->keymap, a[0]))
continue;
- return x11_context_copy(ret,
- &(X11Context) {
- .layout = empty_or_dash_to_null(a[1]),
- .model = empty_or_dash_to_null(a[2]),
- .variant = empty_or_dash_to_null(a[3]),
- .options = empty_or_dash_to_null(a[4]),
- });
+ xc = (X11Context) {
+ .layout = empty_or_dash_to_null(a[1]),
+ .model = empty_or_dash_to_null(a[2]),
+ .variant = empty_or_dash_to_null(a[3]),
+ .options = empty_or_dash_to_null(a[4]),
+ };
+
+ if (x11_context_verify(&xc) < 0)
+ continue;
+
+ return x11_context_copy(ret, &xc);
}
}
return !!*ret;
}
+int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
+ _cleanup_free_ char *keymap = NULL;
+ int r;
+
+ assert(xc);
+ assert(ret);
+
+ if (isempty(xc->layout)) {
+ *ret = (VCContext) {};
+ return 0;
+ }
+
+ r = find_converted_keymap(xc, &keymap);
+ if (r == 0)
+ r = find_legacy_keymap(xc, &keymap);
+ if (r < 0)
+ return r;
+
+ *ret = (VCContext) {
+ .keymap = TAKE_PTR(keymap),
+ };
+ return 0;
+}
+
int find_language_fallback(const char *lang, char **ret) {
const char *map;
_cleanup_fclose_ FILE *f = NULL;
}
}
-int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
- _cleanup_free_ char *keymap = NULL;
- int r;
-
- assert(xc);
- assert(ret);
-
- if (isempty(xc->layout)) {
- *ret = (VCContext) {};
- return 0;
- }
-
- r = find_converted_keymap(xc, &keymap);
- if (r == 0)
- r = find_legacy_keymap(xc, &keymap);
- if (r < 0)
- return r;
-
- *ret = (VCContext) {
- .keymap = TAKE_PTR(keymap),
- };
- return 0;
-}
-
bool locale_gen_check_available(void) {
#if HAVE_LOCALEGEN
if (access(LOCALEGEN_PATH, X_OK) < 0) {
for (;;) {
_cleanup_free_ char *line = NULL;
+ char *l;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
if (r == 0)
return 0;
- line = strstrip(line);
- if (strcaseeq_ptr(line, locale_entry))
+ l = strstrip(line);
+ if (strcaseeq_ptr(l, locale_entry))
return 1;
}
}
continue;
}
- line = strstrip(line);
- if (isempty(line)) {
+ line_locale = strstrip(line);
+ if (isempty(line_locale)) {
fputc('\n', fw);
first_line = false;
continue;
}
- line_locale = line;
if (line_locale[0] == '#')
line_locale = strstrip(line_locale + 1);
else if (strcaseeq_ptr(line_locale, locale_entry))
bool x11_context_is_safe(const X11Context *xc);
bool x11_context_equal(const X11Context *a, const X11Context *b);
int x11_context_copy(X11Context *dest, const X11Context *src);
+int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error);
+static inline int x11_context_verify(const X11Context *xc) {
+ return x11_context_verify_and_warn(xc, LOG_DEBUG, NULL);
+}
X11Context *context_get_x11_context(Context *c);
void vc_context_empty_to_null(VCContext *vc);
bool vc_context_equal(const VCContext *a, const VCContext *b);
int vc_context_copy(VCContext *dest, const VCContext *src);
+int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error);
+static inline int vc_context_verify(const VCContext *vc) {
+ return vc_context_verify_and_warn(vc, LOG_DEBUG, NULL);
+}
int find_converted_keymap(const X11Context *xc, char **ret);
int find_legacy_keymap(const X11Context *xc, char **ret);
#include <sys/types.h>
#include <unistd.h>
-#if HAVE_XKBCOMMON
-#include <xkbcommon/xkbcommon.h>
-#include <dlfcn.h>
-#endif
-
#include "sd-bus.h"
#include "alloc-util.h"
#include "bus-message.h"
#include "bus-polkit.h"
#include "constants.h"
-#include "dlfcn-util.h"
#include "kbd-util.h"
#include "localed-util.h"
#include "macro.h"
vc_context_empty_to_null(&in);
- FOREACH_STRING(name, in.keymap ?: in.toggle, in.keymap ? in.toggle : NULL) {
- r = keymap_exists(name); /* This also verifies that the keymap name is kosher. */
- if (r < 0) {
- log_error_errno(r, "Failed to check keymap %s: %m", name);
- return sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", name);
- }
- if (r == 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", name);
- }
+ r = vc_context_verify_and_warn(&in, LOG_ERR, error);
+ if (r < 0)
+ return r;
r = vconsole_read_data(c, m);
if (r < 0) {
return sd_bus_reply_method_return(m, NULL);
}
-#if HAVE_XKBCOMMON
-
-_printf_(3, 0)
-static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
- const char *fmt;
-
- fmt = strjoina("libxkbcommon: ", format);
- DISABLE_WARNING_FORMAT_NONLITERAL;
- log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args);
- REENABLE_WARNING;
-}
-
-#define LOAD_SYMBOL(symbol, dl, name) \
- ({ \
- (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
- (symbol) ? 0 : -EOPNOTSUPP; \
- })
-
-static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
-
- /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
- * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
- * pointers to the shared library are below: */
-
- struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL;
- void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL;
- void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL;
- struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL;
- void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL;
-
- const struct xkb_rule_names rmlvo = {
- .model = model,
- .layout = layout,
- .variant = variant,
- .options = options,
- };
- struct xkb_context *ctx = NULL;
- struct xkb_keymap *km = NULL;
- _cleanup_(dlclosep) void *dl = NULL;
- int r;
-
- /* Compile keymap from RMLVO information to check out its validity */
-
- dl = dlopen("libxkbcommon.so.0", RTLD_LAZY);
- if (!dl)
- return -EOPNOTSUPP;
-
- r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new");
- if (r < 0)
- goto finish;
-
- r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref");
- if (r < 0)
- goto finish;
-
- r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn");
- if (r < 0)
- goto finish;
-
- r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names");
- if (r < 0)
- goto finish;
-
- r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref");
- if (r < 0)
- goto finish;
-
- ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
- if (!ctx) {
- r = -ENOMEM;
- goto finish;
- }
-
- symbol_xkb_context_set_log_fn(ctx, log_xkb);
-
- km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
- if (!km) {
- r = -EINVAL;
- goto finish;
- }
-
- r = 0;
-
-finish:
- if (symbol_xkb_keymap_unref && km)
- symbol_xkb_keymap_unref(km);
-
- if (symbol_xkb_context_unref && ctx)
- symbol_xkb_context_unref(ctx);
-
- return r;
-}
-
-#else
-
-static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
- return 0;
-}
-
-#endif
-
static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
_cleanup_(vc_context_clear) VCContext converted = {};
Context *c = ASSERT_PTR(userdata);
x11_context_empty_to_null(&in);
- if (!x11_context_is_safe(&in))
- return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keyboard data");
-
- r = verify_xkb_rmlvo(in.model, in.layout, in.variant, in.options);
- if (r == -EOPNOTSUPP)
- log_notice_errno(r, "Cannot verify if new keymap is correct, libxkbcommon.so unavailable.");
- else if (r < 0) {
- log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
- strempty(in.model), strempty(in.layout), strempty(in.variant), strempty(in.options));
- return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
- "Specified keymap cannot be compiled, refusing as invalid.");
- }
+ r = x11_context_verify_and_warn(&in, LOG_ERR, error);
+ if (r < 0)
+ return r;
r = vconsole_read_data(c, m);
if (r < 0) {
systemd_localed_sources = files(
'localed-util.c',
'localed.c',
+ 'xkbcommon-util.c',
)
localectl_sources = files('localectl.c')
install_dir : pkgdatadir)
endif
+# logind will load libxkbcommon.so dynamically on its own, but we still need to
+# specify where the headers are.
+if conf.get('HAVE_XKBCOMMON') == 1
+ libxkbcommon_deps = [libdl,
+ libxkbcommon.partial_dependency(compile_args: true)]
+else
+ libxkbcommon_deps = []
+endif
+
tests += [
{
'sources' : files(
'test-localed-util.c',
'localed-util.c',
+ 'xkbcommon-util.c',
),
+ 'dependencies' : libxkbcommon_deps,
},
]
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dlfcn-util.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "xkbcommon-util.h"
+
+#if HAVE_XKBCOMMON
+static void *xkbcommon_dl = NULL;
+
+struct xkb_context* (*sym_xkb_context_new)(enum xkb_context_flags flags);
+void (*sym_xkb_context_unref)(struct xkb_context *context);
+void (*sym_xkb_context_set_log_fn)(
+ struct xkb_context *context,
+ void (*log_fn)(
+ struct xkb_context *context,
+ enum xkb_log_level level,
+ const char *format,
+ va_list args));
+struct xkb_keymap* (*sym_xkb_keymap_new_from_names)(
+ struct xkb_context *context,
+ const struct xkb_rule_names *names,
+ enum xkb_keymap_compile_flags flags);
+void (*sym_xkb_keymap_unref)(struct xkb_keymap *keymap);
+
+static int dlopen_xkbcommon(void) {
+ return dlopen_many_sym_or_warn(
+ &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG,
+ DLSYM_ARG(xkb_context_new),
+ DLSYM_ARG(xkb_context_unref),
+ DLSYM_ARG(xkb_context_set_log_fn),
+ DLSYM_ARG(xkb_keymap_new_from_names),
+ DLSYM_ARG(xkb_keymap_unref));
+}
+
+_printf_(3, 0)
+static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
+ const char *fmt;
+
+ fmt = strjoina("libxkbcommon: ", format);
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args);
+ REENABLE_WARNING;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xkb_context *, sym_xkb_context_unref, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xkb_keymap *, sym_xkb_keymap_unref, NULL);
+
+int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
+ _cleanup_(sym_xkb_context_unrefp) struct xkb_context *ctx = NULL;
+ _cleanup_(sym_xkb_keymap_unrefp) struct xkb_keymap *km = NULL;
+ const struct xkb_rule_names rmlvo = {
+ .model = model,
+ .layout = layout,
+ .variant = variant,
+ .options = options,
+ };
+ int r;
+
+ /* Compile keymap from RMLVO information to check out its validity */
+
+ r = dlopen_xkbcommon();
+ if (r < 0)
+ return r;
+
+ ctx = sym_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
+ if (!ctx)
+ return -ENOMEM;
+
+ sym_xkb_context_set_log_fn(ctx, log_xkb);
+
+ km = sym_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!km)
+ return -EINVAL;
+
+ return 0;
+}
+
+#endif
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#if HAVE_XKBCOMMON
+#include <xkbcommon/xkbcommon.h>
+
+extern struct xkb_context* (*sym_xkb_context_new)(enum xkb_context_flags flags);
+extern void (*sym_xkb_context_unref)(struct xkb_context *context);
+extern void (*sym_xkb_context_set_log_fn)(
+ struct xkb_context *context,
+ void (*log_fn)(
+ struct xkb_context *context,
+ enum xkb_log_level level,
+ const char *format,
+ va_list args));
+extern struct xkb_keymap* (*sym_xkb_keymap_new_from_names)(
+ struct xkb_context *context,
+ const struct xkb_rule_names *names,
+ enum xkb_keymap_compile_flags flags);
+extern void (*sym_xkb_keymap_unref)(struct xkb_keymap *keymap);
+
+int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options);
+
+#else
+
+static inline int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
+ return 0;
+}
+
+#endif
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0)
switch (c) {
do {
id = mfree(id);
- if (asprintf(&id, "c%lu", ++m->session_counter) < 0)
+ if (asprintf(&id, "c%" PRIu64, ++m->session_counter) < 0)
return -ENOMEM;
} while (hashmap_contains(m->sessions, id));
do {
id = mfree(id);
- if (asprintf(&id, "%lu", ++m->inhibit_counter) < 0)
+ if (asprintf(&id, "%" PRIu64, ++m->inhibit_counter) < 0)
return -ENOMEM;
} while (hashmap_get(m->inhibitors, id));
#include "path-util.h"
#include "signal-util.h"
#include "strv.h"
+#include "terminal-util.h"
#include "user-util.h"
static int property_get_user(
return sd_bus_reply_method_return(message, NULL);
}
+static int method_set_tty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Session *s = ASSERT_PTR(userdata);
+ int fd, r, flags;
+ _cleanup_free_ char *q = NULL;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "h", &fd);
+ if (r < 0)
+ return r;
+
+ if (!session_is_controller(s, sd_bus_message_get_sender(message)))
+ return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You must be in control of this session to set tty");
+
+ assert(fd >= 0);
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0)
+ return -errno;
+ if ((flags & O_ACCMODE) != O_RDWR)
+ return -EACCES;
+ if (FLAGS_SET(flags, O_PATH))
+ return -ENOTTY;
+
+ r = getttyname_malloc(fd, &q);
+ if (r < 0)
+ return r;
+
+ r = session_set_tty(s, q);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Session *s = ASSERT_PTR(userdata);
uint32_t major, minor;
SD_BUS_NO_RESULT,
method_set_display,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("SetTTY",
+ SD_BUS_ARGS("h", tty_fd),
+ SD_BUS_NO_RESULT,
+ method_set_tty,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("TakeDevice",
SD_BUS_ARGS("u", major, "u", minor),
SD_BUS_RESULT("h", fd, "b", inactive),
if (!hashmap_isempty(s->devices)) {
fprintf(f, "DEVICES=");
HASHMAP_FOREACH(sd, s->devices)
- fprintf(f, "%u:%u ", major(sd->dev), minor(sd->dev));
+ fprintf(f, DEVNUM_FORMAT_STR " ", DEVNUM_FORMAT_VAL(sd->dev));
fprintf(f, "\n");
}
}
return 1;
}
+int session_set_tty(Session *s, const char *tty) {
+ int r;
+
+ assert(s);
+ assert(tty);
+
+ r = free_and_strdup(&s->tty, tty);
+ if (r <= 0) /* 0 means the strings were equal */
+ return r;
+
+ session_save(s);
+
+ session_send_changed(s, "TTY", NULL);
+
+ return 1;
+}
+
static int session_dispatch_fifo(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
Session *s = ASSERT_PTR(userdata);
static void session_restore_vt(Session *s) {
int r;
+ if (s->vtfd < 0)
+ return;
+
r = vt_restore(s->vtfd);
if (r == -EIO) {
int vt, old_fd;
void session_set_locked_hint(Session *s, bool b);
void session_set_type(Session *s, SessionType t);
int session_set_display(Session *s, const char *display);
+int session_set_tty(Session *s, const char *tty);
int session_create_fifo(Session *s);
int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
char **kill_only_users, **kill_exclude_users;
bool kill_user_processes;
- unsigned long session_counter;
- unsigned long inhibit_counter;
+ uint64_t session_counter;
+ uint64_t inhibit_counter;
Hashmap *session_units;
Hashmap *user_units;
send_interface="org.freedesktop.login1.Session"
send_member="SetDisplay"/>
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Session"
+ send_member="SetTTY"/>
+
<allow receive_sender="org.freedesktop.login1"/>
</policy>
/* Talk to logind over the message bus */
- r = pam_acquire_bus_connection(handle, &bus);
+ r = pam_acquire_bus_connection(handle, "pam-systemd", &bus);
if (r != PAM_SUCCESS)
return r;
/* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
* not going to use the bus connection in that time, so let's better close before the daemon kicks us
* off because we are not processing anything. */
- (void) pam_release_bus_connection(handle);
+ (void) pam_release_bus_connection(handle, "pam-systemd");
return PAM_SUCCESS;
}
/* Before we go and close the FIFO we need to tell logind that this is a clean session
* shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
- r = pam_acquire_bus_connection(handle, &bus);
+ r = pam_acquire_bus_connection(handle, "pam-systemd", &bus);
if (r != PAM_SUCCESS)
return r;
* ./test-session-properties /org/freedesktop/login1/session/_32
*/
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-locator.h"
+#include "path-util.h"
#include "string-util.h"
+#include "terminal-util.h"
#include "tests.h"
static BusLocator session;
assert_se(isempty(display));
}
+/* Tests org.freedesktop.logind.Session SetTTY */
+TEST(set_tty) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus* bus = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *tty = NULL;
+ const char *path = "/dev/tty2"; /* testsuite uses tty2 */
+ int fd;
+
+ fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
+ assert_se(fd >= 0);
+
+ assert_se(sd_bus_open_system(&bus) >= 0);
+
+ /* tty can only be set by the session controller (which we're not ATM) */
+ assert_se(bus_call_method(bus, &session, "SetTTY", &error, NULL, "h", fd) < 0);
+ assert_se(sd_bus_error_has_name(&error, BUS_ERROR_NOT_IN_CONTROL));
+
+ assert_se(bus_call_method(bus, &session, "TakeControl", NULL, NULL, "b", true) >= 0);
+
+ /* tty can be set */
+ assert_se(bus_call_method(bus, &session, "SetTTY", NULL, NULL, "h", fd) >= 0);
+ tty = mfree(tty);
+ assert_se(bus_get_property_string(bus, &session, "TTY", NULL, &tty) >= 0);
+ assert_se(streq(tty, "tty2"));
+}
+
static int intro(void) {
if (saved_argc <= 1)
return EXIT_FAILURE;
static char *arg_image = NULL;
static bool arg_commit = false;
static bool arg_print = false;
+static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
printf("%s [OPTIONS...]\n"
"\n%sInitialize /etc/machine-id from a random source.%s\n\n"
- " -h --help Show this help\n"
- " --version Show package version\n"
- " --root=PATH Operate relative to root path\n"
- " --image=PATH Operate relative to image file\n"
- " --commit Commit transient ID\n"
- " --print Print used machine ID\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ " --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
+ " --commit Commit transient ID\n"
+ " --print Print used machine ID\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ARG_VERSION = 0x100,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_COMMIT,
ARG_PRINT,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "root", required_argument, NULL, ARG_ROOT },
- { "image", required_argument, NULL, ARG_IMAGE },
- { "commit", no_argument, NULL, ARG_COMMIT },
- { "print", no_argument, NULL, ARG_PRINT },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "commit", no_argument, NULL, ARG_COMMIT },
+ { "print", no_argument, NULL, ARG_PRINT },
{}
};
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_COMMIT:
arg_commit = true;
break;
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |
DISSECT_IMAGE_RELAX_VAR_CHECK |
}
if (arg_commit) {
- const char *etc_machine_id;
-
r = machine_id_commit(arg_root);
if (r < 0)
return r;
- etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
- r = id128_read(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 {
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image);
+ r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image);
+ r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image);
+ r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image);
+ r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
}
static int edit_settings(int argc, char *argv[], void *userdata) {
- _cleanup_(edit_file_context_done) EditFileContext context = {
- .remove_parent = false,
- };
+ _cleanup_(edit_file_context_done) EditFileContext context = {};
int r;
if (!on_tty())
static int enable_machine(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- InstallChange *changes = NULL;
- size_t n_changes = 0;
const char *method;
sd_bus *bus = ASSERT_PTR(userdata);
int r;
+ bool enable;
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles";
+ enable = streq(argv[0], "enable");
+ method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
if (r < 0)
if (r < 0)
return bus_log_create_error(r);
- if (streq(argv[0], "enable")) {
+ if (enable) {
r = sd_bus_message_append(m, "s", "machines.target");
if (r < 0)
return bus_log_create_error(r);
if (r < 0)
return bus_log_create_error(r);
- if (streq(argv[0], "enable"))
+ if (enable)
r = sd_bus_message_append(m, "bb", false, false);
else
r = sd_bus_message_append(m, "b", false);
if (r < 0)
return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
- if (streq(argv[0], "enable")) {
+ if (enable) {
r = sd_bus_message_read(reply, "b", NULL);
if (r < 0)
return bus_log_parse_error(r);
}
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
r = bus_call_method(bus, bus_systemd_mgr, "Reload", &error, NULL, NULL);
- if (r < 0) {
- log_error("Failed to reload daemon: %s", bus_error_message(&error, r));
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
if (arg_now) {
_cleanup_strv_free_ char **new_args = NULL;
- new_args = strv_new(streq(argv[0], "enable") ? "start" : "poweroff");
- if (!new_args) {
- r = log_oom();
- goto finish;
- }
+ new_args = strv_new(enable ? "start" : "poweroff");
+ if (!new_args)
+ return log_oom();
r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates = */ false);
- if (r < 0) {
- log_oom();
- goto finish;
- }
+ if (r < 0)
+ return log_oom();
- if (streq(argv[0], "enable"))
- r = start_machine(strv_length(new_args), new_args, userdata);
- else
- r = poweroff_machine(strv_length(new_args), new_args, userdata);
- }
+ if (enable)
+ return start_machine(strv_length(new_args), new_args, userdata);
-finish:
- install_changes_free(changes, n_changes);
+ return poweroff_machine(strv_length(new_args), new_args, userdata);
+ }
- return r;
+ return 0;
}
static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) {
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+
for (;;) {
static const char option_string[] = "-hp:als:H:M:qn:o:E:";
#include "bus-locator.h"
#include "bus-unit-util.h"
#include "bus-wait-for-jobs.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "device-util.h"
#include "dirent-util.h"
#include "escape.h"
if (!u)
return log_oom();
- r = chase_symlinks(u, NULL, 0, &arg_mount_what, NULL);
+ r = chase(u, NULL, 0, &arg_mount_what, NULL);
if (r < 0)
return log_error_errno(r, "Failed to make path %s absolute: %m", u);
} else {
if (argc > optind+1) {
if (arg_transport == BUS_TRANSPORT_LOCAL) {
- r = chase_symlinks(argv[optind+1], NULL, CHASE_NONEXISTENT, &arg_mount_where, NULL);
+ r = chase(argv[optind+1], NULL, CHASE_NONEXISTENT, &arg_mount_where, NULL);
if (r < 0)
return log_error_errno(r, "Failed to make path %s absolute: %m", argv[optind+1]);
} else {
if (!u)
return log_oom();
- r = chase_symlinks(u, NULL, 0, &p, NULL);
+ r = chase(u, NULL, 0, &p, NULL);
if (r < 0) {
r2 = log_error_errno(r, "Failed to make path %s absolute: %m", argv[i]);
continue;
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)
return 0;
}
- assert((uintptr_t) group % __alignof__(struct nexthop_grp) == 0);
+ assert((uintptr_t) group % alignof(struct nexthop_grp) == 0);
n_group = raw_group_size / sizeof(struct nexthop_grp);
for (size_t i = 0; i < n_group; i++) {
#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)
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
assert(directory);
assert(name || uid_is_valid(uid));
- r = chase_symlinks_and_fopen_unlocked("/etc/passwd", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
+ r = chase_and_fopen_unlocked("/etc/passwd", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
if (r == -ENOENT)
return 0; /* no user database? then no user, hence no collision */
if (r < 0)
assert(directory);
assert(name || gid_is_valid(gid));
- r = chase_symlinks_and_fopen_unlocked("/etc/group", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
+ r = chase_and_fopen_unlocked("/etc/group", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
if (r == -ENOENT)
return 0; /* no group database? then no group, hence no collision */
if (r < 0)
Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership)
Files.BindUser, config_parse_bind_user, 0, offsetof(Settings, bind_user)
Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network)
-Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces)
-Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan)
-Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan)
+Network.Interface, config_parse_network_iface_pair, 0, offsetof(Settings, network_interfaces)
+Network.MACVLAN, config_parse_macvlan_iface_pair, 0, offsetof(Settings, network_macvlan)
+Network.IPVLAN, config_parse_ipvlan_iface_pair, 0, offsetof(Settings, network_ipvlan)
Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth)
Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0
Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge)
#include <linux/magic.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "escape.h"
#include "fd-util.h"
#include "format-util.h"
if (!tmpfs_tmp && FLAGS_SET(mount_table[k].mount_settings, MOUNT_APPLY_TMPFS_TMP))
continue;
- r = chase_symlinks(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where, NULL);
+ r = chase(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].where);
* mounts to be created within the container image before we transition into it. Note
* that MOUNT_IN_USERNS is run after we transitioned hence prefixing is not ncessary
* for those. */
- r = chase_symlinks(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL);
+ r = chase(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].what);
}
if (stat(m->source, &source_st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", m->source);
- r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+ r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
if (r > 0) { /* Path exists already? */
assert(dest);
assert(m);
- r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+ r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
if (r == 0) { /* Doesn't exist yet? */
assert(dest);
assert(m);
- r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+ r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
if (r == 0) { /* Doesn't exist yet? */
assert(dest);
assert(m);
- r = chase_symlinks_and_stat(m->destination, dest, CHASE_PREFIX_ROOT, &where, &st);
+ r = chase_and_stat(m->destination, dest, CHASE_PREFIX_ROOT, &where, &st);
if (r < 0) {
log_full_errno(m->graceful ? LOG_DEBUG : LOG_ERR, r, "Failed to resolve %s/%s: %m", dest, m->destination);
return m->graceful ? 0 : r;
assert(dest);
assert(m);
- r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+ r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
if (r == 0) { /* Doesn't exist yet? */
return remove_one_link(rtnl, bridge_name);
}
-int test_network_interface_initialized(const char *name) {
+static int test_network_interface_initialized(const char *name) {
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
int r;
return 0;
}
-int move_network_interfaces(int netns_fd, char **ifaces) {
+int test_network_interfaces_initialized(char **iface_pairs) {
+ int r;
+ STRV_FOREACH_PAIR(a, b, iface_pairs) {
+ r = test_network_interface_initialized(*a);
+ if (r < 0)
+ return r;
+ }
+ return 0;
+}
+
+int move_network_interfaces(int netns_fd, char **iface_pairs) {
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
int r;
- if (strv_isempty(ifaces))
+ if (strv_isempty(iface_pairs))
return 0;
r = sd_netlink_open(&rtnl);
if (r < 0)
return log_error_errno(r, "Failed to connect to netlink: %m");
- STRV_FOREACH(i, ifaces) {
+ STRV_FOREACH_PAIR(i, b, iface_pairs) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int ifi;
if (r < 0)
return log_error_errno(r, "Failed to append namespace fd to netlink message: %m");
+ if (!streq(*b, *i)) {
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, *b);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add netlink interface name: %m");
+ }
+
r = sd_netlink_call(rtnl, m, 0, NULL);
if (r < 0)
return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i);
return 0;
}
-int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
+int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs) {
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
unsigned idx = 0;
int r;
- if (strv_isempty(ifaces))
+ if (strv_isempty(iface_pairs))
return 0;
r = sd_netlink_open(&rtnl);
if (r < 0)
return log_error_errno(r, "Failed to connect to netlink: %m");
- STRV_FOREACH(i, ifaces) {
+ STRV_FOREACH_PAIR(i, b, iface_pairs) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
- _cleanup_free_ char *n = NULL, *a = NULL;
+ _cleanup_free_ char *n = NULL;
+ int shortened, ifi;
struct ether_addr mac;
- int ifi;
ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
if (ifi < 0)
if (r < 0)
return log_error_errno(r, "Failed to add netlink interface index: %m");
- n = strjoin("mv-", *i);
+ n = strdup(*b);
if (!n)
return log_oom();
- r = shorten_ifname(n);
- if (r > 0) {
- a = strjoin("mv-", *i);
- if (!a)
- return log_oom();
- }
+ shortened = shorten_ifname(n);
r = sd_netlink_message_append_string(m, IFLA_IFNAME, n);
if (r < 0)
if (r < 0)
return log_error_errno(r, "Failed to add new macvlan interfaces: %m");
- (void) set_alternative_ifname(rtnl, n, a);
+ if (shortened > 0)
+ (void) set_alternative_ifname(rtnl, n, *b);
}
return 0;
}
-int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
+int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs) {
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
int r;
- if (strv_isempty(ifaces))
+ if (strv_isempty(iface_pairs))
return 0;
r = sd_netlink_open(&rtnl);
if (r < 0)
return log_error_errno(r, "Failed to connect to netlink: %m");
- STRV_FOREACH(i, ifaces) {
+ STRV_FOREACH_PAIR(i, b, iface_pairs) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
- _cleanup_free_ char *n = NULL, *a = NULL;
- int ifi;
+ _cleanup_free_ char *n = NULL;
+ int shortened, ifi ;
ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
if (ifi < 0)
if (r < 0)
return log_error_errno(r, "Failed to add netlink interface index: %m");
- n = strjoin("iv-", *i);
+ n = strdup(*b);
if (!n)
return log_oom();
- r = shorten_ifname(n);
- if (r > 0) {
- a = strjoin("iv-", *i);
- if (!a)
- return log_oom();
- }
+ shortened = shorten_ifname(n);
r = sd_netlink_message_append_string(m, IFLA_IFNAME, n);
if (r < 0)
if (r < 0)
return log_error_errno(r, "Failed to add new ipvlan interfaces: %m");
- (void) set_alternative_ifname(rtnl, n, a);
+ if (shortened > 0)
+ (void) set_alternative_ifname(rtnl, n, *b);
}
return 0;
return 0;
}
+
+static int network_iface_pair_parse(const char* iftype, char ***l, const char *p, const char* ifprefix) {
+ _cleanup_free_ char *a = NULL, *b = NULL;
+ int r;
+
+ r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract first word in %s parameter: %m", iftype);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Short read while reading %s parameter: %m", iftype);
+ if (!ifname_valid(a))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s, interface name not valid: %s", iftype, a);
+
+ if (isempty(p)) {
+ if (ifprefix)
+ b = strjoin(ifprefix, a);
+ else
+ b = strdup(a);
+ } else
+ b = strdup(p);
+ if (!b)
+ return log_oom();
+
+ if (!ifname_valid(b))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s, interface name not valid: %s", iftype, b);
+
+ r = strv_push_pair(l, a, b);
+ if (r < 0)
+ return log_oom();
+
+ a = b = NULL;
+ return 0;
+}
+
+int interface_pair_parse(char ***l, const char *p) {
+ return network_iface_pair_parse("Network interface", l, p, NULL);
+}
+
+int macvlan_pair_parse(char ***l, const char *p) {
+ return network_iface_pair_parse("MACVLAN network interface", l, p, "mv-");
+}
+
+int ipvlan_pair_parse(char ***l, const char *p) {
+ return network_iface_pair_parse("IPVLAN network interface", l, p, "iv-");
+}
#include <stdbool.h>
#include <sys/types.h>
-int test_network_interface_initialized(const char *name);
+int test_network_interfaces_initialized(char **iface_pairs);
int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge);
int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs);
int setup_bridge(const char *veth_name, const char *bridge_name, bool create);
int remove_bridge(const char *bridge_name);
-int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces);
-int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces);
+int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs);
+int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs);
-int move_network_interfaces(int netns_fd, char **ifaces);
+int move_network_interfaces(int netns_fd, char **iface_pairs);
int veth_extra_parse(char ***l, const char *p);
int remove_veth_links(const char *primary, char **pairs);
+
+int interface_pair_parse(char ***l, const char *p);
+int macvlan_pair_parse(char ***l, const char *p);
+int ipvlan_pair_parse(char ***l, const char *p);
return 0;
}
+int config_parse_network_iface_pair(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char*** l = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ return interface_pair_parse(l, rvalue);
+}
+
+int config_parse_macvlan_iface_pair(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char*** l = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ return macvlan_pair_parse(l, rvalue);
+}
+
+int config_parse_ipvlan_iface_pair(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char*** l = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ return ipvlan_pair_parse(l, rvalue);
+}
+
int config_parse_network_zone(
const char *unit,
const char *filename,
CONFIG_PARSER_PROTOTYPE(config_parse_overlay);
CONFIG_PARSER_PROTOTYPE(config_parse_inaccessible);
CONFIG_PARSER_PROTOTYPE(config_parse_veth_extra);
+CONFIG_PARSER_PROTOTYPE(config_parse_network_iface_pair);
+CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_iface_pair);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_iface_pair);
CONFIG_PARSER_PROTOTYPE(config_parse_network_zone);
CONFIG_PARSER_PROTOTYPE(config_parse_boot);
CONFIG_PARSER_PROTOTYPE(config_parse_pid2);
#include "cap-list.h"
#include "capability-util.h"
#include "cgroup-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "common-signal.h"
#include "copy.h"
#include "cpu-set-util.h"
static bool arg_suppress_sync = false;
static char *arg_settings_filename = NULL;
static Architecture arg_architecture = _ARCHITECTURE_INVALID;
+static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int handle_arg_console(const char *arg) {
if (streq(arg, "help")) {
" remove it after exit\n"
" -i --image=PATH Root file system disk image (or device node) for\n"
" the container\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
" --oci-bundle=PATH OCI bundle directory\n"
" --read-only Mount the root directory read-only\n"
" --volatile[=MODE] Run the system in volatile mode\n"
" --private-users-ownership=auto\n\n"
"%3$sNetworking:%4$s\n"
" --private-network Disable network in container\n"
- " --network-interface=INTERFACE\n"
+ " --network-interface=HOSTIF[:CONTAINERIF]\n"
" Assign an existing network interface to the\n"
" container\n"
- " --network-macvlan=INTERFACE\n"
+ " --network-macvlan=HOSTIF[:CONTAINERIF]\n"
" Create a macvlan network interface based on an\n"
" existing network interface to the container\n"
- " --network-ipvlan=INTERFACE\n"
+ " --network-ipvlan=HOSTIF[:CONTAINERIF]\n"
" Create an ipvlan network interface based on an\n"
" existing network interface to the container\n"
" -n --network-veth Add a virtual Ethernet connection between host\n"
ARG_LOAD_CREDENTIAL,
ARG_BIND_USER,
ARG_SUPPRESS_SYNC,
+ ARG_IMAGE_POLICY,
};
static const struct option options[] = {
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
{ "bind-user", required_argument, NULL, ARG_BIND_USER },
{ "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nUE:P", options, NULL)) >= 0)
switch (c) {
break;
case ARG_NETWORK_INTERFACE:
- if (!ifname_valid(optarg))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Network interface name not valid: %s", optarg);
-
- r = test_network_interface_initialized(optarg);
+ r = interface_pair_parse(&arg_network_interfaces, optarg);
if (r < 0)
return r;
- if (strv_extend(&arg_network_interfaces, optarg) < 0)
- return log_oom();
-
arg_private_network = true;
arg_settings_mask |= SETTING_NETWORK;
break;
case ARG_NETWORK_MACVLAN:
-
- if (!ifname_valid(optarg))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "MACVLAN network interface name not valid: %s", optarg);
-
- r = test_network_interface_initialized(optarg);
+ r = macvlan_pair_parse(&arg_network_macvlan, optarg);
if (r < 0)
return r;
- if (strv_extend(&arg_network_macvlan, optarg) < 0)
- return log_oom();
-
arg_private_network = true;
arg_settings_mask |= SETTING_NETWORK;
break;
case ARG_NETWORK_IPVLAN:
-
- if (!ifname_valid(optarg))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "IPVLAN network interface name not valid: %s", optarg);
-
- r = test_network_interface_initialized(optarg);
+ r = ipvlan_pair_parse(&arg_network_ipvlan, optarg);
if (r < 0)
return r;
- if (strv_extend(&arg_network_ipvlan, optarg) < 0)
- return log_oom();
-
_fallthrough_;
case ARG_PRIVATE_NETWORK:
arg_private_network = true;
arg_settings_mask |= SETTING_SUPPRESS_SYNC;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
return 0;
}
+static int verify_network_interfaces_initialized(void) {
+ int r;
+ r = test_network_interfaces_initialized(arg_network_interfaces);
+ if (r < 0)
+ return r;
+
+ r = test_network_interfaces_initialized(arg_network_macvlan);
+ if (r < 0)
+ return r;
+
+ r = test_network_interfaces_initialized(arg_network_ipvlan);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
int userns_lchown(const char *p, uid_t uid, gid_t gid) {
assert(p);
if (m == TIMEZONE_OFF)
return 0;
- r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
+ r = chase("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
if (r < 0) {
log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m");
return 0;
return 0; /* Already pointing to the right place? Then do nothing .. */
check = strjoina(dest, "/usr/share/zoneinfo/", z);
- r = chase_symlinks(check, dest, 0, NULL, NULL);
+ r = chase(check, dest, 0, NULL, NULL);
if (r < 0)
log_debug_errno(r, "Timezone %s does not exist (or is not accessible) in container, not creating symlink: %m", z);
else {
_cleanup_free_ char *resolved = NULL;
int found;
- found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved, NULL);
+ found = chase(where, dest, CHASE_NONEXISTENT, &resolved, NULL);
if (found < 0) {
log_warning_errno(found, "Failed to resolve /etc/localtime path in container, ignoring: %m");
return 0;
case TIMEZONE_COPY:
/* If mounting failed, try to copy */
- r = copy_file_atomic("/etc/localtime", where, 0644, 0, 0, COPY_REFLINK|COPY_REPLACE);
+ r = copy_file_atomic("/etc/localtime", where, 0644, COPY_REFLINK|COPY_REPLACE);
if (r < 0) {
log_full_errno(IN_SET(r, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
"Failed to copy /etc/localtime to %s, ignoring: %m", where);
if (m == RESOLV_CONF_OFF)
return 0;
- r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
+ r = chase("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
if (r < 0) {
log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m");
return 0;
_cleanup_free_ char *resolved = NULL;
int found;
- found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved, NULL);
+ found = chase(where, dest, CHASE_NONEXISTENT|CHASE_NOFOLLOW, &resolved, NULL);
if (found < 0) {
log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m");
return 0;
}
if (IN_SET(m, RESOLV_CONF_REPLACE_HOST, RESOLV_CONF_REPLACE_STATIC, RESOLV_CONF_REPLACE_UPLINK, RESOLV_CONF_REPLACE_STUB))
- r = copy_file_atomic(what, where, 0644, 0, 0, COPY_REFLINK|COPY_REPLACE);
+ r = copy_file_atomic(what, where, 0644, COPY_REFLINK|COPY_REPLACE);
else
- r = copy_file(what, where, O_TRUNC|O_NOFOLLOW, 0644, 0, 0, COPY_REFLINK);
+ r = copy_file(what, where, O_TRUNC|O_NOFOLLOW, 0644, COPY_REFLINK);
if (r < 0) {
/* If the file already exists as symlink, let's suppress the warning, under the assumption that
* resolved or something similar runs inside and the symlink points there.
}
static int setup_machine_id(const char *directory) {
- const char *etc_machine_id;
- 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). */
- etc_machine_id = prefix_roota(directory, "/etc/machine-id");
-
- r = id128_read(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;
return 0;
}
-static int chase_symlinks_and_update(char **p, unsigned flags) {
+static int chase_and_update(char **p, unsigned flags) {
char *chased;
int r;
if (!*p)
return 0;
- r = chase_symlinks(*p, NULL, flags, &chased, NULL);
+ r = chase(*p, NULL, flags, &chased, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve path %s: %m", *p);
* it again. Note that the other fds closed here are at least the locking and barrier fds. */
log_close();
log_set_open_when_needed(true);
+ log_settle_target();
(void) fdset_close_others(fds);
log_warning("Ignoring CPUAffinity= setting, file '%s' is not trusted.", path);
else {
cpu_set_reset(&arg_cpu_set);
- arg_cpu_set = settings->cpu_set;
- settings->cpu_set = (CPUSet) {};
+ arg_cpu_set = TAKE_STRUCT(settings->cpu_set);
}
}
_exit(EXIT_FAILURE);
}
+ /* Reverse network interfaces pair list so that interfaces get their initial name back.
+ * This is about ensuring interfaces get their old name back when being moved back. */
+ arg_network_interfaces = strv_reverse(arg_network_interfaces);
+
r = move_network_interfaces(parent_netns_fd, arg_network_interfaces);
if (r < 0)
log_error_errno(r, "Failed to move network interfaces back to parent network namespace: %m");
if (r < 0)
goto finish;
+ r = verify_network_interfaces_initialized();
+ if (r < 0)
+ goto finish;
+
/* Reapply environment settings. */
(void) detect_unified_cgroup_hierarchy_from_environment();
if (arg_ephemeral) {
_cleanup_free_ char *np = NULL;
- r = chase_symlinks_and_update(&arg_directory, 0);
+ r = chase_and_update(&arg_directory, 0);
if (r < 0)
goto finish;
free_and_replace(arg_directory, np);
remove_directory = true;
} else {
- r = chase_symlinks_and_update(&arg_directory, arg_template ? CHASE_NONEXISTENT : 0);
+ r = chase_and_update(&arg_directory, arg_template ? CHASE_NONEXISTENT : 0);
if (r < 0)
goto finish;
}
if (arg_template) {
- r = chase_symlinks_and_update(&arg_template, 0);
+ r = chase_and_update(&arg_template, 0);
if (r < 0)
goto finish;
assert(arg_image);
assert(!arg_template);
- r = chase_symlinks_and_update(&arg_image, 0);
+ r = chase_and_update(&arg_image, 0);
if (r < 0)
goto finish;
{
BLOCK_SIGNALS(SIGINT);
- r = copy_file(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600, FS_NOCOW_FL, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME|COPY_SIGINT);
+ r = copy_file_full(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600,
+ FS_NOCOW_FL, FS_NOCOW_FL,
+ COPY_REFLINK|COPY_CRTIME|COPY_SIGINT,
+ NULL, NULL);
}
if (r == -EINTR) {
log_error_errno(r, "Interrupted while copying image file to %s, removed again.", np);
r = dissect_loop_device_and_warn(
loop,
&arg_verity_settings,
- NULL,
+ /* mount_options=*/ NULL,
+ arg_image_policy ?: &image_policy_container,
dissect_image_flags,
&dissected_image);
if (r == -ENOPKG) {
else
clear_candidates = NULL;
- r = oomd_kill_by_pgscan_rate(m->monitored_mem_pressure_cgroup_contexts_candidates, t->path, m->dry_run, &selected);
+ r = oomd_kill_by_pgscan_rate(m->monitored_mem_pressure_cgroup_contexts_candidates,
+ /* prefix= */ t->path,
+ /* dry_run= */ m->dry_run,
+ &selected);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
if (sorted[i]->pgscan == 0 && sorted[i]->current_memory_usage == 0)
continue;
- r = oomd_cgroup_kill(sorted[i]->path, true, dry_run);
+ r = oomd_cgroup_kill(sorted[i]->path, /* recurse= */ true, /* dry_run= */ dry_run);
if (r == -ENOMEM)
return r; /* Treat oom as a hard error */
if (r < 0) {
if (sorted[i]->swap_usage <= threshold_usage)
continue;
- r = oomd_cgroup_kill(sorted[i]->path, true, dry_run);
+ r = oomd_cgroup_kill(sorted[i]->path, /* recurse= */ true, /* dry_run= */ dry_run);
if (r == -ENOMEM)
return r; /* Treat oom as a hard error */
if (r < 0) {
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "conf-parser.h"
#include "constants.h"
static sd_id128_t *arg_defer_partitions = NULL;
static size_t arg_n_defer_partitions = 0;
static uint64_t arg_sector_size = 0;
+static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef struct FreeArea FreeArea;
return 0;
}
+static int context_open_and_lock_backing_fd(Context *context, const char *node) {
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(context);
+ assert(node);
+
+ if (context->backing_fd >= 0)
+ return 0;
+
+ fd = open(node, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open device '%s': %m", node);
+
+ /* Tell udev not to interfere while we are processing the device */
+ if (flock(fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
+ return log_error_errno(errno, "Failed to lock device '%s': %m", node);
+
+ log_debug("Device %s opened and locked.", node);
+ context->backing_fd = TAKE_FD(fd);
+ return 1;
+}
+
static int context_load_partition_table(Context *context) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
else {
uint32_t ssz;
- if (context->backing_fd < 0) {
- context->backing_fd = open(context->node, O_RDONLY|O_CLOEXEC);
- if (context->backing_fd < 0)
- return log_error_errno(errno, "Failed to open device '%s': %m", context->node);
- }
+ r = context_open_and_lock_backing_fd(context, context->node);
+ if (r < 0)
+ return r;
/* Auto-detect sector size if not specified. */
r = probe_sector_size_prefer_ioctl(context->backing_fd, &ssz);
if (context->backing_fd < 0) {
/* If we have no fd referencing the device yet, make a copy of the fd now, so that we have one */
- context->backing_fd = fd_reopen(fdisk_get_devfd(c), O_RDONLY|O_CLOEXEC);
- if (context->backing_fd < 0)
- return log_error_errno(context->backing_fd, "Failed to duplicate fdisk fd: %m");
-
- /* Tell udev not to interfere while we are processing the device */
- if (flock(context->backing_fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
- return log_error_errno(errno, "Failed to lock block device: %m");
+ r = context_open_and_lock_backing_fd(context, FORMAT_PROC_FD_PATH(fdisk_get_devfd(c)));
+ if (r < 0)
+ return r;
}
/* The offsets/sizes libfdisk returns to us will be in multiple of the sector size of the
if (p)
gap = p->offset + p->new_size;
else
- gap = context->start;
+ /* The context start gets rounded up to grain_size, however
+ * existing partitions may be before that so ensure the gap
+ * starts at the first actually usable lba
+ */
+ gap = fdisk_get_first_lba(context->fdisk_context) * context->sector_size;
LIST_FOREACH(partitions, q, context->partitions) {
if (q->dropped)
}
if (next == UINT64_MAX) {
- next = context->end;
+ next = (fdisk_get_last_lba(context->fdisk_context) + 1) * context->sector_size;
if (gap > next)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end.");
}
_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)
if (rfd < 0)
return rfd;
- sfd = chase_symlinks_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
+ sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
if (sfd < 0)
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source);
STRV_FOREACH_PAIR(source, target, p->copy_files) {
_cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
- sfd = chase_symlinks_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
+ sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
if (sfd < 0)
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source);
return log_error_errno(r, "Failed to check type of source file '%s': %m", *source);
/* We are looking at a directory */
- tfd = chase_symlinks_and_open(*target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+ tfd = chase_and_open(*target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
if (tfd < 0) {
_cleanup_free_ char *dn = NULL, *fn = NULL;
if (r < 0)
return log_error_errno(r, "Failed to create parent directory '%s': %m", dn);
- pfd = chase_symlinks_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+ pfd = chase_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
if (pfd < 0)
return log_error_errno(pfd, "Failed to open parent directory of target: %m");
if (r < 0)
return log_error_errno(r, "Failed to create parent directory: %m");
- pfd = chase_symlinks_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+ pfd = chase_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
if (pfd < 0)
return log_error_errno(pfd, "Failed to open parent directory of target: %m");
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;
}
if (!st)
return log_oom();
- r = chase_symlinks_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
+ r = chase_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
if (r == -ENOENT)
return 0;
if (r < 0)
return 0;
if (!arg_randomize) {
- _cleanup_close_ int fd = -EBADF;
-
- fd = chase_symlinks_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);
assert(path);
- r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
+ r = chase(path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r < 0)
return r;
if (p->copy_blocks_path) {
- source_fd = chase_symlinks_and_open(p->copy_blocks_path, p->copy_blocks_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
+ source_fd = chase_and_open(p->copy_blocks_path, p->copy_blocks_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
if (source_fd < 0)
return log_error_errno(source_fd, "Failed to open '%s': %m", p->copy_blocks_path);
_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. */
" --can-factory-reset Test whether factory reset is defined\n"
" --root=PATH Operate relative to root path\n"
" --image=PATH Operate relative to image file\n"
+ " --image-policy=POLICY\n"
+ " Specify disk image dissection policy\n"
" --definitions=DIR Find partition definitions in specified directory\n"
" --key-file=PATH Key to use when encrypting partitions\n"
" --private-key=PATH Private key to use when generating verity roothash\n"
ARG_CAN_FACTORY_RESET,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_SEED,
ARG_PRETTY,
ARG_DEFINITIONS,
ARG_EXCLUDE_PARTITIONS,
ARG_DEFER_PARTITIONS,
ARG_SECTOR_SIZE,
+ ARG_SKIP_PARTITIONS,
};
static const struct option options[] = {
{ "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "seed", required_argument, NULL, ARG_SEED },
{ "pretty", required_argument, NULL, ARG_PRETTY },
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_SEED:
if (isempty(optarg)) {
arg_seed = SD_ID128_NULL;
char **ret,
int *ret_fd) {
- _cleanup_free_ char *found_path = NULL;
+ _cleanup_free_ char *found_path = NULL, *node = NULL;
dev_t devno, fd_devno = MODE_INVALID;
_cleanup_close_ int fd = -EBADF;
struct stat st;
assert(ret);
assert(ret_fd);
- fd = chase_symlinks_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path);
+ fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path);
if (fd < 0)
return fd;
if (r < 0)
log_debug_errno(r, "Failed to find whole disk block device for '%s', ignoring: %m", p);
- r = devname_from_devnum(S_IFBLK, devno, ret);
+ r = devname_from_devnum(S_IFBLK, devno, &node);
if (r < 0)
return log_debug_errno(r, "Failed to determine canonical path for '%s': %m", p);
/* Only if we still look at the same block device we can reuse the fd. Otherwise return an
* invalidated fd. */
- *ret_fd = fd_devno != MODE_INVALID && fd_devno == devno ? TAKE_FD(fd) : -1;
+ if (fd_devno != MODE_INVALID && fd_devno == devno) {
+ /* Tell udev not to interfere while we are processing the device */
+ if (flock(fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
+ return log_error_errno(errno, "Failed to lock device '%s': %m", node);
+
+ *ret_fd = TAKE_FD(fd);
+ } else
+ *ret_fd = -EBADF;
+
+ *ret = TAKE_PTR(node);
return 0;
}
* systems */
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_MOUNT_READ_ONLY |
(arg_node ? DISSECT_IMAGE_DEVICE_READ_ONLY : 0) | /* If a different node to make changes to is specified let's open the device in read-only mode) */
DISSECT_IMAGE_GPT_ONLY |
#include "bus-common-errors.h"
#include "bus-error.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "copy.h"
#include "data-fd-util.h"
#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);
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
- r = chase_symlinks_and_opendir(*i, where, 0, &resolved, &d);
+ r = chase_and_opendir(*i, where, 0, &resolved, &d);
if (r < 0) {
log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i);
continue;
bool path_is_extension,
bool relax_extension_release_check,
char **matches,
+ const ImagePolicy *image_policy,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
sd_bus_error *error) {
r = dissect_loop_device(
d,
- NULL, NULL,
+ /* verity= */ NULL,
+ /* mount_options= */ NULL,
+ image_policy,
DISSECT_IMAGE_READ_ONLY |
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
char **extension_image_paths,
bool validate_sysext,
bool relax_extension_release_check,
+ const ImagePolicy *image_policy,
Image **ret_image,
OrderedHashmap **ret_extension_images,
OrderedHashmap **ret_extension_releases,
}
}
- r = portable_extract_by_path(image->path, /* path_is_extension= */ false, /* relax_extension_release_check= */ false, matches, &os_release, &unit_files, error);
+ r = portable_extract_by_path(
+ image->path,
+ /* path_is_extension= */ false,
+ /* relax_extension_release_check= */ false,
+ matches,
+ image_policy,
+ &os_release,
+ &unit_files,
+ error);
if (r < 0)
return r;
* extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
* the units are started. Also, collect valid portable prefixes if caller requested that. */
if (validate_sysext || ret_valid_prefixes) {
- _cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *prefixes = NULL;
- r = take_fdopen_unlocked(&os_release->fd, "r", &f);
- if (r < 0)
- return r;
-
- r = parse_env_file(f, os_release->name,
- "ID", &id,
- "VERSION_ID", &version_id,
- "SYSEXT_LEVEL", &sysext_level,
- "PORTABLE_PREFIXES", &prefixes);
+ r = parse_env_file_fd(os_release->fd, os_release->name,
+ "ID", &id,
+ "VERSION_ID", &version_id,
+ "SYSEXT_LEVEL", &sysext_level,
+ "PORTABLE_PREFIXES", &prefixes);
if (r < 0)
return r;
if (isempty(id))
_cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL;
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
_cleanup_strv_free_ char **extension_release = NULL;
- _cleanup_close_ int extension_release_fd = -EBADF;
- _cleanup_fclose_ FILE *f = NULL;
const char *e;
- r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, relax_extension_release_check, matches, &extension_release_meta, &extra_unit_files, error);
+ r = portable_extract_by_path(
+ ext->path,
+ /* path_is_extension= */ true,
+ relax_extension_release_check,
+ matches,
+ image_policy,
+ &extension_release_meta,
+ &extra_unit_files,
+ error);
if (r < 0)
return r;
if (!validate_sysext && !ret_valid_prefixes && !ret_extension_releases)
continue;
- /* We need to keep the fd valid, to return the PortableMetadata to the caller. */
- extension_release_fd = fd_reopen(extension_release_meta->fd, O_CLOEXEC|O_RDONLY);
- if (extension_release_fd < 0)
- return extension_release_fd;
-
- r = take_fdopen_unlocked(&extension_release_fd, "r", &f);
- if (r < 0)
- return r;
-
- r = load_env_file_pairs(f, extension_release_meta->name, &extension_release);
+ r = load_env_file_pairs_fd(extension_release_meta->fd, extension_release_meta->name, &extension_release);
if (r < 0)
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)
const char *name_or_path,
char **matches,
char **extension_image_paths,
+ const ImagePolicy *image_policy,
PortableFlags flags,
PortableMetadata **ret_os_release,
OrderedHashmap **ret_extension_releases,
extension_image_paths,
/* validate_sysext= */ false,
/* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
+ image_policy,
&image,
&extension_images,
&extension_releases,
return 0;
}
+static int append_release_log_fields(
+ char **text,
+ const PortableMetadata *release,
+ ImageClass type,
+ const char *field_name) {
+
+ static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
+ [IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "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_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_SYSEXT));
+ assert(!strv_isempty((char *const *)field_ids[type]));
+ assert(!strv_isempty((char *const *)field_versions[type]));
+ assert(field_name);
+ assert(text);
+
+ if (!release)
+ return 0; /* Nothing to do. */
+
+ r = load_env_file_pairs_fd(release->fd, release->name, &fields);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse '%s': %m", release->name);
+
+ /* Find an ID first, in order of preference from more specific to less specific: IMAGE_ID -> ID */
+ id = strv_find_first_field((char *const *)field_ids[type], fields);
+
+ /* Then the version, same logic, prefer the more specific one */
+ version = strv_find_first_field((char *const *)field_versions[type], fields);
+
+ /* If there's no valid version to be found, simply omit it. */
+ if (!id && !version)
+ return 0;
+
+ if (!strextend(text,
+ "LogExtraFields=",
+ field_name,
+ "=",
+ strempty(id),
+ id && version ? "_" : "",
+ strempty(version),
+ "\n"))
+ return -ENOMEM;
+
+ return 0;
+}
+
static int install_chroot_dropin(
const char *image_path,
ImageType type,
OrderedHashmap *extension_images,
+ OrderedHashmap *extension_releases,
const PortableMetadata *m,
+ const PortableMetadata *os_release,
const char *dropin_dir,
PortableFlags flags,
char **ret_dropin,
"LogExtraFields=PORTABLE=", base_name, "\n"))
return -ENOMEM;
+ /* If we have a single image then PORTABLE= will point to it, so we add
+ * PORTABLE_NAME_AND_VERSION= with the os-release fields and we are done. But if we have
+ * extensions, PORTABLE= will point to the image where the current unit was found in. So we
+ * also list PORTABLE_ROOT= and PORTABLE_ROOT_NAME_AND_VERSION= for the base image, and
+ * PORTABLE_EXTENSION= and PORTABLE_EXTENSION_NAME_AND_VERSION= for each extension, so that
+ * all needed metadata is available. */
+ if (ordered_hashmap_isempty(extension_images))
+ r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_NAME_AND_VERSION");
+ else {
+ _cleanup_free_ char *root_base_name = NULL;
+
+ r = path_extract_filename(image_path, &root_base_name);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to extract basename from '%s': %m", image_path);
+
+ if (!strextend(&text,
+ "Environment=PORTABLE_ROOT=", root_base_name, "\n",
+ "LogExtraFields=PORTABLE_ROOT=", root_base_name, "\n"))
+ return -ENOMEM;
+
+ r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_ROOT_NAME_AND_VERSION");
+ }
+ if (r < 0)
+ return r;
+
if (m->image_path && !path_equal(m->image_path, image_path))
- ORDERED_HASHMAP_FOREACH(ext, extension_images)
+ ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+ _cleanup_free_ char *extension_base_name = NULL;
+
+ r = path_extract_filename(ext->path, &extension_base_name);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path);
+
if (!strextend(&text,
+ "\n",
extension_setting_from_image(ext->type),
ext->path,
/* With --force tell PID1 to avoid enforcing that the image <name> and
* extension-release.<name> have to match. */
!IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) &&
FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT) ?
- ":x-systemd.relax-extension-release-check" :
- "",
- "\n"))
+ ":x-systemd.relax-extension-release-check\n" :
+ "\n",
+ /* In PORTABLE= we list the 'main' image name for this unit
+ * (the image where the unit was extracted from), but we are
+ * stacking multiple images, so list those too. */
+ "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n"))
return -ENOMEM;
+
+ /* Look for image/version identifiers in the extension release files. We
+ * look for all possible IDs, but typically only 1 or 2 will be set, so
+ * the number of fields added shouldn't be too large. We prefix the DDI
+ * name to the value, so that we can add the same field multiple times and
+ * still be able to identify what applies to what. */
+ r = append_release_log_fields(&text,
+ ordered_hashmap_get(extension_releases, ext->name),
+ IMAGE_SYSEXT,
+ "PORTABLE_EXTENSION_NAME_AND_VERSION");
+ if (r < 0)
+ return r;
+ }
}
r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
if (flags & PORTABLE_PREFER_COPY) {
- r = copy_file_atomic(from, dropin, 0644, 0, 0, COPY_REFLINK);
+ r = copy_file_atomic(from, dropin, 0644, COPY_REFLINK);
if (r < 0)
return log_debug_errno(r, "Failed to copy %s %s %s: %m", from, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), dropin);
const char *image_path,
ImageType type,
OrderedHashmap *extension_images,
+ OrderedHashmap *extension_releases,
const PortableMetadata *m,
+ const PortableMetadata *os_release,
const char *profile,
PortableFlags flags,
PortableChange **changes,
* is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
* all for PID 1. */
- r = install_chroot_dropin(image_path, type, extension_images, m, dropin_dir, flags, &chroot_dropin, changes, n_changes);
+ r = install_chroot_dropin(image_path, type, extension_images, extension_releases, m, os_release, dropin_dir, flags, &chroot_dropin, changes, n_changes);
if (r < 0)
return r;
char **matches,
const char *profile,
char **extension_image_paths,
+ const ImagePolicy *image_policy,
PortableFlags flags,
PortableChange **changes,
size_t *n_changes,
sd_bus_error *error) {
- _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
+ _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
+ _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
_cleanup_(lookup_paths_free) LookupPaths paths = {};
_cleanup_strv_free_ char **valid_prefixes = NULL;
extension_image_paths,
/* validate_sysext= */ true,
/* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
+ image_policy,
&image,
&extension_images,
- /* extension_releases= */ NULL,
- /* os_release= */ NULL,
+ &extension_releases,
+ &os_release,
&unit_files,
&valid_prefixes,
error);
}
HASHMAP_FOREACH(item, unit_files) {
- r = attach_unit_file(&paths, image->path, image->type, extension_images,
- item, profile, flags, changes, n_changes);
+ r = attach_unit_file(&paths, image->path, image->type, extension_images, extension_releases,
+ item, os_release, profile, flags, changes, n_changes);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name);
}
#include "sd-bus.h"
+#include "dissect-image.h"
#include "hashmap.h"
#include "macro.h"
#include "set.h"
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
-int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
+int portable_extract(const char *image, char **matches, char **extension_image_paths, const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
-int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
+int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, const ImagePolicy* image_policy, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_get_state(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableState *ret, sd_bus_error *error);
#include "bus-locator.h"
#include "bus-unit-util.h"
#include "bus-wait-for-jobs.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "constants.h"
#include "dirent-util.h"
#include "env-file.h"
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Operations on images by path not supported when connecting to remote systems.");
- r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
+ r = chase(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
if (r < 0)
return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
nl = true;
} else {
_cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL,
- *id = NULL, *version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL;
+ *sysext_id = NULL, *sysext_version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL,
+ *id = NULL, *version_id = NULL, *image_id = NULL, *image_version = NULL, *build_id = NULL;
_cleanup_fclose_ FILE *f = NULL;
f = fmemopen_unlocked((void*) data, sz, "r");
return log_error_errno(errno, "Failed to open extension-release buffer: %m");
r = parse_env_file(f, name,
- "ID", &id,
- "VERSION_ID", &version_id,
+ "SYSEXT_ID", &sysext_id,
+ "SYSEXT_VERSION_ID", &sysext_version_id,
+ "SYSEXT_BUILD_ID", &build_id,
+ "SYSEXT_IMAGE_ID", &image_id,
+ "SYSEXT_IMAGE_VERSION", &image_version,
+ "SYSEXT_PRETTY_NAME", &pretty_os,
"SYSEXT_SCOPE", &sysext_scope,
"SYSEXT_LEVEL", &sysext_level,
+ "ID", &id,
+ "VERSION_ID", &version_id,
"PORTABLE_PRETTY_NAME", &pretty_portable,
- "PORTABLE_PREFIXES", &portable_prefixes,
- "PRETTY_NAME", &pretty_os);
+ "PORTABLE_PREFIXES", &portable_prefixes);
if (r < 0)
return log_error_errno(r, "Failed to parse extension release from '%s': %m", name);
printf("Extension:\n\t%s\n"
"\tExtension Scope:\n\t\t%s\n"
"\tExtension Compatibility Level:\n\t\t%s\n"
+ "\tExtension Compatibility OS:\n\t\t%s\n"
+ "\tExtension Compatibility OS Version:\n\t\t%s\n"
"\tPortable Service:\n\t\t%s\n"
"\tPortable Prefixes:\n\t\t%s\n"
- "\tOperating System:\n\t\t%s (%s %s)\n",
+ "\tExtension Image:\n\t\t%s%s%s %s%s%s\n",
name,
strna(sysext_scope),
strna(sysext_level),
+ strna(id),
+ strna(version_id),
strna(pretty_portable),
strna(portable_prefixes),
- strna(pretty_os),
- strna(id),
- strna(version_id));
+ strempty(pretty_os),
+ pretty_os ? " (" : "ID: ",
+ strna(sysext_id ?: image_id),
+ pretty_os ? "" : "Version: ",
+ strna(sysext_version_id ?: image_version ?: build_id),
+ pretty_os ? ")" : "");
}
r = sd_bus_message_exit_container(reply);
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_strv_free_ char **names = NULL;
- InstallChange *changes = NULL;
const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
- size_t n_changes = 0;
int r;
if (!arg_enable)
return bus_log_parse_error(r);
}
- (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
- install_changes_free(changes, n_changes);
+ (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
return 0;
}
assert(message);
+ CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
/* Note that we do not redirect detaching to the image object here, because we want to allow that users can
* detach already deleted images too, in case the user already deleted an image before properly detaching
* it. */
&n_changes,
error);
if (r < 0)
- goto finish;
-
- r = reply_portable_changes(message, changes, n_changes);
+ return r;
-finish:
- portable_changes_free(changes, n_changes);
- return r;
+ return reply_portable_changes(message, changes, n_changes);
}
static int method_reattach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return 1;
if (!image->metadata_valid) {
- r = image_read_metadata(image);
+ r = image_read_metadata(image, &image_policy_service);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
if (d) {
assert(d->fd >= 0);
- f = take_fdopen(&d->fd, "r");
- if (!f)
- return -errno;
+ r = fdopen_independent(d->fd, "r", &f);
+ if (r < 0)
+ return r;
r = read_full_stream(f, &buf, &n);
if (r < 0)
image->path,
matches,
extension_images,
+ /* image_policy= */ NULL,
flags,
&os_release,
&extension_releases,
assert(message);
assert(name_or_path || image);
+ CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
if (!m) {
assert(image);
m = image->userdata;
matches,
profile,
extension_images,
+ /* image_policy= */ NULL,
flags,
&changes,
&n_changes,
error);
if (r < 0)
- goto finish;
-
- r = reply_portable_changes(message, changes, n_changes);
+ return r;
-finish:
- portable_changes_free(changes, n_changes);
- return r;
+ return reply_portable_changes(message, changes, n_changes);
}
static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
assert(message);
+ CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
r = sd_bus_message_read_strv(message, &extension_images);
if (r < 0)
&n_changes,
error);
if (r < 0)
- goto finish;
-
- r = reply_portable_changes(message, changes, n_changes);
+ return r;
-finish:
- portable_changes_free(changes, n_changes);
- return r;
+ return reply_portable_changes(message, changes, n_changes);
}
int bus_image_common_remove(
assert(message);
assert(name_or_path || image);
+ CLEANUP_ARRAY(changes_detached, n_changes_detached, portable_changes_free);
+ CLEANUP_ARRAY(changes_attached, n_changes_attached, portable_changes_free);
+ CLEANUP_ARRAY(changes_gone, n_changes_gone, portable_changes_free);
+
if (!m) {
assert(image);
m = image->userdata;
&n_changes_detached,
error);
if (r < 0)
- goto finish;
+ return r;
r = portable_attach(
sd_bus_message_get_bus(message),
matches,
profile,
extension_images,
+ /* image_policy= */ NULL,
flags,
&changes_attached,
&n_changes_attached,
error);
if (r < 0)
- goto finish;
+ return r;
/* We want to return the list of units really removed by the detach,
* and not added again by the attach */
changes_detached, n_changes_detached,
&changes_gone, &n_changes_gone);
if (r < 0)
- goto finish;
+ return r;
/* First, return the units that are gone (so that the caller can stop them)
* Then, return the units that are changed/added (so that the caller can
* start/restart/enable them) */
- r = reply_portable_changes_pair(message,
- changes_gone, n_changes_gone,
- changes_attached, n_changes_attached);
- if (r < 0)
- goto finish;
-
-finish:
- portable_changes_free(changes_detached, n_changes_detached);
- portable_changes_free(changes_attached, n_changes_attached);
- portable_changes_free(changes_gone, n_changes_gone);
- return r;
+ return reply_portable_changes_pair(message,
+ changes_gone, n_changes_gone,
+ changes_attached, n_changes_attached);
}
static int bus_image_method_reattach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
r = mkdir_parents(ofd_path, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create directory %s: %m", ofd_path);
- r = copy_file_atomic(ifd_path, ofd_path, 0600, 0, 0, COPY_REPLACE);
+ r = copy_file_atomic(ifd_path, ofd_path, 0600, COPY_REPLACE);
if (r < 0)
return log_error_errno(r, "Failed to copy_file_atomic: %s to %s", ifd_path, ofd_path);
}
/* 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)
switch (cmsg->cmsg_type) {
case IPV6_PKTINFO: {
- struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+ struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
if (s->ifindex <= 0)
s->ifindex = i->ipi6_ifindex;
}
case IPV6_HOPLIMIT:
- s->ttl = *(int *) CMSG_DATA(cmsg);
+ s->ttl = *CMSG_TYPED_DATA(cmsg, int);
break;
}
switch (cmsg->cmsg_type) {
case IP_PKTINFO: {
- struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
+ struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
if (s->ifindex <= 0)
s->ifindex = i->ipi_ifindex;
}
case IP_TTL:
- s->ttl = *(int *) CMSG_DATA(cmsg);
+ s->ttl = *CMSG_TYPED_DATA(cmsg, int);
break;
}
}
unsigned nr = 0;
int r;
+ assert(hosts);
+
for (;;) {
_cleanup_free_ char *line = NULL;
char *l;
strip_localhost(&t);
etc_hosts_clear(hosts);
- *hosts = t;
- t = (EtcHosts) {}; /* prevent cleanup */
+ *hosts = TAKE_STRUCT(t);
return 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;
}
switch (cmsg->cmsg_type) {
case IPV6_PKTINFO: {
- struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+ struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
if (p->ifindex <= 0)
p->ifindex = i->ipi6_ifindex;
}
case IPV6_HOPLIMIT:
- p->ttl = *(int *) CMSG_DATA(cmsg);
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
break;
case IPV6_RECVFRAGSIZE:
- p->fragsize = *(int *) CMSG_DATA(cmsg);
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
break;
}
} else if (cmsg->cmsg_level == IPPROTO_IP) {
switch (cmsg->cmsg_type) {
case IP_PKTINFO: {
- struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
+ struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
if (p->ifindex <= 0)
p->ifindex = i->ipi_ifindex;
}
case IP_TTL:
- p->ttl = *(int *) CMSG_DATA(cmsg);
+ p->ttl = *CMSG_TYPED_DATA(cmsg, int);
break;
case IP_RECVFRAGSIZE:
- p->fragsize = *(int *) CMSG_DATA(cmsg);
+ p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
break;
}
}
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
- pi = (struct in_pktinfo*) CMSG_DATA(cmsg);
+ pi = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
pi->ipi_ifindex = ifindex;
if (source)
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
- pi = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+ pi = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
pi->ipi6_ifindex = ifindex;
if (source)
static const char *arg_description = NULL;
static const char *arg_slice = NULL;
static bool arg_slice_inherit = false;
+static bool arg_expand_environment = true;
static bool arg_send_sighup = false;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static const char *arg_host = NULL;
" --description=TEXT Description for unit\n"
" --slice=SLICE Run in the specified slice\n"
" --slice-inherit Inherit the slice\n"
+ " --expand-environment=BOOL Control expansion of environment variables\n"
" --no-block Do not wait until operation finished\n"
" -r --remain-after-exit Leave service around until explicitly stopped\n"
" --wait Wait until service stopped again\n"
ARG_DESCRIPTION,
ARG_SLICE,
ARG_SLICE_INHERIT,
+ ARG_EXPAND_ENVIRONMENT,
ARG_SEND_SIGHUP,
ARG_SERVICE_TYPE,
ARG_EXEC_USER,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "user", no_argument, NULL, ARG_USER },
- { "system", no_argument, NULL, ARG_SYSTEM },
- { "scope", no_argument, NULL, ARG_SCOPE },
- { "unit", required_argument, NULL, 'u' },
- { "description", required_argument, NULL, ARG_DESCRIPTION },
- { "slice", required_argument, NULL, ARG_SLICE },
- { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
- { "remain-after-exit", no_argument, NULL, 'r' },
- { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP },
- { "host", required_argument, NULL, 'H' },
- { "machine", required_argument, NULL, 'M' },
- { "service-type", required_argument, NULL, ARG_SERVICE_TYPE },
- { "wait", no_argument, NULL, ARG_WAIT },
- { "uid", required_argument, NULL, ARG_EXEC_USER },
- { "gid", required_argument, NULL, ARG_EXEC_GROUP },
- { "nice", required_argument, NULL, ARG_NICE },
- { "setenv", required_argument, NULL, 'E' },
- { "property", required_argument, NULL, 'p' },
- { "tty", no_argument, NULL, 't' }, /* deprecated alias */
- { "pty", no_argument, NULL, 't' },
- { "pipe", no_argument, NULL, 'P' },
- { "quiet", no_argument, NULL, 'q' },
- { "on-active", required_argument, NULL, ARG_ON_ACTIVE },
- { "on-boot", required_argument, NULL, ARG_ON_BOOT },
- { "on-startup", required_argument, NULL, ARG_ON_STARTUP },
- { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE },
- { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE },
- { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR },
- { "on-timezone-change",no_argument, NULL, ARG_ON_TIMEZONE_CHANGE},
- { "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE },
- { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY },
- { "path-property", required_argument, NULL, ARG_PATH_PROPERTY },
- { "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY },
- { "no-block", no_argument, NULL, ARG_NO_BLOCK },
- { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
- { "collect", no_argument, NULL, 'G' },
- { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
- { "same-dir", no_argument, NULL, 'd' },
- { "shell", no_argument, NULL, 'S' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "user", no_argument, NULL, ARG_USER },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "scope", no_argument, NULL, ARG_SCOPE },
+ { "unit", required_argument, NULL, 'u' },
+ { "description", required_argument, NULL, ARG_DESCRIPTION },
+ { "slice", required_argument, NULL, ARG_SLICE },
+ { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
+ { "remain-after-exit", no_argument, NULL, 'r' },
+ { "expand-environment", required_argument, NULL, ARG_EXPAND_ENVIRONMENT },
+ { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP },
+ { "host", required_argument, NULL, 'H' },
+ { "machine", required_argument, NULL, 'M' },
+ { "service-type", required_argument, NULL, ARG_SERVICE_TYPE },
+ { "wait", no_argument, NULL, ARG_WAIT },
+ { "uid", required_argument, NULL, ARG_EXEC_USER },
+ { "gid", required_argument, NULL, ARG_EXEC_GROUP },
+ { "nice", required_argument, NULL, ARG_NICE },
+ { "setenv", required_argument, NULL, 'E' },
+ { "property", required_argument, NULL, 'p' },
+ { "tty", no_argument, NULL, 't' }, /* deprecated alias */
+ { "pty", no_argument, NULL, 't' },
+ { "pipe", no_argument, NULL, 'P' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "on-active", required_argument, NULL, ARG_ON_ACTIVE },
+ { "on-boot", required_argument, NULL, ARG_ON_BOOT },
+ { "on-startup", required_argument, NULL, ARG_ON_STARTUP },
+ { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE },
+ { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE },
+ { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR },
+ { "on-timezone-change", no_argument, NULL, ARG_ON_TIMEZONE_CHANGE },
+ { "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE },
+ { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY },
+ { "path-property", required_argument, NULL, ARG_PATH_PROPERTY },
+ { "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY },
+ { "no-block", no_argument, NULL, ARG_NO_BLOCK },
+ { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+ { "collect", no_argument, NULL, 'G' },
+ { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
+ { "same-dir", no_argument, NULL, 'd' },
+ { "shell", no_argument, NULL, 'S' },
{},
};
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tPqGdSu:", options, NULL)) >= 0)
switch (c) {
arg_slice_inherit = true;
break;
+ case ARG_EXPAND_ENVIRONMENT:
+ r = parse_boolean_argument("--expand-environment=", optarg, &arg_expand_environment);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_SEND_SIGHUP:
arg_send_sighup = true;
break;
bool send_term = false;
int r;
+ /* We disable environment expansion on the server side via ExecStartEx=:.
+ * ExecStartEx was added relatively recently (v243), and some bugs were fixed only later.
+ * So use that feature only if required. It will fail with older systemds. */
+ bool use_ex_prop = !arg_expand_environment;
+
assert(m);
r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property);
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_append(m, "s", "ExecStart");
+ r = sd_bus_message_append(m, "s",
+ use_ex_prop ? "ExecStartEx" : "ExecStart");
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_open_container(m, 'v', "a(sasb)");
+ r = sd_bus_message_open_container(m, 'v',
+ use_ex_prop ? "a(sasas)" : "a(sasb)");
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_open_container(m, 'a', "(sasb)");
+ r = sd_bus_message_open_container(m, 'a',
+ use_ex_prop ? "(sasas)" : "(sasb)");
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_open_container(m, 'r', "sasb");
+ r = sd_bus_message_open_container(m, 'r',
+ use_ex_prop ? "sasas" : "sasb");
if (r < 0)
return bus_log_create_error(r);
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_append(m, "b", false);
+ if (use_ex_prop)
+ r = sd_bus_message_append_strv(
+ m,
+ STRV_MAKE(arg_expand_environment ? NULL : "no-env-expand"));
+ else
+ r = sd_bus_message_append(m, "b", false);
if (r < 0)
return bus_log_create_error(r);
return 0;
}
-static int start_transient_service(
+static int make_transient_service_unit(
+ sd_bus *bus,
+ sd_bus_message **message,
+ const char *service,
+ const char *pty_path) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ int r;
+
+ assert(bus);
+ assert(message);
+ assert(service);
+
+ r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* Name and mode */
+ r = sd_bus_message_append(m, "ss", service, "fail");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* Properties */
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = transient_service_set_properties(m, pty_path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* Auxiliary units */
+ r = sd_bus_message_append(m, "a(sa(sv))", 0);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ *message = TAKE_PTR(m);
+ return 0;
+}
+
+static int bus_call_with_hint(
sd_bus *bus,
- int *retval) {
+ sd_bus_message *message,
+ const char *name,
+ sd_bus_message **reply) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+ r = sd_bus_call(bus, message, 0, &error, reply);
+ if (r < 0) {
+ log_error_errno(r, "Failed to start transient %s unit: %s", name, bus_error_message(&error, r));
+
+ if (!arg_expand_environment &&
+ sd_bus_error_has_names(&error,
+ SD_BUS_ERROR_UNKNOWN_PROPERTY,
+ SD_BUS_ERROR_PROPERTY_READ_ONLY))
+ log_notice_errno(r, "Hint: --expand-environment=no is not supported by old systemd");
+ }
+
+ return r;
+}
+
+static int start_transient_service(sd_bus *bus) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
int r;
assert(bus);
- assert(retval);
if (arg_stdio == ARG_STDIO_PTY) {
return r;
}
- r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
- if (r < 0)
- return bus_log_create_error(r);
-
- /* Name and mode */
- r = sd_bus_message_append(m, "ss", service, "fail");
- if (r < 0)
- return bus_log_create_error(r);
-
- /* Properties */
- r = sd_bus_message_open_container(m, 'a', "(sv)");
- if (r < 0)
- return bus_log_create_error(r);
-
- r = transient_service_set_properties(m, pty_path);
+ r = make_transient_service_unit(bus, &m, service, pty_path);
if (r < 0)
return r;
- r = sd_bus_message_close_container(m);
- if (r < 0)
- return bus_log_create_error(r);
-
- /* Auxiliary units */
- r = sd_bus_message_append(m, "a(sa(sv))", 0);
- if (r < 0)
- return bus_log_create_error(r);
-
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- r = sd_bus_call(bus, m, 0, &error, &reply);
+ r = bus_call_with_hint(bus, m, "service", &reply);
if (r < 0)
- return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r));
+ return r;
if (w) {
const char *object;
/* Try to propagate the service's return value. But if the service defines
* e.g. SuccessExitStatus, honour this, and return 0 to mean "success". */
if (streq_ptr(c.result, "success"))
- *retval = 0;
- else if (streq_ptr(c.result, "exit-code") && c.exit_status > 0)
- *retval = c.exit_status;
- else if (streq_ptr(c.result, "signal"))
- *retval = EXIT_EXCEPTION;
- else
- *retval = EXIT_FAILURE;
+ return EXIT_SUCCESS;
+ if (streq_ptr(c.result, "exit-code") && c.exit_status > 0)
+ return c.exit_status;
+ if (streq_ptr(c.result, "signal"))
+ return EXIT_EXCEPTION;
+ return EXIT_FAILURE;
}
- return 0;
+ return EXIT_SUCCESS;
}
static int acquire_invocation_id(sd_bus *bus, sd_id128_t *ret) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
- _cleanup_strv_free_ char **env = NULL, **user_env = NULL;
+ _cleanup_strv_free_ char **env = NULL, **user_env = NULL, **expanded_cmdline = NULL;
_cleanup_free_ char *scope = NULL;
const char *object = NULL;
sd_id128_t invocation_id;
if (!arg_quiet)
log_info("Running scope as unit: %s", scope);
+ if (arg_expand_environment) {
+ expanded_cmdline = replace_env_argv(arg_cmdline, env);
+ if (!expanded_cmdline)
+ return log_oom();
+ arg_cmdline = expanded_cmdline;
+ }
+
execvpe(arg_cmdline[0], arg_cmdline, env);
return log_error_errno(errno, "Failed to execute: %m");
}
-static int start_transient_trigger(
+static int make_transient_trigger_unit(
sd_bus *bus,
- const char *suffix) {
+ sd_bus_message **message,
+ const char *suffix,
+ const char *trigger,
+ const char *service) {
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
- _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
- _cleanup_free_ char *trigger = NULL, *service = NULL;
- const char *object = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
int r;
assert(bus);
-
- r = bus_wait_for_jobs_new(bus, &w);
- if (r < 0)
- return log_oom();
-
- if (arg_unit) {
- switch (unit_name_to_type(arg_unit)) {
-
- case UNIT_SERVICE:
- service = strdup(arg_unit);
- if (!service)
- return log_oom();
-
- r = unit_name_change_suffix(service, suffix, &trigger);
- if (r < 0)
- return log_error_errno(r, "Failed to change unit suffix: %m");
- break;
-
- case UNIT_TIMER:
- trigger = strdup(arg_unit);
- if (!trigger)
- return log_oom();
-
- r = unit_name_change_suffix(trigger, ".service", &service);
- if (r < 0)
- return log_error_errno(r, "Failed to change unit suffix: %m");
- break;
-
- default:
- r = unit_name_mangle_with_suffix(arg_unit, "as unit",
- arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
- ".service", &service);
- if (r < 0)
- return log_error_errno(r, "Failed to mangle unit name: %m");
-
- r = unit_name_mangle_with_suffix(arg_unit, "as trigger",
- arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
- suffix, &trigger);
- if (r < 0)
- return log_error_errno(r, "Failed to mangle unit name: %m");
-
- break;
- }
- } else {
- r = make_unit_name(bus, UNIT_SERVICE, &service);
- if (r < 0)
- return r;
-
- r = unit_name_change_suffix(service, suffix, &trigger);
- if (r < 0)
- return log_error_errno(r, "Failed to change unit suffix: %m");
- }
+ assert(message);
+ assert(suffix);
+ assert(trigger);
+ assert(service);
r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
if (r < 0)
if (r < 0)
return bus_log_create_error(r);
+ *message = TAKE_PTR(m);
+ return 0;
+}
+
+static int start_transient_trigger(sd_bus *bus, const char *suffix) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+ _cleanup_free_ char *trigger = NULL, *service = NULL;
+ const char *object = NULL;
+ int r;
+
+ assert(bus);
+ assert(suffix);
+
+ r = bus_wait_for_jobs_new(bus, &w);
+ if (r < 0)
+ return log_oom();
+
+ if (arg_unit) {
+ switch (unit_name_to_type(arg_unit)) {
+
+ case UNIT_SERVICE:
+ service = strdup(arg_unit);
+ if (!service)
+ return log_oom();
+
+ r = unit_name_change_suffix(service, suffix, &trigger);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change unit suffix: %m");
+ break;
+
+ case UNIT_TIMER:
+ trigger = strdup(arg_unit);
+ if (!trigger)
+ return log_oom();
+
+ r = unit_name_change_suffix(trigger, ".service", &service);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change unit suffix: %m");
+ break;
+
+ default:
+ r = unit_name_mangle_with_suffix(arg_unit, "as unit",
+ arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
+ ".service", &service);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mangle unit name: %m");
+
+ r = unit_name_mangle_with_suffix(arg_unit, "as trigger",
+ arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
+ suffix, &trigger);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mangle unit name: %m");
+
+ break;
+ }
+ } else {
+ r = make_unit_name(bus, UNIT_SERVICE, &service);
+ if (r < 0)
+ return r;
+
+ r = unit_name_change_suffix(service, suffix, &trigger);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change unit suffix: %m");
+ }
+
+ r = make_transient_trigger_unit(bus, &m, suffix, trigger, service);
+ if (r < 0)
+ return r;
+
polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
- r = sd_bus_call(bus, m, 0, &error, &reply);
+ r = bus_call_with_hint(bus, m, suffix + 1, &reply);
if (r < 0)
- return log_error_errno(r, "Failed to start transient %s unit: %s", suffix + 1, bus_error_message(&error, r));
+ return r;
r = sd_bus_message_read(reply, "o", &object);
if (r < 0)
log_info("Will run service as unit: %s", service);
}
- return 0;
+ return EXIT_SUCCESS;
}
static bool shall_make_executable_absolute(void) {
static int run(int argc, char* argv[]) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *description = NULL;
- int r, retval = EXIT_SUCCESS;
+ int r;
log_show_color(true);
log_parse_environment();
return bus_log_connect_error(r, arg_transport);
if (arg_scope)
- r = start_transient_scope(bus);
- else if (arg_path_property)
- r = start_transient_trigger(bus, ".path");
- else if (arg_socket_property)
- r = start_transient_trigger(bus, ".socket");
- else if (arg_with_timer)
- r = start_transient_trigger(bus, ".timer");
- else
- r = start_transient_service(bus, &retval);
- if (r < 0)
- return r;
-
- return retval;
+ return start_transient_scope(bus);
+ if (arg_path_property)
+ return start_transient_trigger(bus, ".path");
+ if (arg_socket_property)
+ return start_transient_trigger(bus, ".socket");
+ if (arg_with_timer)
+ return start_transient_trigger(bus, ".timer");
+ return start_transient_service(bus);
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "boot-entry.h"
+#include "chase.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "id128-util.h"
+#include "os-util.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "utf8.h"
+
+bool boot_entry_token_valid(const char *p) {
+ return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p);
+}
+
+static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType *type, char **token) {
+ _cleanup_free_ char *buf = NULL, *p = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(type);
+ assert(*type == BOOT_ENTRY_TOKEN_AUTO);
+ assert(token);
+
+ if (!etc_kernel)
+ return 0;
+
+ p = path_join(etc_kernel, "entry-token");
+ if (!p)
+ return log_oom();
+
+ r = chase_and_fopenat_unlocked(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to chase and open '%s': %m", p);
+
+ r = read_line(f, NAME_MAX, &buf);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", p);
+
+ if (isempty(buf))
+ return 0;
+
+ if (!boot_entry_token_valid(buf)) {
+ log_debug("Invalid entry token specified in %s, ignoring.", p);
+ return 0;
+ }
+
+ *token = TAKE_PTR(buf);
+ *type = BOOT_ENTRY_TOKEN_LITERAL;
+ return 1;
+}
+
+static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) {
+ char *p;
+
+ assert(type);
+ assert(IN_SET(*type, BOOT_ENTRY_TOKEN_AUTO, BOOT_ENTRY_TOKEN_MACHINE_ID));
+ assert(token);
+
+ if (sd_id128_is_null(machine_id))
+ return 0;
+
+ p = strdup(SD_ID128_TO_STRING(machine_id));
+ if (!p)
+ return log_oom();
+
+ *token = p;
+ *type = BOOT_ENTRY_TOKEN_MACHINE_ID;
+ return 1;
+}
+
+static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char **token) {
+ _cleanup_free_ char *id = NULL, *image_id = NULL;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(type);
+ assert(IN_SET(*type, BOOT_ENTRY_TOKEN_AUTO, BOOT_ENTRY_TOKEN_OS_IMAGE_ID, BOOT_ENTRY_TOKEN_OS_ID));
+ assert(token);
+
+ switch (*type) {
+ case BOOT_ENTRY_TOKEN_AUTO:
+ r = parse_os_release_at(rfd,
+ "IMAGE_ID", &image_id,
+ "ID", &id);
+ break;
+
+ case BOOT_ENTRY_TOKEN_OS_IMAGE_ID:
+ r = parse_os_release_at(rfd, "IMAGE_ID", &image_id);
+ break;
+
+ case BOOT_ENTRY_TOKEN_OS_ID:
+ r = parse_os_release_at(rfd, "ID", &id);
+ break;
+
+ default:
+ assert_not_reached();
+ }
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to load /etc/os-release: %m");
+
+ if (!isempty(image_id) && boot_entry_token_valid(image_id)) {
+ *token = TAKE_PTR(image_id);
+ *type = BOOT_ENTRY_TOKEN_OS_IMAGE_ID;
+ return 1;
+ }
+
+ if (!isempty(id) && boot_entry_token_valid(id)) {
+ *token = TAKE_PTR(id);
+ *type = BOOT_ENTRY_TOKEN_OS_ID;
+ return 1;
+ }
+
+ return 0;
+}
+
+int boot_entry_token_ensure_at(
+ int rfd,
+ const char *etc_kernel,
+ sd_id128_t machine_id,
+ bool machine_id_is_random,
+ BootEntryTokenType *type,
+ char **token) {
+
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(type);
+ assert(token);
+
+ if (*token)
+ return 0; /* Already set. */
+
+ switch (*type) {
+
+ case BOOT_ENTRY_TOKEN_AUTO:
+ r = entry_token_load(rfd, etc_kernel, type, token);
+ if (r != 0)
+ return r;
+
+ if (!machine_id_is_random) {
+ r = entry_token_from_machine_id(machine_id, type, token);
+ if (r != 0)
+ return r;
+ }
+
+ r = entry_token_from_os_release(rfd, type, token);
+ if (r != 0)
+ return r;
+
+ if (machine_id_is_random) {
+ r = entry_token_from_machine_id(machine_id, type, token);
+ if (r != 0)
+ return r;
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No machine ID set, and /etc/os-release carries no ID=/IMAGE_ID= fields.");
+
+ case BOOT_ENTRY_TOKEN_MACHINE_ID:
+ r = entry_token_from_machine_id(machine_id, type, token);
+ if (r != 0)
+ return r;
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set.");
+
+ case BOOT_ENTRY_TOKEN_OS_IMAGE_ID:
+ r = entry_token_from_os_release(rfd, type, token);
+ if (r != 0)
+ return r;
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "IMAGE_ID= field not set in /etc/os-release.");
+
+ case BOOT_ENTRY_TOKEN_OS_ID:
+ r = entry_token_from_os_release(rfd, type, token);
+ if (r != 0)
+ return r;
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "ID= field not set in /etc/os-release.");
+
+ case BOOT_ENTRY_TOKEN_LITERAL:
+ /* In this case, the token should be already set by the user input. */
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+}
+
+int boot_entry_token_ensure(
+ const char *root,
+ const char *etc_kernel,
+ sd_id128_t machine_id,
+ bool machine_id_is_random,
+ BootEntryTokenType *type,
+ char **token) {
+
+ assert(token);
+
+ if (*token)
+ return 0; /* Already set. */
+
+ _cleanup_close_ int rfd = -EBADF;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+
+ return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token);
+}
+
+int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) {
+ assert(s);
+ assert(type);
+ assert(token);
+
+ /*
+ * This function is intended to be used in command line parsers, to handle token that are passed in.
+ *
+ * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON SUCCESS!
+ * Hence, do not pass in uninitialized pointers.
+ */
+
+ if (streq(s, "machine-id")) {
+ *type = BOOT_ENTRY_TOKEN_MACHINE_ID;
+ *token = mfree(*token);
+ return 0;
+ }
+
+ if (streq(s, "os-image-id")) {
+ *type = BOOT_ENTRY_TOKEN_OS_IMAGE_ID;
+ *token = mfree(*token);
+ return 0;
+ }
+
+ if (streq(s, "os-id")) {
+ *type = BOOT_ENTRY_TOKEN_OS_ID;
+ *token = mfree(*token);
+ return 0;
+ }
+
+ const char *e = startswith(s, "literal:");
+ if (e) {
+ if (!boot_entry_token_valid(e))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid entry token literal is specified for --entry-token=.");
+
+ *type = BOOT_ENTRY_TOKEN_LITERAL;
+ return free_and_strdup_warn(token, e);
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unexpected parameter for --entry-token=: %s", s);
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-id128.h"
+
+typedef enum BootEntryTokenType {
+ BOOT_ENTRY_TOKEN_MACHINE_ID,
+ BOOT_ENTRY_TOKEN_OS_IMAGE_ID,
+ BOOT_ENTRY_TOKEN_OS_ID,
+ BOOT_ENTRY_TOKEN_LITERAL,
+ BOOT_ENTRY_TOKEN_AUTO,
+} BootEntryTokenType;
+
+bool boot_entry_token_valid(const char *p);
+
+int boot_entry_token_ensure(
+ const char *root,
+ const char *etc_kernel, /* will be prefixed with root, typically /etc/kernel. */
+ sd_id128_t machine_id,
+ bool machine_id_is_random,
+ BootEntryTokenType *type, /* input and output */
+ char **token); /* output, but do not pass uninitialized value. */
+int boot_entry_token_ensure_at(
+ int rfd,
+ const char *etc_kernel,
+ sd_id128_t machine_id,
+ bool machine_id_is_random,
+ BootEntryTokenType *type,
+ char **token);
+
+int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token);
#include "bootspec-fundamental.h"
#include "bootspec.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "devnum-util.h"
#include "dirent-util.h"
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);
return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while parsing: %m");
}
- *entry = tmp;
- tmp = (BootEntry) {};
+ *entry = TAKE_STRUCT(tmp);
return 0;
}
assert(config);
assert(path);
- r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
+ r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
if (r == -ENOENT)
return 0;
if (r < 0)
assert(root);
assert(dir);
- dir_fd = chase_symlinks_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
+ dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
if (dir_fd == -ENOENT)
return 0;
if (dir_fd < 0)
return log_oom();
}
- *ret = tmp;
- tmp = (BootEntry) {};
+ *ret = TAKE_STRUCT(tmp);
return 0;
}
assert(config);
assert(dir);
- r = chase_symlinks_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
+ r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
assert(p);
assert(ret_status);
- int status = chase_symlinks_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
+ int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
/* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
* the absence of color support) to the user that the boot loader is only interested in the second
}
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 0;
}
-int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags) {
+int bpf_map_new(
+ const char *name,
+ enum bpf_map_type type,
+ size_t key_size,
+ size_t value_size,
+ size_t max_entries,
+ uint32_t flags) {
+
union bpf_attr attr;
+ const char *n = name;
zero(attr);
attr.map_type = type;
attr.max_entries = max_entries;
attr.map_flags = flags;
+ /* The map name is primarily informational for debugging purposes, and typically too short
+ * to carry the full unit name, hence we employ a trivial lossy escaping to make it fit
+ * (truncation + only alphanumerical, "." and "_" are allowed as per
+ * https://www.kernel.org/doc/html/next/bpf/maps.html#usage-notes) */
+ for (size_t i = 0; i < sizeof(attr.map_name) - 1 && *n; i++, n++)
+ attr.map_name[i] = strchr(ALPHANUMERICAL ".", *n) ? *n : '_';
+
return RET_NERRNO(bpf(BPF_MAP_CREATE, &attr, sizeof(attr)));
}
extern const struct hash_ops bpf_program_hash_ops;
-int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags);
+int bpf_map_new(const char *name, enum bpf_map_type type, size_t key_size, size_t value_size,
+ size_t max_entries, uint32_t flags);
int bpf_map_update_element(int fd, const void *key, void *value);
int bpf_map_lookup_element(int fd, const void *key, void *value);
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);
s = is_soft ? strndupa_safe(property, is_soft - property) : property;
/* Skip over any prefix, such as "Default" */
- assert_se(p = strstr(s, "Limit"));
+ assert_se(p = strstrafter(s, "Limit"));
- z = rlimit_from_string(p + 5);
+ z = rlimit_from_string(p);
assert(z >= 0);
(void) getrlimit(z, &buf);
"ProcSubset",
"NetworkNamespacePath",
"IPCNamespacePath",
- "LogNamespace"))
+ "LogNamespace",
+ "RootImagePolicy",
+ "MountImagePolicy",
+ "ExtensionImagePolicy"))
return bus_append_string(m, field, eq);
if (STR_IN_SET(field, "IgnoreSIGPIPE",
"USBFunctionStrings",
"OOMPolicy",
"TimeoutStartFailureMode",
- "TimeoutStopFailureMode"))
+ "TimeoutStopFailureMode",
+ "FileDescriptorStorePreserve"))
return bus_append_string(m, field, eq);
if (STR_IN_SET(field, "PermissionsStartOnly",
return 0;
}
-int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, InstallChange **changes, size_t *n_changes) {
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet) {
const char *type, *path, *source;
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
int r;
- /* changes is dereferenced when calling install_changes_dump() later,
- * so we have to make sure this is not NULL. */
- assert(changes);
- assert(n_changes);
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
if (r < 0)
continue;
}
- r = install_changes_add(changes, n_changes, t, path, source);
+ r = install_changes_add(&changes, &n_changes, t, path, source);
if (r < 0)
return r;
}
if (r < 0)
return bus_log_parse_error(r);
- install_changes_dump(0, NULL, *changes, *n_changes, quiet);
+ install_changes_dump(0, NULL, changes, n_changes, quiet);
+
return 0;
}
int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const char *assignment);
int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char **l);
-int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, InstallChange **changes, size_t *n_changes);
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet);
int unit_load_state(sd_bus *bus, const char *name, char **load_state);
}
static int condition_test_kernel_command_line(Condition *c, char **env) {
- _cleanup_free_ char *line = NULL;
+ _cleanup_strv_free_ char **args = NULL;
int r;
assert(c);
assert(c->parameter);
assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv(&args);
if (r < 0)
return r;
bool equal = strchr(c->parameter, '=');
- for (const char *p = line;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(word, args) {
bool found;
- r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
if (equal)
- found = streq(word, c->parameter);
+ found = streq(*word, c->parameter);
else {
const char *f;
- f = startswith(word, c->parameter);
+ f = startswith(*word, c->parameter);
found = f && IN_SET(*f, 0, '=');
}
copy_progress_bytes_t progress,
void *userdata) {
+ _cleanup_close_ int fdf_opened = -EBADF, fdt_opened = -EBADF;
bool try_cfr = true, try_sendfile = true, try_splice = true, copied_something = false;
int r, nonblock_pipe = -1;
size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */
if (ret_remains_size)
*ret_remains_size = 0;
+ fdf = fd_reopen_condition(fdf, O_CLOEXEC | O_NOCTTY | O_RDONLY, O_PATH, &fdf_opened);
+ if (fdf < 0)
+ return fdf;
+ fdt = fd_reopen_condition(fdt, O_CLOEXEC | O_NOCTTY | O_RDWR, O_PATH, &fdt_opened);
+ if (fdt < 0)
+ return fdt;
+
/* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of
* source and destination first. */
if ((copy_flags & COPY_REFLINK)) {
return 0;
}
-int copy_file_fd_full(
+int copy_file_fd_at_full(
+ int dir_fdf,
const char *from,
int fdt,
CopyFlags copy_flags,
struct stat st;
int r;
+ assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD);
assert(from);
assert(fdt >= 0);
- fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ fdf = openat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fdf < 0)
return -errno;
return r;
}
-int copy_file_atomic_full(
+int copy_file_atomic_at_full(
+ int dir_fdf,
const char *from,
+ int dir_fdt,
const char *to,
mode_t mode,
unsigned chattr_flags,
assert(to);
if (copy_flags & COPY_MAC_CREATE) {
- r = mac_selinux_create_file_prepare(to, S_IFREG);
+ r = mac_selinux_create_file_prepare_at(dir_fdt, to, S_IFREG);
if (r < 0)
return r;
}
- fdt = open_tmpfile_linkable(to, O_WRONLY|O_CLOEXEC, &t);
+ fdt = open_tmpfile_linkable_at(dir_fdt, to, O_WRONLY|O_CLOEXEC, &t);
if (copy_flags & COPY_MAC_CREATE)
mac_selinux_create_file_clear();
if (fdt < 0)
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL);
- r = copy_file_fd_full(from, fdt, copy_flags, progress_bytes, userdata);
+ r = copy_file_fd_at_full(dir_fdf, from, fdt, copy_flags, progress_bytes, userdata);
if (r < 0)
return r;
return -errno;
}
- r = link_tmpfile(fdt, t, to, copy_flags & COPY_REPLACE);
+ r = link_tmpfile_at(fdt, dir_fdt, t, to, copy_flags & COPY_REPLACE);
if (r < 0)
return r;
if (copy_flags & COPY_FSYNC_FULL) {
/* Sync the parent directory */
- r = fsync_parent_at(AT_FDCWD, to);
+ r = fsync_parent_at(dir_fdt, to);
if (r < 0)
goto fail;
}
return 0;
fail:
- (void) unlink(to);
+ (void) unlinkat(dir_fdt, to, 0);
return r;
}
typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);
typedef int (*copy_progress_path_t)(const char *path, const struct stat *st, void *userdata);
-int copy_file_fd_full(const char *from, int to, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
+int copy_file_fd_at_full(int dir_fdf, const char *from, int to, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
+static inline int copy_file_fd_at(int dir_fdf, const char *from, int to, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata) {
+ return copy_file_fd_at_full(dir_fdf, from, to, copy_flags, progress, userdata);
+}
+static inline int copy_file_fd_full(const char *from, int to, CopyFlags copy_flags) {
+ return copy_file_fd_at_full(AT_FDCWD, from, to, copy_flags, NULL, NULL);
+}
static inline int copy_file_fd(const char *from, int to, CopyFlags copy_flags) {
- return copy_file_fd_full(from, to, copy_flags, NULL, NULL);
+ return copy_file_fd_at(AT_FDCWD, from, to, copy_flags, NULL, NULL);
}
int copy_file_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
-static inline int copy_file_at(int dir_fdf, const char *from, int dir_fdt, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags) {
- return copy_file_at_full(dir_fdf, from, dir_fdt, to, open_flags, mode, chattr_flags, chattr_mask, copy_flags, NULL, NULL);
+static inline int copy_file_at(int dir_fdf, const char *from, int dir_fdt, const char *to, int open_flags, mode_t mode, CopyFlags copy_flags) {
+ return copy_file_at_full(dir_fdf, from, dir_fdt, to, open_flags, mode, 0, 0, copy_flags, NULL, NULL);
}
static inline int copy_file_full(const char *from, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata) {
return copy_file_at_full(AT_FDCWD, from, AT_FDCWD, to, open_flags, mode, chattr_flags, chattr_mask, copy_flags, progress, userdata);
}
-static inline int copy_file(const char *from, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags) {
- return copy_file_at(AT_FDCWD, from, AT_FDCWD, to, open_flags, mode, chattr_flags, chattr_mask, copy_flags);
+static inline int copy_file(const char *from, const char *to, int open_flags, mode_t mode, CopyFlags copy_flags) {
+ return copy_file_at(AT_FDCWD, from, AT_FDCWD, to, open_flags, mode, copy_flags);
}
-int copy_file_atomic_full(const char *from, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
-static inline int copy_file_atomic(const char *from, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags) {
- return copy_file_atomic_full(from, to, mode, chattr_flags, chattr_mask, copy_flags, NULL, NULL);
+int copy_file_atomic_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
+static inline int copy_file_atomic_at(int dir_fdf, const char *from, int dir_fdt, const char *to, mode_t mode, CopyFlags copy_flags) {
+ return copy_file_atomic_at_full(dir_fdf, from, dir_fdt, to, mode, 0, 0, copy_flags, NULL, NULL);
+}
+static inline int copy_file_atomic_full(const char *from, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata) {
+ return copy_file_atomic_at_full(AT_FDCWD, from, AT_FDCWD, to, mode, 0, 0, copy_flags, NULL, NULL);
+}
+static inline int copy_file_atomic(const char *from, const char *to, mode_t mode, CopyFlags copy_flags) {
+ return copy_file_atomic_full(from, to, mode, 0, 0, copy_flags, NULL, NULL);
}
int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <elf.h>
+
#include "coredump-util.h"
#include "extract-word.h"
#include "fileio.h"
#include "string-table.h"
+#include "unaligned.h"
#include "virt.h"
static const char *const coredump_filter_table[_COREDUMP_FILTER_MAX] = {
return 0;
}
+#define _DEFINE_PARSE_AUXV(size, type, unaligned_read) \
+ static int parse_auxv##size( \
+ int log_level, \
+ const void *auxv, \
+ size_t size_bytes, \
+ int *at_secure, \
+ uid_t *uid, \
+ uid_t *euid, \
+ gid_t *gid, \
+ gid_t *egid) { \
+ \
+ assert(auxv || size_bytes == 0); \
+ assert(at_secure); \
+ assert(uid); \
+ assert(euid); \
+ assert(gid); \
+ assert(egid); \
+ \
+ if (size_bytes % (2 * sizeof(type)) != 0) \
+ return log_full_errno(log_level, \
+ SYNTHETIC_ERRNO(EIO), \
+ "Incomplete auxv structure (%zu bytes).", \
+ size_bytes); \
+ \
+ size_t words = size_bytes / sizeof(type); \
+ \
+ /* Note that we set output variables even on error. */ \
+ \
+ for (size_t i = 0; i + 1 < words; i += 2) { \
+ type key, val; \
+ \
+ key = unaligned_read((uint8_t*) auxv + i * sizeof(type)); \
+ val = unaligned_read((uint8_t*) auxv + (i + 1) * sizeof(type)); \
+ \
+ switch (key) { \
+ case AT_SECURE: \
+ *at_secure = val != 0; \
+ break; \
+ case AT_UID: \
+ *uid = val; \
+ break; \
+ case AT_EUID: \
+ *euid = val; \
+ break; \
+ case AT_GID: \
+ *gid = val; \
+ break; \
+ case AT_EGID: \
+ *egid = val; \
+ break; \
+ case AT_NULL: \
+ if (val != 0) \
+ goto error; \
+ return 0; \
+ } \
+ } \
+ error: \
+ return log_full_errno(log_level, \
+ SYNTHETIC_ERRNO(ENODATA), \
+ "AT_NULL terminator not found, cannot parse auxv structure."); \
+ }
+
+#define DEFINE_PARSE_AUXV(size) \
+ _DEFINE_PARSE_AUXV(size, uint##size##_t, unaligned_read_ne##size)
+
+DEFINE_PARSE_AUXV(32);
+DEFINE_PARSE_AUXV(64);
+
+int parse_auxv(int log_level,
+ uint8_t elf_class,
+ const void *auxv,
+ size_t size_bytes,
+ int *at_secure,
+ uid_t *uid,
+ uid_t *euid,
+ gid_t *gid,
+ gid_t *egid) {
+
+ switch (elf_class) {
+ case ELFCLASS64:
+ return parse_auxv64(log_level, auxv, size_bytes, at_secure, uid, euid, gid, egid);
+ case ELFCLASS32:
+ return parse_auxv32(log_level, auxv, size_bytes, at_secure, uid, euid, gid, egid);
+ default:
+ return log_full_errno(log_level, SYNTHETIC_ERRNO(EPROTONOSUPPORT),
+ "Unknown ELF class %d.", elf_class);
+ }
+}
+
int set_coredump_filter(uint64_t value) {
char t[STRLEN("0xFFFFFFFF")];
CoredumpFilter coredump_filter_from_string(const char *s) _pure_;
int coredump_filter_mask_from_string(const char *s, uint64_t *ret);
+int parse_auxv(int log_level,
+ uint8_t elf_class,
+ const void *auxv,
+ size_t size_bytes,
+ int *at_secure,
+ uid_t *uid,
+ uid_t *euid,
+ gid_t *gid,
+ gid_t *egid);
+
int set_coredump_filter(uint64_t value);
void disable_coredumps(void);
_cleanup_(cpu_set_reset) CPUSet c = {};
const char *p = ASSERT_PTR(rvalue);
+ assert(cpu_set);
+
for (;;) {
_cleanup_free_ char *word = NULL;
unsigned cpu_lower, cpu_upper;
}
}
- /* On success, transfer ownership to the output variable */
- *cpu_set = c;
- c = (CPUSet) {};
+ *cpu_set = TAKE_STRUCT(c);
return 0;
}
_cleanup_(cpu_set_reset) CPUSet cpuset = {};
int r;
+ assert(old);
+
r = parse_cpu_set_full(rvalue, &cpuset, true, unit, filename, line, lvalue);
if (r < 0)
return r;
}
if (!old->set) {
- *old = cpuset;
- cpuset = (CPUSet) {};
+ *old = TAKE_STRUCT(cpuset);
return 1;
}
return r;
}
- *set = s;
- s = (CPUSet) {};
+ *set = TAKE_STRUCT(s);
return 0;
}
&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 "string-util.h"
#include "utf8.h"
-int allow_listed_char_for_devnode(char c, const char *white) {
+int allow_listed_char_for_devnode(char c, const char *additional) {
return
ascii_isdigit(c) ||
ascii_isalpha(c) ||
strchr("#+-.:=@_", c) ||
- (white && strchr(white, c));
+ (additional && strchr(additional, c));
}
int encode_devnode_name(const char *str, char *str_enc, size_t len) {
#include "alloc-util.h"
#include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "chattr-util.h"
#include "copy.h"
#include "dirent-util.h"
#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"
#include "hostname-setup.h"
#include "id128-util.h"
+#include "initrd-util.h"
#include "lock-util.h"
#include "log.h"
#include "loop-util.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",
+};
+
+/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
+ * search dir) */
+static const char* const image_search_path_initrd[_IMAGE_CLASS_MAX] = {
+ /* (entries that aren't listed here will get the same search path as for the non initrd-case) */
+
+ [IMAGE_SYSEXT] = "/etc/extensions\0" /* only place symlinks here */
+ "/run/extensions\0" /* and here too */
+ "/var/lib/extensions\0" /* the main place for images */
+ "/.extra/sysext\0" /* put sysext picked up by systemd-stub last, since not trusted */
};
static Image *image_free(Image *i) {
return -EMEDIUMTYPE;
}
+static const char *pick_image_search_path(ImageClass class) {
+ if (class < 0 || class >= _IMAGE_CLASS_MAX)
+ return NULL;
+
+ /* Use the initrd search path if there is one, otherwise use the common one */
+ return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
+}
+
int image_find(ImageClass class,
const char *name,
const char *root,
if (!image_name_is_valid(name))
return -ENOENT;
- NULSTR_FOREACH(path, image_search_path[class]) {
+ NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
struct stat st;
int flags;
- r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+ r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
if (r == -ENOENT)
continue;
if (r < 0)
assert(class < _IMAGE_CLASS_MAX);
assert(h);
- NULSTR_FOREACH(path, image_search_path[class]) {
+ NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
- r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+ r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
if (r == -ENOENT)
continue;
if (r < 0)
if (r < 0)
return r;
- return copy_file_atomic(path, rs, 0664, 0, 0, COPY_REFLINK);
+ return copy_file_atomic(path, rs, 0664, COPY_REFLINK);
}
int image_clone(Image *i, const char *new_name, bool read_only) {
case IMAGE_RAW:
new_path = strjoina("/var/lib/machines/", new_name, ".raw");
- r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME);
+ r = copy_file_atomic_full(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, FS_NOCOW_FL,
+ COPY_REFLINK|COPY_CRTIME, NULL, NULL);
break;
case IMAGE_BLOCK:
return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
}
-int image_read_metadata(Image *i) {
+int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
int r;
_cleanup_free_ char *hostname = NULL;
_cleanup_free_ char *path = NULL;
- r = chase_symlinks("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &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);
else if (r >= 0) {
path = mfree(path);
- r = chase_symlinks("/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_symlinks("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
+ r = chase("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
if (r < 0 && r != -ENOENT)
log_debug_errno(r, "Failed to chase /etc/machine-info in image %s: %m", i->name);
else if (r >= 0) {
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");
r = dissect_loop_device(
d,
- NULL, NULL,
+ /* verity= */ NULL,
+ /* mount_options= */ NULL,
+ image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
assert(image);
- NULSTR_FOREACH(path, image_search_path[class]) {
+ NULSTR_FOREACH(path, pick_image_search_path(class)) {
const char *p, *q;
size_t k;
};
DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
-
-static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
- [IMAGE_MACHINE] = "machine",
- [IMAGE_PORTABLE] = "portable",
- [IMAGE_EXTENSION] = "extension",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
#include "sd-id128.h"
#include "hashmap.h"
+#include "image-policy.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);
int image_set_limit(Image *i, uint64_t referenced_max);
-int image_read_metadata(Image *i);
+int image_read_metadata(Image *i, const ImagePolicy *image_policy);
bool image_in_search_path(ImageClass class, const char *root, const char *image);
#include "blkid-util.h"
#include "blockdev-util.h"
#include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "constants.h"
#include "copy.h"
#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"
if (!b)
return -ENOMEM;
+ /* The Linux kernel maintains separate block device caches for main ("whole") and partition block
+ * devices, which means making a change to one might not be reflected immediately when reading via
+ * the other. That's massively confusing when mixing accesses to such devices. Let's address this in
+ * a limited way: when probing a file system that is not at the beginning of the block device we
+ * apparently probe a partition via the main block device, and in that case let's first flush the
+ * main block device cache, so that we get the data that the per-partition block device last
+ * sync'ed on.
+ *
+ * This only works under the assumption that any tools that write to the partition block devices
+ * issue an syncfs()/fsync() on the device after making changes. Typically file system formatting
+ * tools that write a superblock onto a partition block device do that, however. */
+ if (offset != 0)
+ if (ioctl(fd, BLKFLSBUF, 0) < 0)
+ log_debug_errno(errno, "Failed to flush block device cache, ignoring: %m");
+
errno = 0;
r = blkid_probe_set_device(
b,
}
#if HAVE_BLKID
-static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
+static int image_policy_may_use(
+ const ImagePolicy *policy,
+ PartitionDesignator designator) {
+
+ PartitionPolicyFlags f;
+
+ /* For each partition we find in the partition table do a first check if it may exist at all given
+ * the policy, or if it shall be ignored. */
+
+ f = image_policy_get_exhaustively(policy, designator);
+ if (f < 0)
+ return f;
+
+ if ((f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
+ /* only flag set in policy is "absent"? then this partition may not exist at all */
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(ERFKILL),
+ "Partition of designator '%s' exists, but not allowed by policy, refusing.",
+ partition_designator_to_string(designator));
+ if ((f & _PARTITION_POLICY_USE_MASK & ~PARTITION_POLICY_ABSENT) == PARTITION_POLICY_UNUSED) {
+ /* only "unused" or "unused" + "absent" are set? then don't use it */
+ log_debug("Partition of designator '%s' exists, and policy dictates to ignore it, doing so.",
+ partition_designator_to_string(designator));
+ return false; /* ignore! */
+ }
+
+ return true; /* use! */
+}
+
+static int image_policy_check_protection(
+ const ImagePolicy *policy,
+ PartitionDesignator designator,
+ PartitionPolicyFlags found_flags) {
+
+ PartitionPolicyFlags policy_flags;
+
+ /* Checks if the flags in the policy for the designated partition overlap the flags of what we found */
+
+ if (found_flags < 0)
+ return found_flags;
+
+ policy_flags = image_policy_get_exhaustively(policy, designator);
+ if (policy_flags < 0)
+ return policy_flags;
+
+ if ((found_flags & policy_flags) == 0) {
+ _cleanup_free_ char *found_flags_string = NULL, *policy_flags_string = NULL;
+
+ (void) partition_policy_flags_to_string(found_flags, /* simplify= */ true, &found_flags_string);
+ (void) partition_policy_flags_to_string(policy_flags, /* simplify= */ true, &policy_flags_string);
+
+ return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s discovered with policy '%s' but '%s' was required, refusing.",
+ partition_designator_to_string(designator),
+ strnull(found_flags_string), strnull(policy_flags_string));
+ }
+
+ return 0;
+}
+
+static int image_policy_check_partition_flags(
+ const ImagePolicy *policy,
+ PartitionDesignator designator,
+ uint64_t gpt_flags) {
+
+ PartitionPolicyFlags policy_flags;
+ bool b;
+
+ /* Checks if the partition flags in the policy match reality */
+
+ policy_flags = image_policy_get_exhaustively(policy, designator);
+ if (policy_flags < 0)
+ return policy_flags;
+
+ b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_READ_ONLY);
+ if ((policy_flags & _PARTITION_POLICY_READ_ONLY_MASK) == (b ? PARTITION_POLICY_READ_ONLY_OFF : PARTITION_POLICY_READ_ONLY_ON))
+ return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'read-only' flag incorrectly set (must be %s, is %s), refusing.",
+ partition_designator_to_string(designator),
+ one_zero(!b), one_zero(b));
+
+ b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_GROWFS);
+ if ((policy_flags & _PARTITION_POLICY_GROWFS_MASK) == (b ? PARTITION_POLICY_GROWFS_OFF : PARTITION_POLICY_GROWFS_ON))
+ return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'growfs' flag incorrectly set (must be %s, is %s), refusing.",
+ partition_designator_to_string(designator),
+ one_zero(!b), one_zero(b));
+
+ return 0;
+}
+
+static int dissected_image_probe_filesystems(
+ DissectedImage *m,
+ int fd,
+ const ImagePolicy *policy) {
+
int r;
assert(m);
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
+ PartitionPolicyFlags found_flags;
if (!p->found)
continue;
return r;
}
- if (streq_ptr(p->fstype, "crypto_LUKS"))
+ if (streq_ptr(p->fstype, "crypto_LUKS")) {
m->encrypted = true;
+ found_flags = PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */
+ } else
+ /* found it, but it's definitely not encrypted, hence mask the encrypted flag, but
+ * set all other ways that indicate "present". */
+ found_flags = PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED;
if (p->fstype && fstype_is_ro(p->fstype))
p->rw = false;
if (!p->rw)
p->growfs = false;
+
+ /* We might have learnt more about the file system now (i.e. whether it is encrypted or not),
+ * hence we need to validate this against policy again, to see if the policy still matches
+ * with this new information. Note that image_policy_check_protection() will check for
+ * overlap between what's allowed in the policy and what we pass as 'found_policy' here. In
+ * the unencrypted case we thus might pass an overly unspecific mask here (i.e. unprotected
+ * OR verity OR signed), but that's fine since the earlier policy check already checked more
+ * specific which of those three cases where OK. Keep in mind that this function here only
+ * looks at specific partitions (and thus can only deduce encryption or not) but not the
+ * overall partition table (and thus cannot deduce verity or not). The earlier dissection
+ * checks already did the relevant checks that look at the whole partition table, and
+ * enforced policy there as needed. */
+ r = image_policy_check_protection(policy, i, found_flags);
+ if (r < 0)
+ return r;
}
return 0;
log_debug("Unexpected partition flag %llu set on %s!", bit, node);
}
}
-#endif
-#if HAVE_BLKID
static int dissected_image_new(const char *path, DissectedImage **ret) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_free_ char *name = NULL;
const char *devname,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *policy,
DissectImageFlags flags) {
sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
* Returns -ENOPKG if no suitable partition table or file system could be found.
* Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found.
* Returns -ENXIO if we couldn't find any partition suitable as root or /usr partition
- * Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that */
+ * Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that
+ * Returns -ERFKILL if image doesn't match image policy
+ * Returns -EBADR if verity data was provided externally for an image that has a GPT partition table (i.e. is not just a naked fs)
+ * Returns -EPROTONOSUPPORT if DISSECT_IMAGE_ADD_PARTITION_DEVICES is set but the block device does not have partition logic enabled
+ * Returns -ENOMSG if we didn't find a single usable partition (and DISSECT_IMAGE_REFUSE_EMPTY is set) */
uint64_t diskseq = m->loop ? m->loop->diskseq : 0;
const char *fstype = NULL, *options = NULL, *suuid = NULL;
_cleanup_close_ int mount_node_fd = -EBADF;
sd_id128_t uuid = SD_ID128_NULL;
+ PartitionPolicyFlags found_flags;
+ bool encrypted;
+
+ /* OK, we have found a file system, that's our root partition then. */
+
+ r = image_policy_may_use(policy, PARTITION_ROOT);
+ if (r < 0)
+ return r;
+ if (r == 0) /* policy says ignore this, so we ignore it */
+ return -ENOPKG;
+
+ (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+ (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
+
+ encrypted = streq_ptr(fstype, "crypto_LUKS");
+
+ if (verity_settings_data_covers(verity, PARTITION_ROOT))
+ found_flags = verity->root_hash_sig ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
+ else
+ found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED;
+
+ r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags);
+ if (r < 0)
+ return r;
+
+ r = image_policy_check_partition_flags(policy, PARTITION_ROOT, 0); /* we have no gpt partition flags, hence check against all bits off */
+ if (r < 0)
+ return r;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop);
return mount_node_fd;
}
- /* OK, we have found a file system, that's our root partition then. */
- (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
- (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
-
if (fstype) {
t = strdup(fstype);
if (!t)
return r;
m->single_file_system = true;
- m->encrypted = streq_ptr(fstype, "crypto_LUKS");
+ m->encrypted = encrypted;
m->has_verity = verity && verity->data_path;
m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
_cleanup_close_ int mount_node_fd = -EBADF;
const char *options = NULL;
+ r = image_policy_may_use(policy, type.designator);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Policy says: ignore; Remember this fact, so that we later can distinguish between "found but ignored" and "not found at all" */
+
+ if (!m->partitions[type.designator].found)
+ m->partitions[type.designator].ignored = true;
+
+ continue;
+ }
+
if (m->partitions[type.designator].found) {
/* For most partition types the first one we see wins. Except for the
* rootfs and /usr, where we do a version compare of the label, and
sd_id128_t id = SD_ID128_NULL;
const char *options = NULL;
+ r = image_policy_may_use(policy, PARTITION_XBOOTLDR);
+ if (r < 0)
+ return r;
+ if (r == 0) { /* policy says: ignore */
+ if (!m->partitions[PARTITION_XBOOTLDR].found)
+ m->partitions[PARTITION_XBOOTLDR].ignored = true;
+
+ continue;
+ }
+
/* First one wins */
if (m->partitions[PARTITION_XBOOTLDR].found)
continue;
/* If we didn't find a generic node, then we can't fix this up either */
if (generic_node) {
- _cleanup_close_ int mount_node_fd = -EBADF;
- _cleanup_free_ char *o = NULL, *n = NULL;
- const char *options;
-
- if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
- mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
- if (mount_node_fd < 0)
- return mount_node_fd;
- }
-
- r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
+ r = image_policy_may_use(policy, PARTITION_ROOT);
if (r < 0)
return r;
+ if (r == 0)
+ /* Policy says: ignore; remember that we did */
+ m->partitions[PARTITION_ROOT].ignored = true;
+ else {
+ _cleanup_close_ int mount_node_fd = -EBADF;
+ _cleanup_free_ char *o = NULL, *n = NULL;
+ const char *options;
- options = mount_options_from_designator(mount_options, PARTITION_ROOT);
- if (options) {
- o = strdup(options);
- if (!o)
- return -ENOMEM;
- }
+ if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
+ mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
+ if (mount_node_fd < 0)
+ return mount_node_fd;
+ }
- assert(generic_nr >= 0);
- m->partitions[PARTITION_ROOT] = (DissectedPartition) {
- .found = true,
- .rw = generic_rw,
- .growfs = generic_growfs,
- .partno = generic_nr,
- .architecture = _ARCHITECTURE_INVALID,
- .node = TAKE_PTR(n),
- .uuid = generic_uuid,
- .mount_options = TAKE_PTR(o),
- .mount_node_fd = TAKE_FD(mount_node_fd),
- .offset = UINT64_MAX,
- .size = UINT64_MAX,
- };
+ r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
+ if (r < 0)
+ return r;
+
+ options = mount_options_from_designator(mount_options, PARTITION_ROOT);
+ if (options) {
+ o = strdup(options);
+ if (!o)
+ return -ENOMEM;
+ }
+
+ assert(generic_nr >= 0);
+ m->partitions[PARTITION_ROOT] = (DissectedPartition) {
+ .found = true,
+ .rw = generic_rw,
+ .growfs = generic_growfs,
+ .partno = generic_nr,
+ .architecture = _ARCHITECTURE_INVALID,
+ .node = TAKE_PTR(n),
+ .uuid = generic_uuid,
+ .mount_options = TAKE_PTR(o),
+ .mount_node_fd = TAKE_FD(mount_node_fd),
+ .offset = UINT64_MAX,
+ .size = UINT64_MAX,
+ };
+ }
}
}
}
}
- r = dissected_image_probe_filesystems(m, fd);
+ bool any = false;
+
+ /* After we discovered all partitions let's see if the verity requirements match the policy. (Note:
+ * we don't check encryption requirements here, because we haven't probed the file system yet, hence
+ * don't know if this is encrypted or not) */
+ for (PartitionDesignator di = 0; di < _PARTITION_DESIGNATOR_MAX; di++) {
+ PartitionDesignator vi, si;
+ PartitionPolicyFlags found_flags;
+
+ any = any || m->partitions[di].found;
+
+ vi = partition_verity_of(di);
+ si = partition_verity_sig_of(di);
+
+ /* Determine the verity protection level for this partition. */
+ found_flags = m->partitions[di].found ?
+ (vi >= 0 && m->partitions[vi].found ?
+ (si >= 0 && m->partitions[si].found ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY) :
+ PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED) :
+ (m->partitions[di].ignored ? PARTITION_POLICY_UNUSED : PARTITION_POLICY_ABSENT);
+
+ r = image_policy_check_protection(policy, di, found_flags);
+ if (r < 0)
+ return r;
+
+ if (m->partitions[di].found) {
+ r = image_policy_check_partition_flags(policy, di, m->partitions[di].gpt_flags);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (!any && !FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_EMPTY))
+ return -ENOMSG;
+
+ r = dissected_image_probe_filesystems(m, fd, policy);
if (r < 0)
return r;
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
int r;
assert(path);
- assert(ret);
fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
if (r < 0)
return r;
- r = dissect_image(m, fd, path, verity, mount_options, flags);
+ r = dissect_image(m, fd, path, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
- *ret = TAKE_PTR(m);
+ if (ret)
+ *ret = TAKE_PTR(m);
return 0;
#else
return -EOPNOTSUPP;
#endif
}
+int dissect_log_error(int log_level, int r, const char *name, const VeritySettings *verity) {
+ assert(log_level >= 0 && log_level <= LOG_DEBUG);
+ assert(name);
+
+ switch (r) {
+
+ case 0 ... INT_MAX: /* success! */
+ return r;
+
+ case -EOPNOTSUPP:
+ return log_full_errno(log_level, r, "Dissecting images is not supported, compiled without blkid support.");
+
+ case -ENOPKG:
+ return log_full_errno(log_level, r, "%s: Couldn't identify a suitable partition table or file system.", name);
+
+ case -ENOMEDIUM:
+ return log_full_errno(log_level, r, "%s: The image does not pass os-release/extension-release validation.", name);
+
+ case -EADDRNOTAVAIL:
+ return log_full_errno(log_level, r, "%s: No root partition for specified root hash found.", name);
+
+ case -ENOTUNIQ:
+ return log_full_errno(log_level, r, "%s: Multiple suitable root partitions found in image.", name);
+
+ case -ENXIO:
+ return log_full_errno(log_level, r, "%s: No suitable root partition found in image.", name);
+
+ case -EPROTONOSUPPORT:
+ return log_full_errno(log_level, r, "Device '%s' is a loopback block device with partition scanning turned off, please turn it on.", name);
+
+ case -ENOTBLK:
+ return log_full_errno(log_level, r, "%s: Image is not a block device.", name);
+
+ case -EBADR:
+ return log_full_errno(log_level, r,
+ "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
+ "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
+ name, strna(verity ? verity->data_path : NULL));
+
+ case -ERFKILL:
+ return log_full_errno(log_level, r, "%s: image does not match image policy.", name);
+
+ case -ENOMSG:
+ return log_full_errno(log_level, r, "%s: no suitable partitions found.", name);
+
+ default:
+ return log_full_errno(log_level, r, "%s: cannot dissect image: %m", name);
+ }
+}
+
+int dissect_image_file_and_warn(
+ const char *path,
+ const VeritySettings *verity,
+ const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
+ DissectImageFlags flags,
+ DissectedImage **ret) {
+
+ return dissect_log_error(
+ LOG_ERR,
+ dissect_image_file(path, verity, mount_options, image_policy, flags, ret),
+ path,
+ verity);
+}
+
DissectedImage* dissected_image_unref(DissectedImage *m) {
if (!m)
return NULL;
return log_debug_errno(r, "Failed to fork off fsck: %m");
if (r == 0) {
/* Child */
- execl("/sbin/fsck", "/sbin/fsck", "-aT", FORMAT_PROC_FD_PATH(node_fd), NULL);
+ execlp("fsck", "fsck", "-aT", FORMAT_PROC_FD_PATH(node_fd), NULL);
log_open();
log_debug_errno(errno, "Failed to execl() fsck: %m");
_exit(FSCK_OPERATIONAL_ERROR);
exit_status = wait_for_terminate_and_check("fsck", pid, 0);
if (exit_status < 0)
- return log_debug_errno(exit_status, "Failed to fork off /sbin/fsck: %m");
+ return log_debug_errno(exit_status, "Failed to fork off fsck: %m");
if ((exit_status & ~FSCK_ERROR_CORRECTED) != FSCK_SUCCESS) {
log_debug("fsck failed with exit status %i.", exit_status);
if (!fstype)
return -EAFNOSUPPORT;
- r = dissect_fstype_ok(fstype);
- if (r < 0)
- return r;
- if (!r)
- return -EIDRM; /* Recognizable error */
/* We are looking at an encrypted partition? This either means stacked encryption, or the caller
* didn't call dissected_image_decrypt() beforehand. Let's return a recognizable error for this
if (streq(fstype, "crypto_LUKS"))
return -EUNATCH;
+ r = dissect_fstype_ok(fstype);
+ if (r < 0)
+ return r;
+ if (!r)
+ return -EIDRM; /* Recognizable error */
+
rw = m->rw && !(flags & DISSECT_IMAGE_MOUNT_READ_ONLY);
discard = ((flags & DISSECT_IMAGE_DISCARD) ||
if (r < 0 && r != -EROFS)
return r;
- r = chase_symlinks(directory, where, CHASE_PREFIX_ROOT, &chased, NULL);
+ r = chase(directory, where, CHASE_PREFIX_ROOT, &chased, NULL);
if (r < 0)
return r;
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)
/* Mount the ESP to /efi if it exists. If it doesn't exist, use /boot instead, but only if it
* exists and is empty, and we didn't already mount the XBOOTLDR partition into it. */
- r = chase_symlinks("/efi", where, CHASE_PREFIX_ROOT, NULL, NULL);
+ r = chase("/efi", where, CHASE_PREFIX_ROOT, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
return r;
if (!xbootldr_mounted) {
_cleanup_free_ char *p = NULL;
- r = chase_symlinks("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
+ r = chase("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
if (r < 0) {
if (r != -ENOENT)
return r;
* 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;
"/lib/systemd/systemd", /* systemd on /usr non-merged systems */
"/sbin/init") { /* traditional path the Linux kernel invokes */
- r = chase_symlinks(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
+ r = chase(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
log_debug_errno(r, "Failed to resolve %s, ignoring: %m", init);
default:
NULSTR_FOREACH(p, paths[k]) {
- fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ fd = chase_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (fd >= 0)
break;
}
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
int r;
assert(loop);
- assert(ret);
r = dissected_image_new(loop->backing_file ?: loop->node, &m);
if (r < 0)
m->loop = loop_device_ref(loop);
m->sector_size = m->loop->sector_size;
- r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags);
+ r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
- *ret = TAKE_PTR(m);
+ if (ret)
+ *ret = TAKE_PTR(m);
+
return 0;
#else
return -EOPNOTSUPP;
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
- const char *name;
- int r;
-
assert(loop);
- assert(loop->fd >= 0);
- name = ASSERT_PTR(loop->backing_file ?: loop->node);
+ return dissect_log_error(
+ LOG_ERR,
+ dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
+ loop->backing_file ?: loop->node,
+ verity);
- r = dissect_loop_device(loop, verity, mount_options, flags, ret);
- switch (r) {
-
- case -EOPNOTSUPP:
- return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
-
- case -ENOPKG:
- return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
-
- case -ENOMEDIUM:
- return log_error_errno(r, "%s: The image does not pass validation.", name);
-
- case -EADDRNOTAVAIL:
- return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
-
- case -ENOTUNIQ:
- return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
-
- case -ENXIO:
- return log_error_errno(r, "%s: No suitable root partition found in image.", name);
-
- case -EPROTONOSUPPORT:
- return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
-
- case -ENOTBLK:
- return log_error_errno(r, "%s: Image is not a block device.", name);
-
- case -EBADR:
- return log_error_errno(r,
- "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
- "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
- name, strna(verity ? verity->data_path : NULL));
-
- default:
- if (r < 0)
- return log_error_errno(r, "Failed to dissect image '%s': %m", name);
-
- return r;
- }
}
bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator partition_designator) {
int mount_image_privately_interactively(
const char *image,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
char **ret_directory,
int *ret_dir_fd,
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", image);
- r = dissect_loop_device_and_warn(d, &verity, NULL, flags, &dissected_image);
+ r = dissect_loop_device_and_warn(
+ d,
+ &verity,
+ /* mount_options= */ NULL,
+ image_policy,
+ flags,
+ &dissected_image);
if (r < 0)
return r;
const char *src,
const char *dest,
const MountOptions *options,
+ const ImagePolicy *image_policy,
const char *required_host_os_release_id,
const char *required_host_os_release_version_id,
const char *required_host_os_release_sysext_level,
loop_device,
&verity,
options,
+ image_policy,
dissect_image_flags,
&dissected_image);
/* No partition table? Might be a single-filesystem image, try again */
loop_device,
&verity,
options,
+ image_policy,
dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
&dissected_image);
if (r < 0)
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)
struct DissectedPartition {
bool found:1;
+ bool ignored:1;
bool rw:1;
bool growfs:1;
int partno; /* -1 if there was no partition and the images contains a file system directly */
DISSECT_IMAGE_PIN_PARTITION_DEVICES = 1 << 21, /* Open dissected partitions and decrypted partitions and pin them by fd */
DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 22, /* Don't insist that the extension-release file name matches the image name */
DISSECT_IMAGE_DISKSEQ_DEVNODE = 1 << 23, /* Prefer /dev/disk/by-diskseq/… device nodes */
+ DISSECT_IMAGE_ALLOW_EMPTY = 1 << 24, /* Allow that no usable partitions is present */
} DissectImageFlags;
struct DissectedImage {
.designator = _PARTITION_DESIGNATOR_INVALID \
}
+/* We include image-policy.h down here, since ImagePolicy wants a complete definition of PartitionDesignator first. */
+#include "image-policy.h"
+
MountOptions* mount_options_free_all(MountOptions *options);
DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all);
const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator);
static inline int probe_filesystem(const char *path, char **ret_fstype) {
return probe_filesystem_full(-1, path, 0, UINT64_MAX, ret_fstype);
}
-int dissect_image_file(
- const char *path,
- const VeritySettings *verity,
- const MountOptions *mount_options,
- DissectImageFlags flags,
- DissectedImage **ret);
-int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
-int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
+
+int dissect_log_error(int log_level, int r, const char *name, const VeritySettings *verity);
+int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator d);
bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesignator d);
-int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
+int mount_image_privately_interactively(const char *path, const ImagePolicy *image_policy, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
-int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
+int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
int dissect_fstype_ok(const char *fstype);
#include <stdlib.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "dirent-util.h"
#include "dropin.h"
/* This adds [original_root]/path to dirs, if it exists. */
- r = chase_symlinks(path, original_root, 0, &chased, NULL);
+ r = chase(path, original_root, 0, &chased, NULL);
if (r == -ENOENT) /* Ignore -ENOENT, after all most units won't have a drop-in dir. */
return 0;
if (r == -ENAMETOOLONG) {
#include "mkdir-label.h"
#include "path-util.h"
#include "process-util.h"
-#include "selinux-util.h"
-#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
-#include "tmpfile-util.h"
+#include "tmpfile-util-label.h"
void edit_file_context_done(EditFileContext *context) {
int r;
if (edit_files_contains(context, path))
return 0;
- if (!GREEDY_REALLOC0(context->files, context->n_files + 2))
+ if (!GREEDY_REALLOC(context->files, context->n_files + 1))
return log_oom();
new_path = strdup(path);
static int create_edit_temp_file(EditFile *e) {
_cleanup_(unlink_and_freep) char *temp = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *source;
+ bool has_original, has_target;
unsigned line = 1;
int r;
if (e->temp)
return 0;
- r = tempfn_random(e->path, NULL, &temp);
+ r = mkdir_parents_label(e->path, 0755);
if (r < 0)
- return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", e->path);
+ return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
- r = mkdir_parents_label(e->path, 0755);
+ r = fopen_temporary_label(e->path, e->path, &f, &temp);
if (r < 0)
- return log_error_errno(r, "Failed to create parent directories for \"%s\": %m", e->path);
+ return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
- if (!e->original_path && !e->comment_paths) {
- r = mac_selinux_create_file_prepare(e->path, S_IFREG);
- if (r < 0)
- return r;
+ if (fchmod(fileno(f), 0644) < 0)
+ return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
- r = touch(temp);
- mac_selinux_create_file_clear();
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
- }
+ has_original = e->original_path && access(e->original_path, F_OK) >= 0;
+ has_target = access(e->path, F_OK) >= 0;
- if (e->original_path) {
- r = mac_selinux_create_file_prepare(e->path, S_IFREG);
- if (r < 0)
- return r;
-
- r = copy_file(e->original_path, temp, 0, 0644, 0, 0, COPY_REFLINK);
- if (r == -ENOENT) {
- r = touch(temp);
- mac_selinux_create_file_clear();
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
- } else {
- mac_selinux_create_file_clear();
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", e->path);
- }
- }
+ if (has_original && (!has_target || e->context->overwrite_with_origin))
+ /* We are asked to overwrite target with original_path or target doesn't exist. */
+ source = e->original_path;
+ else if (has_target)
+ /* Target exists and shouldn't be overwritten. */
+ source = e->path;
+ else
+ source = NULL;
if (e->comment_paths) {
- _cleanup_free_ char *target_contents = NULL;
- _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *source_contents = NULL;
- r = mac_selinux_create_file_prepare(e->path, S_IFREG);
- if (r < 0)
- return r;
-
- f = fopen(temp, "we");
- mac_selinux_create_file_clear();
- if (!f)
- return log_error_errno(errno, "Failed to open temporary file \"%s\": %m", temp);
-
- if (fchmod(fileno(f), 0644) < 0)
- return log_error_errno(errno, "Failed to change mode of temporary file \"%s\": %m", temp);
-
- r = read_full_file(e->path, &target_contents, NULL);
- if (r < 0 && r != -ENOENT)
- return log_error_errno(r, "Failed to read target file \"%s\": %m", e->path);
+ if (source) {
+ r = read_full_file(source, &source_contents, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read source file '%s': %m", source);
+ }
fprintf(f,
"### Editing %s\n"
"%s\n",
e->path,
e->context->marker_start,
- strempty(target_contents),
- target_contents && endswith(target_contents, "\n") ? "" : "\n",
+ strempty(source_contents),
+ source_contents && endswith(source_contents, "\n") ? "" : "\n",
e->context->marker_end);
line = 4; /* Start editing at the contents area */
- /* Add a comment with the contents of the original files */
STRV_FOREACH(path, e->comment_paths) {
- _cleanup_free_ char *contents = NULL;
+ _cleanup_free_ char *comment = NULL;
- /* Skip the file that's being edited, already processed in above */
- if (path_equal(*path, e->path))
+ /* Skip the file which is being edited and the source file (can be the same) */
+ if (PATH_IN_SET(*path, e->path, source))
continue;
- r = read_full_file(*path, &contents, NULL);
+ r = read_full_file(*path, &comment, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to read original file \"%s\": %m", *path);
+ return log_error_errno(r, "Failed to read comment file '%s': %m", *path);
fprintf(f, "\n\n### %s", *path);
- if (!isempty(contents)) {
- _cleanup_free_ char *commented_contents = NULL;
- commented_contents = strreplace(strstrip(contents), "\n", "\n# ");
- if (!commented_contents)
+ if (!isempty(comment)) {
+ _cleanup_free_ char *c = NULL;
+
+ c = strreplace(strstrip(comment), "\n", "\n# ");
+ if (!c)
return log_oom();
- fprintf(f, "\n# %s", commented_contents);
+ fprintf(f, "\n# %s", c);
}
}
-
- r = fflush_and_check(f);
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
+ } else if (source) {
+ r = copy_file_fd(source, fileno(f), COPY_REFLINK);
+ if (r < 0) {
+ assert(r != -ENOENT);
+ return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", source, temp);
+ }
}
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp);
+
e->temp = TAKE_PTR(temp);
e->line = line;
r = read_full_file(e->temp, &old_contents, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to read temporary file \"%s\": %m", e->temp);
+ return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp);
if (e->context->marker_start) {
/* Trim out the lines between the two markers */
assert(e->context->marker_end);
- contents_start = strstr(old_contents, e->context->marker_start);
- if (contents_start)
- contents_start += strlen(e->context->marker_start);
- else
+ contents_start = strstrafter(old_contents, e->context->marker_start);
+ if (!contents_start)
contents_start = old_contents;
contents_end = strstr(contents_start, e->context->marker_end);
r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
if (r < 0)
- return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", e->temp);
+ return log_error_errno(r, "Failed to strip temporary file '%s': %m", e->temp);
return 1; /* Contents have real changes and are changed after stripping */
}
r = RET_NERRNO(rename(i->temp, i->path));
if (r < 0)
- return log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", i->temp, i->path);
+ return log_error_errno(r,
+ "Failed to rename temporary file '%s' to target file '%s': %m",
+ i->temp,
+ i->path);
i->temp = mfree(i->temp);
log_info("Successfully installed edited file '%s'.", i->path);
#include <stdbool.h>
+#define DROPIN_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file"
+#define DROPIN_MARKER_END "### Edits below this comment will be discarded"
+
typedef struct EditFile EditFile;
typedef struct EditFileContext EditFileContext;
const char *marker_start;
const char *marker_end;
bool remove_parent;
+ bool overwrite_with_origin; /* whether to always overwrite target with original file */
};
void edit_file_context_done(EditFileContext *context);
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;
}
static int do_execute(
char* const* paths,
+ const char *root,
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
_cleanup_close_ int fd = -EBADF;
pid_t pid;
- t = strdup(*path);
+ t = path_join(root, *path);
if (!t)
return log_oom();
int execute_strv(
const char *name,
char* const* paths,
+ const char *root,
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
if (r < 0)
return r;
if (r == 0) {
- r = do_execute(paths, timeout, callbacks, callback_args, fd, argv, envp, flags);
+ r = do_execute(paths, root, timeout, callbacks, callback_args, fd, argv, envp, flags);
_exit(r < 0 ? EXIT_FAILURE : r);
}
return log_error_errno(r, "Failed to extract file name from '%s': %m", directories[0]);
}
- return execute_strv(name, paths, timeout, callbacks, callback_args, argv, envp, flags);
+ return execute_strv(name, paths, NULL, timeout, callbacks, callback_args, argv, envp, flags);
}
static int gather_environment_generate(int fd, void *arg) {
_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];
int execute_strv(
const char *name,
char* const* paths,
+ const char *root,
usec_t timeout,
gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
void* const callback_args[_STDOUT_CONSUME_MAX],
+++ /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. "
#include "alloc-util.h"
#include "blkid-util.h"
#include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "device-util.h"
#include "devnum-util.h"
#include "env-util.h"
}
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_symlinks(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_symlinks(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_symlinks(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;
- if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+ 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) {
+ r = chaseat_prefix_root(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
+ 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_symlinks(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_xbootldr(p, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid);
- if (r < 0)
- return r;
-
- 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_symlinks(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);
+
+ 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 (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), "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_symlinks("/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));
+ }
- 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 */
- return r;
+ return 0;
+}
- return -ENOKEY;
+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;
-found:
- if (ret_path)
- *ret_path = TAKE_PTR(p);
+ 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;
+
+ if (ret_path) {
+ r = chaseat_prefix_root(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
+ 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
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "image-policy.h"
+#include "logarithm.h"
+#include "sort-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+/* Rationale for the chosen syntax:
+ *
+ * → one line, so that it can be reasonably added to a shell command line, for example via `systemd-dissect
+ * --image-policy=…` or to the kernel command line via `systemd.image_policy=`.
+ *
+ * → no use of "," or ";" as separators, so that it can be included in mount/fstab-style option strings and
+ * doesn't require escaping. Instead, separators are ":", "=", "+" which should be fine both in shell
+ * command lines and in mount/fstab style option strings.
+ */
+
+static int partition_policy_compare(const PartitionPolicy *a, const PartitionPolicy *b) {
+ return CMP(ASSERT_PTR(a)->designator, ASSERT_PTR(b)->designator);
+}
+
+static PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
+ if (!policy)
+ return NULL;
+
+ return typesafe_bsearch(
+ &(PartitionPolicy) { .designator = designator },
+ ASSERT_PTR(policy)->policies,
+ ASSERT_PTR(policy)->n_policies,
+ partition_policy_compare);
+}
+
+static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
+ PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
+
+ /* This normalizes the per-partition policy flags. This means if the user left some things
+ * unspecified, we'll fill in the appropriate "dontcare" policy instead. We'll also mask out bits
+ * that do not make any sense for specific partition types. */
+
+ /* If no protection flag is set, then this means all are set */
+ if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
+ flags |= PARTITION_POLICY_OPEN;
+
+ /* If this is a verity or verity signature designator, then mask off all protection bits, this after
+ * all needs no protection, because it *is* the protection */
+ if (partition_verity_to_data(policy->designator) >= 0 ||
+ partition_verity_sig_to_data(policy->designator) >= 0)
+ flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED);
+
+ /* if this designator has no verity concept, then mask off verity protection flags */
+ if (partition_verity_of(policy->designator) < 0)
+ flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);
+
+ if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
+ /* If the partition must be absent, then the gpt flags don't matter */
+ flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);
+ else {
+ /* If the gpt flags bits are not specified, set both options for each */
+ if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
+ flags |= PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_READ_ONLY_OFF;
+ if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
+ flags |= PARTITION_POLICY_GROWFS_ON|PARTITION_POLICY_GROWFS_OFF;
+ }
+
+ return flags;
+}
+
+PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
+ PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
+ PartitionPolicy *pp;
+
+ /* No policy means: everything may be used in any mode */
+ if (!policy)
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = PARTITION_POLICY_OPEN,
+ .designator = designator,
+ });
+
+ pp = image_policy_bsearch(policy, designator);
+ if (pp)
+ return partition_policy_normalized_flags(pp);
+
+ /* Hmm, so this didn't work, then let's see if we can derive some policy from the underlying data
+ * partition in case of verity/signature partitions */
+
+ data_designator = partition_verity_to_data(designator);
+ if (data_designator >= 0) {
+ PartitionPolicyFlags data_flags;
+
+ /* So we are asked for the policy for a verity partition, and there's no explicit policy for
+ * that case. Let's synthesize a policy from the protection setting for the underlying data
+ * partition. */
+
+ data_flags = image_policy_get(policy, data_designator);
+ if (data_flags < 0)
+ return data_flags;
+
+ /* We need verity if verity or verity with sig is requested */
+ if (!(data_flags & (PARTITION_POLICY_SIGNED|PARTITION_POLICY_VERITY)))
+ return _PARTITION_POLICY_FLAGS_INVALID;
+
+ /* If the data partition may be unused or absent, then the verity partition may too. Also, inherit the partition flags policy */
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
+ (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
+ .designator = designator,
+ });
+ }
+
+ data_designator = partition_verity_sig_to_data(designator);
+ if (data_designator >= 0) {
+ PartitionPolicyFlags data_flags;
+
+ /* Similar case as for verity partitions, but slightly more strict rules */
+
+ data_flags = image_policy_get(policy, data_designator);
+ if (data_flags < 0)
+ return data_flags;
+
+ if (!(data_flags & PARTITION_POLICY_SIGNED))
+ return _PARTITION_POLICY_FLAGS_INVALID;
+
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
+ (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
+ .designator = designator,
+ });
+ }
+
+ return _PARTITION_POLICY_FLAGS_INVALID; /* got nothing */
+}
+
+PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator) {
+ PartitionPolicyFlags flags;
+
+ /* This is just like image_policy_get() but whenever there is no policy for a specific designator, we
+ * return the default policy. */
+
+ flags = image_policy_get(policy, designator);
+ if (flags < 0)
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = image_policy_default(policy),
+ .designator = designator,
+ });
+
+ return flags;
+}
+
+static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
+ assert(s);
+
+ /* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */
+
+ if (streq(s, "verity"))
+ return PARTITION_POLICY_VERITY;
+ if (streq(s, "signed"))
+ return PARTITION_POLICY_SIGNED;
+ if (streq(s, "encrypted"))
+ return PARTITION_POLICY_ENCRYPTED;
+ if (streq(s, "unprotected"))
+ return PARTITION_POLICY_UNPROTECTED;
+ if (streq(s, "unused"))
+ return PARTITION_POLICY_UNUSED;
+ if (streq(s, "absent"))
+ return PARTITION_POLICY_ABSENT;
+ if (streq(s, "open")) /* shortcut alias */
+ return PARTITION_POLICY_OPEN;
+ if (streq(s, "ignore")) /* ditto */
+ return PARTITION_POLICY_IGNORE;
+ if (streq(s, "read-only-on"))
+ return PARTITION_POLICY_READ_ONLY_ON;
+ if (streq(s, "read-only-off"))
+ return PARTITION_POLICY_READ_ONLY_OFF;
+ if (streq(s, "growfs-on"))
+ return PARTITION_POLICY_GROWFS_ON;
+ if (streq(s, "growfs-off"))
+ return PARTITION_POLICY_GROWFS_OFF;
+
+ return _PARTITION_POLICY_FLAGS_INVALID;
+}
+
+PartitionPolicyFlags partition_policy_flags_from_string(const char *s) {
+ PartitionPolicyFlags flags = 0;
+ int r;
+
+ assert(s);
+
+ if (empty_or_dash(s))
+ return 0;
+
+ for (;;) {
+ _cleanup_free_ char *f = NULL;
+ PartitionPolicyFlags ff;
+
+ r = extract_first_word(&s, &f, "+", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ ff = policy_flag_from_string_one(strstrip(f));
+ if (ff < 0)
+ return -EBADRQC; /* recognizable error */
+
+ flags |= ff;
+ }
+
+ return flags;
+}
+
+static ImagePolicy* image_policy_new(size_t n_policies) {
+ ImagePolicy *p;
+
+ if (n_policies > (SIZE_MAX - offsetof(ImagePolicy, policies)) / sizeof(PartitionPolicy)) /* overflow check */
+ return NULL;
+
+ p = malloc(offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * n_policies);
+ if (!p)
+ return NULL;
+
+ *p = (ImagePolicy) {
+ .default_flags = PARTITION_POLICY_IGNORE,
+ };
+ return p;
+}
+
+int image_policy_from_string(const char *s, ImagePolicy **ret) {
+ _cleanup_free_ ImagePolicy *p = NULL;
+ uint64_t dmask = 0;
+ ImagePolicy *t;
+ PartitionPolicyFlags symbolic_policy;
+ int r;
+
+ assert(s);
+ assert_cc(sizeof(dmask) * 8 >= _PARTITION_DESIGNATOR_MAX);
+
+ /* Recognizable errors:
+ *
+ * ENOTUNIQ → Two or more rules for the same partition
+ * EBADSLT → Unknown partition designator
+ * EBADRQC → Unknown policy flags
+ */
+
+ /* First, let's handle "symbolic" policies, i.e. "-", "*", "~" */
+ if (empty_or_dash(s))
+ /* ignore policy: everything may exist, but nothing used */
+ symbolic_policy = PARTITION_POLICY_IGNORE;
+ else if (streq(s, "*"))
+ /* allow policy: everything is allowed */
+ symbolic_policy = PARTITION_POLICY_OPEN;
+ else if (streq(s, "~"))
+ /* deny policy: nothing may exist */
+ symbolic_policy = PARTITION_POLICY_ABSENT;
+ else
+ symbolic_policy = _PARTITION_POLICY_FLAGS_INVALID;
+
+ if (symbolic_policy >= 0) {
+ if (!ret)
+ return 0;
+
+ p = image_policy_new(0);
+ if (!p)
+ return -ENOMEM;
+
+ p->default_flags = symbolic_policy;
+ *ret = TAKE_PTR(p);
+ return 0;
+ }
+
+ /* Allocate the policy at maximum size, i.e. for all designators. We might overshoot a bit, but the
+ * items are cheap, and we can return unused space to libc once we know we don't need it */
+ p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
+ if (!p)
+ return -ENOMEM;
+
+ const char *q = s;
+ bool default_specified = false;
+ for (;;) {
+ _cleanup_free_ char *e = NULL, *d = NULL;
+ PartitionDesignator designator;
+ PartitionPolicyFlags flags;
+ char *f, *ds, *fs;
+
+ r = extract_first_word(&q, &e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ f = e;
+ r = extract_first_word((const char**) &f, &d, "=", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected designator name followed by '='; got instead: %s", e);
+ if (!f) /* no separator? */
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in policy expression: %s", e);
+
+ ds = strstrip(d);
+ if (isempty(ds)) {
+ /* Not partition name? then it's the default policy */
+ if (default_specified)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Default partition policy flags specified more than once.");
+
+ designator = _PARTITION_DESIGNATOR_INVALID;
+ default_specified = true;
+ } else {
+ designator = partition_designator_from_string(ds);
+ if (designator < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Unknown partition designator: %s", ds); /* recognizable error */
+ if (dmask & (UINT64_C(1) << designator))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition designator specified more than once: %s", ds);
+ dmask |= UINT64_C(1) << designator;
+ }
+
+ fs = strstrip(f);
+ flags = partition_policy_flags_from_string(fs);
+ if (flags == -EBADRQC)
+ return log_debug_errno(flags, "Unknown partition policy flag: %s", fs);
+ if (flags < 0)
+ return log_debug_errno(flags, "Failed to parse partition policy flags '%s': %m", fs);
+
+ if (designator < 0)
+ p->default_flags = flags;
+ else {
+ p->policies[p->n_policies++] = (PartitionPolicy) {
+ .designator = designator,
+ .flags = flags,
+ };
+ }
+ };
+
+ assert(p->n_policies <= _PARTITION_DESIGNATOR_MAX);
+
+ /* Return unused space to libc */
+ t = realloc(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies);
+ if (t)
+ p = t;
+
+ typesafe_qsort(p->policies, p->n_policies, partition_policy_compare);
+
+ if (ret)
+ *ret = TAKE_PTR(p);
+
+ return 0;
+}
+
+int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret) {
+ _cleanup_free_ char *buf = NULL;
+ const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK) + 1]; /* one string per known flag at most */
+ size_t m = 0;
+
+ assert(ret);
+
+ if (flags < 0)
+ return -EINVAL;
+
+ /* If 'simplify' is false we'll output the precise value of every single flag.
+ *
+ * If 'simplify' is true we'll try to make the output shorter, by doing the following:
+ *
+ * → we'll spell the long form "verity+signed+encrypted+unprotected+unused+absent" via its
+ * equivalent shortcut form "open" (which we happily parse btw, see above)
+ *
+ * → we'll spell the long form "unused+absent" via its shortcut "ignore" (which we are also happy
+ * to parse)
+ *
+ * → if the read-only/growfs policy flags are both set, we suppress them. this thus removes the
+ * distinction between "user explicitly declared don't care" and "we implied don't care because
+ * user didn't say anything".
+ *
+ * net result: the resulting string is shorter, but the effective policy declared that way will have
+ * the same results as the long form. */
+
+ if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_OPEN)
+ l[m++] = "open";
+ else if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE)
+ l[m++] = "ignore";
+ else {
+ if (flags & PARTITION_POLICY_VERITY)
+ l[m++] = "verity";
+ if (flags & PARTITION_POLICY_SIGNED)
+ l[m++] = "signed";
+ if (flags & PARTITION_POLICY_ENCRYPTED)
+ l[m++] = "encrypted";
+ if (flags & PARTITION_POLICY_UNPROTECTED)
+ l[m++] = "unprotected";
+ if (flags & PARTITION_POLICY_UNUSED)
+ l[m++] = "unused";
+ if (flags & PARTITION_POLICY_ABSENT)
+ l[m++] = "absent";
+ }
+
+ if (!simplify || (!(flags & PARTITION_POLICY_READ_ONLY_ON) != !(flags & PARTITION_POLICY_READ_ONLY_OFF))) {
+ if (flags & PARTITION_POLICY_READ_ONLY_ON)
+ l[m++] = "read-only-on";
+ if (flags & PARTITION_POLICY_READ_ONLY_OFF)
+ l[m++] = "read-only-off";
+ }
+
+ if (!simplify || (!(flags & PARTITION_POLICY_GROWFS_ON) != !(flags & PARTITION_POLICY_GROWFS_OFF))) {
+ if (flags & PARTITION_POLICY_GROWFS_OFF)
+ l[m++] = "growfs-off";
+ if (flags & PARTITION_POLICY_GROWFS_ON)
+ l[m++] = "growfs-on";
+ }
+
+ if (m == 0)
+ buf = strdup("-");
+ else {
+ assert(m+1 < ELEMENTSOF(l));
+ l[m] = NULL;
+
+ buf = strv_join((char**) l, "+");
+ }
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {
+
+ if (expected < 0)
+ return -EINVAL;
+
+ if (image_policy_default(policy) != expected)
+ return false;
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ PartitionPolicyFlags f, w;
+
+ f = image_policy_get_exhaustively(policy, d);
+ if (f < 0)
+ return f;
+
+ w = partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = expected,
+ .designator = d,
+ });
+ if (w < 0)
+ return w;
+ if (f != w)
+ return false;
+ }
+
+ return true;
+}
+
+bool image_policy_equiv_ignore(const ImagePolicy *policy) {
+ /* Checks if this is the ignore policy (or equivalent to it), i.e. everything is ignored, aka '-', aka '' */
+ return image_policy_flags_all_match(policy, PARTITION_POLICY_IGNORE);
+}
+
+bool image_policy_equiv_allow(const ImagePolicy *policy) {
+ /* Checks if this is the allow policy (or equivalent to it), i.e. everything is allowed, aka '*' */
+ return image_policy_flags_all_match(policy, PARTITION_POLICY_OPEN);
+}
+
+bool image_policy_equiv_deny(const ImagePolicy *policy) {
+ /* Checks if this is the deny policy (or equivalent to it), i.e. everything must be absent, aka '~' */
+ return image_policy_flags_all_match(policy, PARTITION_POLICY_ABSENT);
+}
+
+int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert(ret);
+
+ if (simplify) {
+ const char *fixed;
+
+ if (image_policy_equiv_allow(policy))
+ fixed = "*";
+ else if (image_policy_equiv_ignore(policy))
+ fixed = "-";
+ else if (image_policy_equiv_deny(policy))
+ fixed = "~";
+ else
+ fixed = NULL;
+
+ if (fixed) {
+ s = strdup(fixed);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+ }
+ }
+
+ for (size_t i = 0; i < image_policy_n_entries(policy); i++) {
+ const PartitionPolicy *p = policy->policies + i;
+ _cleanup_free_ char *f = NULL;
+ const char *t;
+
+ assert(i == 0 || p->designator > policy->policies[i-1].designator); /* Validate perfect ordering */
+
+ assert_se(t = partition_designator_to_string(p->designator));
+
+ if (simplify) {
+ /* Skip policy entries that match the default anyway */
+ PartitionPolicyFlags df;
+
+ df = partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = image_policy_default(policy),
+ .designator = p->designator,
+ });
+ if (df < 0)
+ return df;
+
+ if (df == p->flags)
+ continue;
+ }
+
+ r = partition_policy_flags_to_string(p->flags, simplify, &f);
+ if (r < 0)
+ return r;
+
+ if (!strextend(&s, isempty(s) ? "" : ":", t, "=", f))
+ return -ENOMEM;
+ }
+
+ if (!simplify || image_policy_default(policy) != PARTITION_POLICY_IGNORE) {
+ _cleanup_free_ char *df = NULL;
+
+ r = partition_policy_flags_to_string(image_policy_default(policy), simplify, &df);
+ if (r < 0)
+ return r;
+
+ if (!strextend(&s, isempty(s) ? "" : ":", "=", df))
+ return -ENOMEM;
+ }
+
+ if (isempty(s)) { /* no rule and default policy? then let's return "-" */
+ s = strdup("-");
+ if (!s)
+ return -ENOMEM;
+ }
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b) {
+ if (a == b)
+ return true;
+ if (image_policy_n_entries(a) != image_policy_n_entries(b))
+ return false;
+ if (image_policy_default(a) != image_policy_default(b))
+ return false;
+ for (size_t i = 0; i < image_policy_n_entries(a); i++) {
+ if (a->policies[i].designator != b->policies[i].designator)
+ return false;
+ if (a->policies[i].flags != b->policies[i].flags)
+ return false;
+ }
+
+ return true;
+}
+
+int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b) {
+
+ /* The image_policy_equal() function checks if the policy is defined the exact same way. This
+ * function here instead looks at the outcome of the two policies instead. Where does this come to
+ * different results you ask? We imply some logic regarding Verity/Encryption: when no rule is
+ * defined for a verity partition we can synthesize it from the protection level of the data
+ * partition it protects. Or: any per-partition rule that is identical to the default rule is
+ * redundant, and will be recognized as such by image_policy_equivalent() but not by
+ * image_policy_equal()- */
+
+ if (image_policy_default(a) != image_policy_default(b))
+ return false;
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ PartitionPolicyFlags f, w;
+
+ f = image_policy_get_exhaustively(a, d);
+ if (f < 0)
+ return f;
+
+ w = image_policy_get_exhaustively(b, d);
+ if (w < 0)
+ return w;
+
+ if (f != w)
+ return false;
+ }
+
+ return true;
+}
+
+int config_parse_image_policy(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(image_policy_freep) ImagePolicy *np = NULL;
+ ImagePolicy **p = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *p = image_policy_free(*p);
+ return 0;
+ }
+
+ r = image_policy_from_string(rvalue, &np);
+ if (r == -ENOTUNIQ)
+ return log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate rule in image policy, refusing: %s", rvalue);
+ if (r == -EBADSLT)
+ return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition type in image policy, refusing: %s", rvalue);
+ if (r == -EBADRQC)
+ return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition policy flag in image policy, refusing: %s", rvalue);
+ if (r < 0)
+ return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse image policy, refusing: %s", rvalue);
+
+ return free_and_replace_full(*p, np, image_policy_free);
+}
+
+int parse_image_policy_argument(const char *s, ImagePolicy **policy) {
+ _cleanup_(image_policy_freep) ImagePolicy *np = NULL;
+ int r;
+
+ assert(s);
+ assert(policy);
+
+ /*
+ * This function is intended to be used in command line parsers.
+ *
+ * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON SUCCESS!
+ * Hence, do not pass in uninitialized pointers.
+ */
+
+ r = image_policy_from_string(s, &np);
+ if (r == -ENOTUNIQ)
+ return log_error_errno(r, "Duplicate rule in image policy: %s", s);
+ if (r == -EBADSLT)
+ return log_error_errno(r, "Unknown partition type in image policy: %s", s);
+ if (r == -EBADRQC)
+ return log_error_errno(r, "Unknown partition policy flag in image policy: %s", s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse image policy: %s", s);
+
+ return free_and_replace_full(*policy, np, image_policy_free);
+}
+
+const ImagePolicy image_policy_allow = {
+ /* Allow policy */
+ .n_policies = 0,
+ .default_flags = PARTITION_POLICY_OPEN,
+};
+
+const ImagePolicy image_policy_deny = {
+ /* Allow policy */
+ .n_policies = 0,
+ .default_flags = PARTITION_POLICY_ABSENT,
+};
+
+const ImagePolicy image_policy_ignore = {
+ /* Allow policy */
+ .n_policies = 0,
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_sysext = {
+ /* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
+ * we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
+ * be. */
+ .n_policies = 2,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_sysext_strict = {
+ /* For system extensions, requiring signing */
+ .n_policies = 2,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_confext = {
+ /* For configuration extensions, honour root file system, and ignore everything else. After all, we
+ * are only interested in the /etc/ tree anyway, and that's really the only place it can be. */
+ .n_policies = 1,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_container = {
+ /* For systemd-nspawn containers we use all partitions, with the exception of swap */
+ .n_policies = 8,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_host = {
+ /* For the host policy we basically use everything */
+ .n_policies = 9,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_service = {
+ /* For RootImage= in services we skip ESP/XBOOTLDR and swap */
+ .n_policies = 6,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct ImagePolicy ImagePolicy;
+
+#include "conf-parser.h"
+#include "dissect-image.h"
+#include "errno-list.h"
+
+typedef enum PartitionPolicyFlags {
+ /* Not all policy flags really make sense on all partition types, see comments. But even if they
+ * don't make sense we'll parse them anyway, because maybe one day we'll add them for more partition
+ * types, too. Moreover, we allow configuring a "default" policy for all partition types for which no
+ * explicit policy is specified. It's useful if we can use policy flags in there and apply this
+ * default policy gracefully even to partition types where they don't really make too much sense
+ * on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/
+ * partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for
+ * /home/), and so on. */
+ PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */
+ PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */
+ PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */
+ PARTITION_POLICY_UNPROTECTED = 1 << 3, /* must exist, activate without encryption/verity */
+ PARTITION_POLICY_UNUSED = 1 << 4, /* must exist, don't use */
+ PARTITION_POLICY_ABSENT = 1 << 5, /* must not exist */
+ PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|
+ PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
+ PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
+ _PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN,
+
+ PARTITION_POLICY_READ_ONLY_OFF = 1 << 6, /* State of GPT partition flag "read-only" must be on */
+ PARTITION_POLICY_READ_ONLY_ON = 1 << 7,
+ _PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON,
+ PARTITION_POLICY_GROWFS_OFF = 1 << 8, /* State of GPT partition flag "growfs" must be on */
+ PARTITION_POLICY_GROWFS_ON = 1 << 9,
+ _PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON,
+ _PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
+
+ _PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
+
+ _PARTITION_POLICY_FLAGS_INVALID = -EINVAL,
+ _PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
+} PartitionPolicyFlags;
+
+assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */
+
+typedef struct PartitionPolicy {
+ PartitionDesignator designator;
+ PartitionPolicyFlags flags;
+} PartitionPolicy;
+
+struct ImagePolicy {
+ PartitionPolicyFlags default_flags; /* for any designator not listed in the list below */
+ size_t n_policies;
+ PartitionPolicy policies[]; /* sorted by designator, hence suitable for binary search */
+};
+
+/* Default policies for various usecases */
+extern const ImagePolicy image_policy_allow;
+extern const ImagePolicy image_policy_deny;
+extern const ImagePolicy image_policy_ignore;
+extern const ImagePolicy image_policy_sysext; /* No verity required */
+extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
+extern const ImagePolicy image_policy_confext; /* No verity required */
+extern const ImagePolicy image_policy_container;
+extern const ImagePolicy image_policy_service;
+extern const ImagePolicy image_policy_host;
+
+PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator);
+PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator);
+
+/* We want that the NULL image policy means "everything" allowed, hence use these simple accessors to make
+ * NULL policies work reasonably */
+static inline PartitionPolicyFlags image_policy_default(const ImagePolicy *policy) {
+ return policy ? policy->default_flags : PARTITION_POLICY_OPEN;
+}
+
+static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
+ return policy ? policy->n_policies : 0;
+}
+
+PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
+int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
+
+int image_policy_from_string(const char *s, ImagePolicy **ret);
+int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret);
+
+/* Recognizes three special policies by equivalence */
+bool image_policy_equiv_ignore(const ImagePolicy *policy);
+bool image_policy_equiv_allow(const ImagePolicy *policy);
+bool image_policy_equiv_deny(const ImagePolicy *policy);
+
+bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b); /* checks if defined the same way, i.e. has literally the same ruleset */
+int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
+
+static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
+ return mfree(p);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_image_policy);
+int parse_image_policy_argument(const char *s, ImagePolicy **policy);
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "conf-parser.h"
#include "constants.h"
OrderedHashmap *have_processed;
} InstallContext;
-typedef enum {
- PRESET_UNKNOWN,
- PRESET_ENABLE,
- PRESET_DISABLE,
-} PresetAction;
-
struct UnitFilePresetRule {
char *pattern;
PresetAction action;
char **instances;
};
+/* NB! strings use past tense. */
+static const char *const preset_action_past_tense_table[_PRESET_ACTION_MAX] = {
+ [PRESET_UNKNOWN] = "unknown",
+ [PRESET_ENABLE] = "enabled",
+ [PRESET_DISABLE] = "disabled",
+ [PRESET_IGNORE] = "ignored",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(preset_action_past_tense, PresetAction);
+
static bool install_info_has_rules(const InstallInfo *i) {
assert(i);
return !strv_isempty(i->also);
}
-void unit_file_presets_freep(UnitFilePresets *p) {
+static void unit_file_preset_rule_done(UnitFilePresetRule *rule) {
+ assert(rule);
+
+ free(rule->pattern);
+ strv_free(rule->instances);
+}
+
+void unit_file_presets_done(UnitFilePresets *p) {
if (!p)
return;
- for (size_t i = 0; i < p->n_rules; i++) {
- free(p->rules[i].pattern);
- strv_free(p->rules[i].instances);
- }
+ FOREACH_ARRAY(rule, p->rules, p->n_rules)
+ unit_file_preset_rule_done(rule);
free(p->rules);
p->n_rules = 0;
if (!found) {
_cleanup_free_ char *dest = NULL;
- q = chase_symlinks(p, lp->root_dir, CHASE_NONEXISTENT, &dest, NULL);
+ q = chase(p, lp->root_dir, CHASE_NONEXISTENT, &dest, NULL);
if (q == -ENOENT)
continue;
if (q < 0) {
if (!(flags & SEARCH_LOAD))
return 0;
- fd = chase_symlinks_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ fd = chase_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (fd < 0)
return fd;
}
if (!alias_path)
return -ENOMEM;
- q = chase_symlinks(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL);
+ q = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL);
if (q < 0 && q != -ENOENT) {
r = r < 0 ? r : q;
continue;
}
static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePresets *presets) {
- _cleanup_(unit_file_presets_freep) UnitFilePresets ps = {};
+ _cleanup_(unit_file_presets_done) UnitFilePresets ps = {};
_cleanup_strv_free_ char **files = NULL;
int r;
for (;;) {
_cleanup_free_ char *line = NULL;
- UnitFilePresetRule rule = {};
+ _cleanup_(unit_file_preset_rule_done) UnitFilePresetRule rule = {};
const char *parameter;
char *l;
};
}
+ parameter = first_word(l, "ignore");
+ if (parameter) {
+ char *pattern;
+
+ pattern = strdup(parameter);
+ if (!pattern)
+ return -ENOMEM;
+
+ rule = (UnitFilePresetRule) {
+ .pattern = pattern,
+ .action = PRESET_IGNORE,
+ };
+ }
+
if (rule.action) {
if (!GREEDY_REALLOC(ps.rules, ps.n_rules + 1))
return -ENOMEM;
- ps.rules[ps.n_rules++] = rule;
+ ps.rules[ps.n_rules++] = TAKE_STRUCT(rule);
continue;
}
}
ps.initialized = true;
- *presets = ps;
- ps = (UnitFilePresets){};
+ *presets = TAKE_STRUCT(ps);
return 0;
}
switch (action) {
case PRESET_UNKNOWN:
log_debug("Preset files don't specify rule for %s. Enabling.", name);
- return 1;
+ return PRESET_ENABLE;
case PRESET_ENABLE:
if (instance_name_list && *instance_name_list)
STRV_FOREACH(s, *instance_name_list)
log_debug("Preset files say enable %s.", *s);
else
log_debug("Preset files say enable %s.", name);
- return 1;
+ return PRESET_ENABLE;
case PRESET_DISABLE:
log_debug("Preset files say disable %s.", name);
- return 0;
+ return PRESET_DISABLE;
+ case PRESET_IGNORE:
+ log_debug("Preset files say ignore %s.", name);
+ return PRESET_IGNORE;
default:
assert_not_reached();
}
}
-int unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
- _cleanup_(unit_file_presets_freep) UnitFilePresets tmp = {};
+PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
+ _cleanup_(unit_file_presets_done) UnitFilePresets tmp = {};
int r;
if (!cached)
if (r < 0)
return r;
- if (r > 0) {
+ if (r == PRESET_ENABLE) {
if (instance_name_list)
STRV_FOREACH(s, instance_name_list) {
r = install_info_discover_and_check(plus, lp, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
return r;
}
- } else
+ } else if (r == PRESET_DISABLE)
r = install_info_discover(minus, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
&info, changes, n_changes);
_cleanup_(install_context_done) InstallContext plus = {}, minus = {};
_cleanup_(lookup_paths_free) LookupPaths lp = {};
- _cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
+ _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
const char *config_path;
int r;
_cleanup_(install_context_done) InstallContext plus = {}, minus = {};
_cleanup_(lookup_paths_free) LookupPaths lp = {};
- _cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
+ _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
const char *config_path = NULL;
int r;
return execute_preset(file_flags, &plus, &minus, &lp, config_path, NULL, mode, changes, n_changes);
}
-static UnitFileList* unit_file_list_free_one(UnitFileList *f) {
+static UnitFileList* unit_file_list_free(UnitFileList *f) {
if (!f)
return NULL;
return mfree(f);
}
-Hashmap* unit_file_list_free(Hashmap *h) {
- return hashmap_free_with_destructor(h, unit_file_list_free_one);
-}
+DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ unit_file_list_hash_ops_free,
+ char,
+ string_hash_func,
+ string_compare_func,
+ UnitFileList,
+ unit_file_list_free);
int unit_file_get_list(
RuntimeScope scope,
}
FOREACH_DIRENT(de, d, return -errno) {
- _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL;
+ _cleanup_(unit_file_list_freep) UnitFileList *f = NULL;
if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
continue;
int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name);
int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
-Hashmap* unit_file_list_free(Hashmap *h);
+
+extern const struct hash_ops unit_file_list_hash_ops_free;
InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source);
void install_changes_free(InstallChange *changes, size_t n_changes);
bool initialized;
} UnitFilePresets;
-void unit_file_presets_freep(UnitFilePresets *p);
-int unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached);
+typedef enum PresetAction {
+ PRESET_UNKNOWN,
+ PRESET_ENABLE,
+ PRESET_DISABLE,
+ PRESET_IGNORE,
+ _PRESET_ACTION_MAX,
+ _PRESET_ACTION_INVALID = -EINVAL,
+ _PRESET_ACTION_ERRNO_MAX = -ERRNO_MAX, /* Ensure this type covers the whole negative errno range */
+} PresetAction;
+
+const char *preset_action_past_tense_to_string(PresetAction action);
+
+void unit_file_presets_done(UnitFilePresets *p);
+PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached);
const char *unit_file_state_to_string(UnitFileState s) _const_;
UnitFileState unit_file_state_from_string(const char *s) _pure_;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "fd-util.h"
+#include "fileio.h"
#include "env-file.h"
#include "kernel-image.h"
#include "os-util.h"
}
int inspect_kernel(
+ int dir_fd,
const char *filename,
KernelImageType *ret_type,
char **ret_cmdline,
KernelImageType t;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(filename);
- f = fopen(filename, "re");
- if (!f)
- return log_error_errno(errno, "Failed to open kernel image file '%s': %m", filename);
+ r = xfopenat(dir_fd, filename, "re", 0, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open kernel image file '%s': %m", filename);
r = pe_sections(f, §ions, &scount);
if (r < 0)
const char* kernel_image_type_to_string(KernelImageType t) _const_;
int inspect_kernel(
+ int dir_fd,
const char *filename,
KernelImageType *ret_type,
char **ret_cmdline,
if (r < 0)
return log_error_errno(r, "Failed to find catalog entry: %m");
- weblink = startswith(t, "Documentation:");
- if (!weblink) {
- weblink = strstr(t + 1, "\nDocumentation:");
- if (!weblink)
- goto notfound;
-
- weblink += 15;
- }
+ weblink = find_line_startswith(t, "Documentation:");
+ if (!weblink)
+ goto notfound;
/* Skip whitespace to value */
weblink += strspn(weblink, " \t");
#include "env-util.h"
#include "errno-util.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "fileio.h"
#include "loop-util.h"
#include "missing_loop.h"
static int loop_is_bound(int fd) {
struct loop_info64 info;
- assert(fd >= 0);
-
- if (ioctl(fd, LOOP_GET_STATUS64, &info) < 0) {
+ if (ioctl(ASSERT_FD(fd), LOOP_GET_STATUS64, &info) < 0) {
if (errno == ENXIO)
return false; /* not bound! */
static int open_lock_fd(int primary_fd, int operation) {
_cleanup_close_ int lock_fd = -EBADF;
- assert(primary_fd >= 0);
assert(IN_SET(operation & ~LOCK_NB, LOCK_SH, LOCK_EX));
- lock_fd = fd_reopen(primary_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+ lock_fd = fd_reopen(ASSERT_FD(primary_fd), O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (lock_fd < 0)
return lock_fd;
int r, f_flags;
struct stat st;
- assert(fd >= 0);
assert(ret);
assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
- if (fstat(fd, &st) < 0)
+ if (fstat(ASSERT_FD(fd), &st) < 0)
return -errno;
if (S_ISBLK(st.st_mode)) {
ret);
}
-int loop_device_make_by_path(
+int loop_device_make_by_path_at(
+ int dir_fd,
const char *path,
int open_flags,
uint32_t sector_size,
_cleanup_close_ int fd = -EBADF;
bool direct = false;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(path);
assert(ret);
assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY));
direct_flags = FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO) ? O_DIRECT : 0;
rdwr_flags = open_flags >= 0 ? open_flags : O_RDWR;
- fd = open(path, basic_flags|direct_flags|rdwr_flags);
+ fd = xopenat(dir_fd, path, basic_flags|direct_flags|rdwr_flags, 0);
if (fd < 0 && direct_flags != 0) /* If we had O_DIRECT on, and things failed with that, let's immediately try again without */
- fd = open(path, basic_flags|rdwr_flags);
+ fd = xopenat(dir_fd, path, basic_flags|rdwr_flags, 0);
else
direct = direct_flags != 0;
if (fd < 0) {
if (open_flags >= 0 || !(ERRNO_IS_PRIVILEGE(r) || r == -EROFS))
return r;
- fd = open(path, basic_flags|direct_flags|O_RDONLY);
+ fd = xopenat(dir_fd, path, basic_flags|direct_flags|O_RDONLY, 0);
if (fd < 0 && direct_flags != 0) /* as above */
- fd = open(path, basic_flags|O_RDONLY);
+ fd = xopenat(dir_fd, path, basic_flags|O_RDONLY, 0);
else
direct = direct_flags != 0;
if (fd < 0)
direct ? "enabled" : "disabled",
direct != (direct_flags != 0) ? " (O_DIRECT was requested but not supported)" : "");
- return loop_device_make_internal(path, fd, open_flags, 0, 0, sector_size, loop_flags, lock_op, ret);
+ return loop_device_make_internal(
+ dir_fd == AT_FDCWD ? path : NULL,
+ fd,
+ open_flags,
+ /* offset = */ 0,
+ /* size = */ 0,
+ sector_size,
+ loop_flags,
+ lock_op,
+ ret);
}
int loop_device_make_by_path_memory(
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
int r;
- assert(fd >= 0);
-
- r = block_device_new_from_fd(fd, 0, &dev);
+ r = block_device_new_from_fd(ASSERT_FD(fd), 0, &dev);
if (r < 0)
return r;
dev_t devno;
int r;
- assert(partition_fd >= 0);
-
/* Resizes the partition the loopback device refer to (assuming it refers to one instead of an actual
* loopback device), and changes the offset, if needed. This is a fancy wrapper around
* BLKPG_RESIZE_PARTITION. */
- if (fstat(partition_fd, &st) < 0)
+ if (fstat(ASSERT_FD(partition_fd), &st) < 0)
return -errno;
assert(S_ISBLK(st.st_mode));
/* If we had no lock fd so far, create one and lock it right-away */
if (d->lock_fd < 0) {
- assert(d->fd >= 0);
-
- d->lock_fd = open_lock_fd(d->fd, operation);
+ d->lock_fd = open_lock_fd(ASSERT_FD(d->fd), operation);
if (d->lock_fd < 0)
return d->lock_fd;
int loop_device_sync(LoopDevice *d) {
assert(d);
- assert(d->fd >= 0);
/* We also do this implicitly in loop_device_unref(). Doing this explicitly here has the benefit that
* we can check the return value though. */
- return RET_NERRNO(fsync(d->fd));
+ return RET_NERRNO(fsync(ASSERT_FD(d->fd)));
}
int loop_device_set_autoclear(LoopDevice *d, bool autoclear) {
assert(d);
- if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0)
+ if (ioctl(ASSERT_FD(d->fd), LOOP_GET_STATUS64, &info) < 0)
return -errno;
if (autoclear == FLAGS_SET(info.lo_flags, LO_FLAGS_AUTOCLEAR))
if (name && strlen(name) >= sizeof(info.lo_file_name))
return -ENOBUFS;
- if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0)
+ if (ioctl(ASSERT_FD(d->fd), LOOP_GET_STATUS64, &info) < 0)
return -errno;
if (strneq((char*) info.lo_file_name, strempty(name), sizeof(info.lo_file_name)))
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <fcntl.h>
+
#include "sd-device.h"
#include "macro.h"
#define LOOP_DEVICE_IS_FOREIGN(d) ((d)->nr < 0)
int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
-int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+int loop_device_make_by_path_at(int dir_fd, const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+static inline int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) {
+ return loop_device_make_by_path_at(AT_FDCWD, path, open_flags, sector_size, loop_flags, lock_op, ret);
+}
int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
int loop_device_open(sd_device *dev, int open_flags, int lock_op, LoopDevice **ret);
int loop_device_open_from_fd(int fd, int open_flags, int lock_op, LoopDevice **ret);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "fileio.h"
+#include "lsm-util.h"
+#include "string-util.h"
+
+int lsm_supported(const char *name) {
+ _cleanup_free_ char *lsm_list = NULL;
+ int r;
+
+ assert(name);
+
+ r = read_one_line_file("/sys/kernel/security/lsm", &lsm_list);
+ if (r == -ENOENT) /* LSM support not available at all? */
+ return false;
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read /sys/kernel/security/lsm: %m");
+
+ for (const char *p = lsm_list;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, ",", 0);
+ if (r == 0)
+ return false;
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse /sys/kernel/security/lsm: %m");
+
+ if (streq(word, name))
+ return true;
+ }
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int lsm_supported(const char *name);
#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) {
r = impl; \
if (r < 0) \
(void) sd_notifyf(0, "ERRNO=%i", -r); \
+ (void) sd_notifyf(0, "EXIT_STATUS=%i", ret); \
ask_password_agent_close(); \
polkit_agent_close(); \
pager_close(); \
'bitmap.c',
'blockdev-util.c',
'bond-util.c',
+ 'boot-entry.c',
'boot-timestamps.c',
'bootspec.c',
'bpf-dlopen.c',
'ethtool-util.c',
'exec-util.c',
'exit-status.c',
- 'extension-release.c',
+ 'extension-util.c',
'fdset.c',
'fileio-label.c',
'find-esp.c',
'id128-print.c',
'idn-util.c',
'ima-util.c',
+ 'image-policy.c',
'import-util.c',
'in-addr-prefix-util.c',
'install-file.c',
'logs-show.c',
'loop-util.c',
'loopback-setup.c',
+ 'lsm-util.c',
'machine-id-setup.c',
'machine-pool.c',
'macvlan-util.c',
shared_sources += files('nscd-flush.c')
endif
-if conf.get('HAVE_LIBFIDO2') == 1
+if conf.get('HAVE_LIBFIDO2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1
shared_sources += files('cryptsetup-fido2.c')
endif
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,
#endif
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dissect-image.h"
#include "exec-util.h"
#include "extract-word.h"
bool read_only,
bool make_file_or_directory,
const MountOptions *options,
+ const ImagePolicy *image_policy,
bool is_image) {
_cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF;
if (r < 0)
return log_debug_errno(r == -ENOENT ? SYNTHETIC_ERRNO(EOPNOTSUPP) : r, "Target does not allow propagation of mount points");
- r = chase_symlinks(src, NULL, 0, &chased_src_path, &chased_src_fd);
+ r = chase(src, NULL, 0, &chased_src_path, &chased_src_fd);
if (r < 0)
return log_debug_errno(r, "Failed to resolve source path of %s: %m", src);
log_debug("Chased source path of %s to %s", src, chased_src_path);
mount_tmp_created = true;
if (is_image)
- r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, NULL, NULL, NULL, NULL);
+ r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, image_policy, NULL, NULL, NULL, NULL);
else
r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)
bool read_only,
bool make_file_or_directory) {
- return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, NULL, false);
+ return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, /* options= */ NULL, /* image_policy= */ NULL, /* is_image= */ false);
}
int mount_image_in_namespace(
const char *dest,
bool read_only,
bool make_file_or_directory,
- const MountOptions *options) {
+ const MountOptions *options,
+ const ImagePolicy *image_policy) {
- return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, true);
+ return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, image_policy, /* is_image=*/ true);
}
int make_mount_point(const char *path) {
return 1;
}
-static int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
+int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
_cleanup_close_ int userns_fd = -EBADF;
_cleanup_free_ char *line = NULL;
/* Allocates a userns file descriptor with the mapping we need. For this we'll fork off a child
* process whose only purpose is to give us a new user namespace. It's killed when we got it. */
+ if (!userns_shift_range_valid(uid_shift, uid_range))
+ return -EINVAL;
+
if (IN_SET(idmapping, REMOUNT_IDMAPPING_NONE, REMOUNT_IDMAPPING_HOST_ROOT)) {
if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0u, uid_shift, uid_range) < 0)
return log_oom_debug();
return TAKE_FD(userns_fd);
}
-int remount_idmap(
+int remount_idmap_fd(
const char *p,
- uid_t uid_shift,
- uid_t uid_range,
- uid_t owner,
- RemountIdmapping idmapping) {
+ int userns_fd) {
- _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF;
+ _cleanup_close_ int mount_fd = -EBADF;
int r;
assert(p);
-
- if (!userns_shift_range_valid(uid_shift, uid_range))
- return -EINVAL;
+ assert(userns_fd >= 0);
/* Clone the mount point */
mount_fd = open_tree(-1, p, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
if (mount_fd < 0)
return log_debug_errno(errno, "Failed to open tree of mounted filesystem '%s': %m", p);
- /* Create a user namespace mapping */
- userns_fd = make_userns(uid_shift, uid_range, owner, idmapping);
- if (userns_fd < 0)
- return userns_fd;
-
/* Set the user namespace mapping attribute on the cloned mount point */
if (mount_setattr(mount_fd, "", AT_EMPTY_PATH | AT_RECURSIVE,
&(struct mount_attr) {
return 0;
}
+int remount_idmap(const char *p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
+ _cleanup_close_ int userns_fd = -EBADF;
+
+ userns_fd = make_userns(uid_shift, uid_range, owner, idmapping);
+ if (userns_fd < 0)
+ return userns_fd;
+
+ return remount_idmap_fd(p, userns_fd);
+}
+
typedef struct SubMount {
char *path;
int mount_fd;
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);
-int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options);
+int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options, const ImagePolicy *image_policy);
int make_mount_point(const char *path);
_REMOUNT_IDMAPPING_INVALID = -EINVAL,
} RemountIdmapping;
+int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping);
+int remount_idmap_fd(const char *p, int userns_fd);
int remount_idmap(const char *p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping);
int remount_and_move_sub_mounts(
return r;
}
- *ret = s;
- s = (CPUSet) {};
+ *ret = TAKE_STRUCT(s);
return 0;
}
}
static void cleanup_system_bus(pam_handle_t *handle, void *data, int error_status) {
+ /* The PAM_DATA_SILENT flag is the way that pam_end() communicates to the module stack that this
+ * invocation of pam_end() is not the final one, but in the process that is going to directly exec
+ * the child. This means we are being called after a fork(), and we do not want to try and clean
+ * up the sd-bus object, as it would affect the parent too and we'll hit an assertion. */
+ if (error_status & PAM_DATA_SILENT)
+ return (void) pam_syslog_pam_error(
+ handle,
+ LOG_ERR,
+ SYNTHETIC_ERRNO(EUCLEAN),
+ "Attempted to close sd-bus after fork, this should not happen.");
+
sd_bus_flush_close_unref(data);
}
-int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret) {
+int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret) {
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ _cleanup_free_ char *cache_id = NULL;
int r;
assert(handle);
+ assert(module_name);
assert(ret);
+ cache_id = strjoin("system-bus-", module_name);
+ if (!cache_id)
+ return pam_log_oom(handle);
+
/* We cache the bus connection so that we can share it between the session and the authentication hooks */
- r = pam_get_data(handle, "systemd-system-bus", (const void**) &bus);
+ r = pam_get_data(handle, cache_id, (const void**) &bus);
if (r == PAM_SUCCESS && bus) {
*ret = sd_bus_ref(TAKE_PTR(bus)); /* Increase the reference counter, so that the PAM data stays valid */
return PAM_SUCCESS;
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to connect to system bus: %m");
- r = pam_set_data(handle, "systemd-system-bus", bus, cleanup_system_bus);
+ r = pam_set_data(handle, cache_id, bus, cleanup_system_bus);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@");
return PAM_SUCCESS;
}
-int pam_release_bus_connection(pam_handle_t *handle) {
+int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) {
+ _cleanup_free_ char *cache_id = NULL;
int r;
- r = pam_set_data(handle, "systemd-system-bus", NULL, NULL);
+ assert(module_name);
+
+ cache_id = strjoin("system-bus-", module_name);
+ if (!cache_id)
+ return pam_log_oom(handle);
+
+ r = pam_set_data(handle, cache_id, NULL, NULL);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to release PAM user record data: @PAMERR@");
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse bus message: %m");
}
-int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret);
-int pam_release_bus_connection(pam_handle_t *handle);
+/* Use a different module name per different PAM module. They are all loaded in the same namespace, and this
+ * helps avoid a clash in the internal data structures of sd-bus. It will be used as key for cache items. */
+int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret);
+int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
#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;
"exit_group\0"
"futex\0"
"futex_time64\0"
+ "futex_waitv\0"
"get_robust_list\0"
"get_thread_area\0"
"getegid\0"
"open_by_handle_at\0"
"pivot_root\0"
"quotactl\0"
+ "quotactl_fd\0"
"setdomainname\0"
"setfsuid\0"
"setfsuid32\0"
"sched_setparam\0"
"sched_setscheduler\0"
"set_mempolicy\0"
+ "set_mempolicy_home_node\0"
"setpriority\0"
"setrlimit\0"
},
+ [SYSCALL_FILTER_SET_SANDBOX] = {
+ .name = "@sandbox",
+ .help = "Sandbox functionality",
+ .value =
+ "landlock_add_rule\0"
+ "landlock_create_ruleset\0"
+ "landlock_restrict_self\0"
+ "seccomp\0"
+ },
[SYSCALL_FILTER_SET_SETUID] = {
.name = "@setuid",
.help = "Operations for changing user/group credentials",
SYSCALL_FILTER_SET_RAW_IO,
SYSCALL_FILTER_SET_REBOOT,
SYSCALL_FILTER_SET_RESOURCES,
+ SYSCALL_FILTER_SET_SANDBOX,
SYSCALL_FILTER_SET_SETUID,
SYSCALL_FILTER_SET_SIGNAL,
SYSCALL_FILTER_SET_SWAP,
SYSCALL_FILTER_SET_SYSTEM_SERVICE,
SYSCALL_FILTER_SET_TIMER,
SYSCALL_FILTER_SET_KNOWN,
- _SYSCALL_FILTER_SET_MAX
+ _SYSCALL_FILTER_SET_MAX,
};
+assert_cc(SYSCALL_FILTER_SET_DEFAULT == 0);
+assert_cc(SYSCALL_FILTER_SET_KNOWN == _SYSCALL_FILTER_SET_MAX-1);
+
extern const SyscallFilterSet syscall_filter_sets[];
const SyscallFilterSet *syscall_filter_set_find(const char *name);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <stdbool.h>
+
#include "missing_securebits.h"
int secure_bits_to_string_alloc(int i, char **s);
#include "alloc-util.h"
#include "architecture.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
if (!path)
return -ENOENT;
- return chase_symlinks(path, root, 0, ret, NULL);
+ return chase(path, root, 0, ret, NULL);
}
int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
assert(ret);
- if (root) {
- _cleanup_close_ int fd = -EBADF;
-
- fd = chase_symlinks_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 <unistd.h>
#include "base-filesystem.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "initrd-util.h"
#include "log.h"
old_root_fd = safe_close(old_root_fd);
/* Determine where we shall place the old root after the transition */
- r = chase_symlinks(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL);
+ r = chase(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, old_root_after);
if (r == 0) /* Doesn't exist yet. Let's create it */
FOREACH_STRING(path, "/sys", "/dev", "/run", "/proc") {
_cleanup_free_ char *chased = NULL;
- r = chase_symlinks(path, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL);
+ r = chase(path, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, path);
if (r > 0) {
#include "fs-util.h"
#include "hexdecoct.h"
#include "hmac.h"
+#include "lock-util.h"
+#include "log.h"
+#include "logarithm.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "parse-util.h"
#include "random-util.h"
#include "sha256.h"
#include "stat-util.h"
+#include "string-table.h"
#include "time-util.h"
#include "tpm2-util.h"
#include "virt.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);
uint32_t mask;
tpm2_tpms_pcr_selection_to_mask(s, &mask);
- return (size_t)__builtin_popcount(mask);
+ return popcount(mask);
}
/* Utility functions for TPML_PCR_SELECTION. */
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);
if (r < 0)
return log_error_errno(r, "Failed to parse PCR list: %s", arg);
- r = safe_atou(pcr, &n);
+ r = pcr_index_from_string(pcr);
if (r < 0)
- return log_error_errno(r, "Failed to parse PCR number: %s", pcr);
- if (n >= TPM2_PCRS_MAX)
- return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
- "PCR number out of range (valid range 0…%u): %u",
- TPM2_PCRS_MAX - 1, n);
-
+ return log_error_errno(r, "Failed to parse specified PCR or specified PCR is out of range: %s", pcr);
+ n = r;
SET_BIT(mask, n);;
}
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;
}
return 0;
}
+
+static const char* const pcr_index_table[_PCR_INDEX_MAX_DEFINED] = {
+ [PCR_PLATFORM_CODE] = "platform-code",
+ [PCR_PLATFORM_CONFIG] = "platform-config",
+ [PCR_EXTERNAL_CODE] = "external-code",
+ [PCR_EXTERNAL_CONFIG] = "external-config",
+ [PCR_BOOT_LOADER_CODE] = "boot-loader-code",
+ [PCR_BOOT_LOADER_CONFIG] = "boot-loader-config",
+ [PCR_SECURE_BOOT_POLICY] = "secure-boot-policy",
+ [PCR_KERNEL_INITRD] = "kernel-initrd",
+ [PCR_IMA] = "ima",
+ [PCR_KERNEL_BOOT] = "kernel-boot",
+ [PCR_KERNEL_CONFIG] = "kernel-config",
+ [PCR_SYSEXTS] = "sysexts",
+ [PCR_SHIM_POLICY] = "shim-policy",
+ [PCR_SYSTEM_IDENTITY] = "system-identity",
+ [PCR_DEBUG] = "debug",
+ [PCR_APPLICATION_SUPPORT] = "application-support",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(pcr_index, int, TPM2_PCRS_MAX);
} 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)
TPM2_SUPPORT_FULL = TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM,
} Tpm2Support;
+typedef enum PcrIndex {
+/* The following names for PCRs 0…7 are based on the names in the "TCG PC Client Specific Platform Firmware Profile Specification" (https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/) */
+ PCR_PLATFORM_CODE = 0,
+ PCR_PLATFORM_CONFIG = 1,
+ PCR_EXTERNAL_CODE = 2,
+ PCR_EXTERNAL_CONFIG = 3,
+ PCR_BOOT_LOADER_CODE = 4,
+ PCR_BOOT_LOADER_CONFIG = 5,
+ PCR_SECURE_BOOT_POLICY = 7,
+/* The following names for PCRs 9…15 are based on the "Linux TPM PCR Registry"
+(https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/) */
+ PCR_KERNEL_INITRD = 9,
+ PCR_IMA = 10,
+ PCR_KERNEL_BOOT = 11,
+ PCR_KERNEL_CONFIG = 12,
+ PCR_SYSEXTS = 13,
+ PCR_SHIM_POLICY = 14,
+ PCR_SYSTEM_IDENTITY = 15,
+/* As per "TCG PC Client Specific Platform Firmware Profile Specification" again, see above */
+ PCR_DEBUG = 16,
+ PCR_APPLICATION_SUPPORT = 23,
+ _PCR_INDEX_MAX_DEFINED = TPM2_PCRS_MAX,
+ _PCR_INDEX_INVALID = -EINVAL,
+} PcrIndex;
+
Tpm2Support tpm2_support(void);
int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask);
const void *salt,
size_t saltlen,
uint8_t res[static SHA256_DIGEST_SIZE]);
+
+int pcr_index_from_string(const char *s);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "missing_threads.h"
if (!path)
path = "/etc/login.defs";
- r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", NULL, &f);
+ r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", NULL, &f);
if (r == -ENOENT)
goto defaults;
if (r < 0)
int r;
assert(pwd);
- assert(ret);
if (isempty(pwd->pw_name))
return -EINVAL;
hr->mask = USER_RECORD_REGULAR |
(!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
- *ret = TAKE_PTR(hr);
+ if (ret)
+ *ret = TAKE_PTR(hr);
return 0;
}
int r;
assert(name);
- assert(ret);
for (;;) {
buf = malloc(buflen);
if (r < 0)
return r;
- (*ret)->incomplete = incomplete;
+ if (ret)
+ (*ret)->incomplete = incomplete;
return 0;
}
struct spwd spwd, *sresult = NULL;
int r;
- assert(ret);
-
for (;;) {
buf = malloc(buflen);
if (!buf)
if (r < 0)
return r;
- (*ret)->incomplete = incomplete;
+ if (ret)
+ (*ret)->incomplete = incomplete;
return 0;
}
int r;
assert(grp);
- assert(ret);
if (isempty(grp->gr_name))
return -EINVAL;
g->mask = USER_RECORD_REGULAR |
(!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
- *ret = TAKE_PTR(g);
+ if (ret)
+ *ret = TAKE_PTR(g);
return 0;
}
int r;
assert(name);
- assert(ret);
for (;;) {
buf = malloc(buflen);
if (r < 0)
return r;
- (*ret)->incomplete = incomplete;
+ if (ret)
+ (*ret)->incomplete = incomplete;
return 0;
}
struct sgrp sgrp, *sresult = NULL;
int r;
- assert(ret);
-
for (;;) {
buf = malloc(buflen);
if (!buf)
if (r < 0)
return r;
- (*ret)->incomplete = incomplete;
+ if (ret)
+ (*ret)->incomplete = incomplete;
return 0;
}
bool incomplete;
};
-static void user_group_data_release(struct user_group_data *d) {
+static void user_group_data_done(struct user_group_data *d) {
json_variant_unref(d->record);
}
+struct membership_data {
+ char *user_name;
+ char *group_name;
+};
+
+static void membership_data_done(struct membership_data *d) {
+ free(d->user_name);
+ free(d->group_name);
+}
+
static int userdb_on_query_reply(
Varlink *link,
JsonVariant *parameters,
switch (iterator->what) {
case LOOKUP_USER: {
- _cleanup_(user_group_data_release) struct user_group_data user_data = {};
+ _cleanup_(user_group_data_done) struct user_group_data user_data = {};
static const JsonDispatch dispatch_table[] = {
{ "record", _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
}
case LOOKUP_GROUP: {
- _cleanup_(user_group_data_release) struct user_group_data group_data = {};
+ _cleanup_(user_group_data_done) struct user_group_data group_data = {};
static const JsonDispatch dispatch_table[] = {
{ "record", _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
}
case LOOKUP_MEMBERSHIP: {
- struct membership_data {
- const char *user_name;
- const char *group_name;
- } membership_data = {};
+ _cleanup_(membership_data_done) struct membership_data membership_data = {};
static const JsonDispatch dispatch_table[] = {
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, user_name), JSON_RELAX },
if (r < 0)
goto finish;
- iterator->found_user_name = mfree(iterator->found_user_name);
- iterator->found_group_name = mfree(iterator->found_group_name);
-
- iterator->found_user_name = strdup(membership_data.user_name);
- if (!iterator->found_user_name) {
- r = -ENOMEM;
- goto finish;
- }
-
- iterator->found_group_name = strdup(membership_data.group_name);
- if (!iterator->found_group_name) {
- r = -ENOMEM;
- goto finish;
- }
-
+ iterator->found_user_name = TAKE_PTR(membership_data.user_name);
+ iterator->found_group_name = TAKE_PTR(membership_data.group_name);
iterator->n_found++;
if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
VARLINK_PENDING_METHOD, \
VARLINK_PENDING_METHOD_MORE)
+typedef struct VarlinkJsonQueueItem VarlinkJsonQueueItem;
+
+/* A queued message we shall write into the socket, along with the file descriptors to send at the same
+ * time. This queue item binds them together so that message/fd boundaries are maintained throughout the
+ * whole pipeline. */
+struct VarlinkJsonQueueItem {
+ LIST_FIELDS(VarlinkJsonQueueItem, queue);
+ JsonVariant *data;
+ size_t n_fds;
+ int fds[];
+};
+
struct Varlink {
unsigned n_ref;
size_t output_buffer_index;
size_t output_buffer_size;
+ int *input_fds; /* file descriptors associated with the data in input_buffer (for fd passing) */
+ size_t n_input_fds;
+
+ int *output_fds; /* file descriptors associated with the data in output_buffer (for fd passing) */
+ size_t n_output_fds;
+
+ /* Further messages to output not yet formatted into text, and thus not included in output_buffer
+ * yet. We keep them separate from output_buffer, to not violate fd message boundaries: we want that
+ * each fd that is sent is associated with its fds, and that fds cannot be accidentally associated
+ * with preceding or following messages. */
+ LIST_HEAD(VarlinkJsonQueueItem, output_queue);
+ VarlinkJsonQueueItem *output_queue_tail;
+
+ /* The fds to associate with the next message that is about to be enqueued. The user first pushes the
+ * fds it intends to send via varlink_push_fd() into this queue, and then once the message data is
+ * submitted we'll combine the fds and the message data into one. */
+ int *pushed_fds;
+ size_t n_pushed_fds;
+
VarlinkReply reply_callback;
JsonVariant *current;
- JsonVariant *reply;
struct ucred ucred;
bool ucred_acquired:1;
bool prefer_read_write:1;
bool got_pollhup:1;
+ bool allow_fd_passing_input:1;
+ bool allow_fd_passing_output:1;
+
+ bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */
+
+ int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */
+
usec_t timestamp;
usec_t timeout;
#define varlink_server_log(s, fmt, ...) \
log_debug("%s: " fmt, varlink_server_description(s), ##__VA_ARGS__)
+static int varlink_format_queue(Varlink *v);
+
static inline const char *varlink_description(Varlink *v) {
return (v ? v->description : NULL) ?: "varlink";
}
return (s ? s->description : NULL) ?: "varlink";
}
+static VarlinkJsonQueueItem *varlink_json_queue_item_free(VarlinkJsonQueueItem *q) {
+ if (!q)
+ return NULL;
+
+ json_variant_unref(q->data);
+ close_many(q->fds, q->n_fds);
+
+ return mfree(q);
+}
+
+static VarlinkJsonQueueItem *varlink_json_queue_item_new(JsonVariant *m, const int fds[], size_t n_fds) {
+ VarlinkJsonQueueItem *q;
+
+ assert(m);
+ assert(fds || n_fds == 0);
+
+ q = malloc(offsetof(VarlinkJsonQueueItem, fds) + sizeof(int) * n_fds);
+ if (!q)
+ return NULL;
+
+ *q = (VarlinkJsonQueueItem) {
+ .data = json_variant_ref(m),
+ .n_fds = n_fds,
+ };
+
+ memcpy_safe(q->fds, fds, n_fds * sizeof(int));
+
+ return TAKE_PTR(q);
+}
+
static void varlink_set_state(Varlink *v, VarlinkState state) {
assert(v);
assert(state >= 0 && state < _VARLINK_STATE_MAX);
.ucred = UCRED_INVALID,
.timestamp = USEC_INFINITY,
- .timeout = VARLINK_DEFAULT_TIMEOUT_USEC
+ .timeout = VARLINK_DEFAULT_TIMEOUT_USEC,
+
+ .af = -1,
};
*ret = v;
return log_debug_errno(errno, "Failed to create AF_UNIX socket: %m");
v->fd = fd_move_above_stdio(v->fd);
+ v->af = AF_UNIX;
r = sockaddr_un_set_path(&sockaddr.un, address);
if (r < 0) {
return log_debug_errno(r, "Failed to create varlink object: %m");
v->fd = fd;
+ v->af = -1,
varlink_set_state(v, VARLINK_IDLE_CLIENT);
/* Note that if this function is called we assume the passed socket (if it is one) is already
v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source);
}
+static void varlink_clear_current(Varlink *v) {
+ assert(v);
+
+ /* Clears the currently processed incoming message */
+ v->current = json_variant_unref(v->current);
+
+ close_many(v->input_fds, v->n_input_fds);
+ v->input_fds = mfree(v->input_fds);
+ v->n_input_fds = 0;
+}
+
static void varlink_clear(Varlink *v) {
assert(v);
v->fd = safe_close(v->fd);
+ varlink_clear_current(v);
+
v->input_buffer = mfree(v->input_buffer);
- v->output_buffer = mfree(v->output_buffer);
+ v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer);
- v->current = json_variant_unref(v->current);
- v->reply = json_variant_unref(v->reply);
+ varlink_clear_current(v);
+
+ close_many(v->output_fds, v->n_output_fds);
+ v->output_fds = mfree(v->output_fds);
+ v->n_output_fds = 0;
+
+ close_many(v->pushed_fds, v->n_pushed_fds);
+ v->pushed_fds = mfree(v->pushed_fds);
+ v->n_pushed_fds = 0;
+
+ while (v->output_queue) {
+ VarlinkJsonQueueItem *q = v->output_queue;
+
+ LIST_REMOVE(queue, v->output_queue, q);
+ varlink_json_queue_item_free(q);
+ }
+ v->output_queue_tail = NULL;
v->event = sd_event_unref(v->event);
}
static int varlink_write(Varlink *v) {
ssize_t n;
+ int r;
assert(v);
if (v->connecting) /* Writing while we are still wait for a non-blocking connect() to complete will
* result in ENOTCONN, hence exit early here */
return 0;
- if (v->output_buffer_size == 0)
- return 0;
if (v->write_disconnected)
return 0;
+ /* If needed let's convert some output queue json variants into text form */
+ r = varlink_format_queue(v);
+ if (r < 0)
+ return r;
+
+ if (v->output_buffer_size == 0)
+ return 0;
+
assert(v->fd >= 0);
- /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible
- * with non-socket IO, hence fall back automatically.
- *
- * Use a local variable to help gcc figure out that we set 'n' in all cases. */
- bool prefer_write = v->prefer_read_write;
- if (!prefer_write) {
- n = send(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL);
- if (n < 0 && errno == ENOTSOCK)
- prefer_write = v->prefer_read_write = true;
+ if (v->n_output_fds > 0) { /* If we shall send fds along, we must use sendmsg() */
+ struct iovec iov = {
+ .iov_base = v->output_buffer + v->output_buffer_index,
+ .iov_len = v->output_buffer_size,
+ };
+ struct msghdr mh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_controllen = CMSG_SPACE(sizeof(int) * v->n_output_fds),
+ };
+
+ mh.msg_control = alloca0(mh.msg_controllen);
+
+ struct cmsghdr *control = CMSG_FIRSTHDR(&mh);
+ control->cmsg_len = CMSG_LEN(sizeof(int) * v->n_output_fds);
+ control->cmsg_level = SOL_SOCKET;
+ control->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(control), v->output_fds, sizeof(int) * v->n_output_fds);
+
+ n = sendmsg(v->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+ } else {
+ /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible
+ * with non-socket IO, hence fall back automatically.
+ *
+ * Use a local variable to help gcc figure out that we set 'n' in all cases. */
+ bool prefer_write = v->prefer_read_write;
+ if (!prefer_write) {
+ n = send(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL);
+ if (n < 0 && errno == ENOTSOCK)
+ prefer_write = v->prefer_read_write = true;
+ }
+ if (prefer_write)
+ n = write(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size);
}
- if (prefer_write)
- n = write(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size);
if (n < 0) {
if (errno == EAGAIN)
return 0;
return -errno;
}
+ if (v->output_buffer_sensitive)
+ explicit_bzero_safe(v->output_buffer + v->output_buffer_index, n);
+
v->output_buffer_size -= n;
- if (v->output_buffer_size == 0)
+ if (v->output_buffer_size == 0) {
v->output_buffer_index = 0;
- else
+ v->output_buffer_sensitive = false; /* We can reset the sensitive flag once the buffer is empty */
+ } else
v->output_buffer_index += n;
+ close_many(v->output_fds, v->n_output_fds);
+ v->n_output_fds = 0;
+
v->timestamp = now(CLOCK_MONOTONIC);
return 1;
}
+#define VARLINK_FDS_MAX (16U*1024U)
+
static int varlink_read(Varlink *v) {
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * VARLINK_FDS_MAX)) control;
+ struct iovec iov;
+ struct msghdr mh;
size_t rs;
ssize_t n;
+ void *p;
assert(v);
}
}
+ p = v->input_buffer + v->input_buffer_index + v->input_buffer_size;
rs = MALLOC_SIZEOF_SAFE(v->input_buffer) - (v->input_buffer_index + v->input_buffer_size);
- bool prefer_read = v->prefer_read_write;
- if (!prefer_read) {
- n = recv(v->fd, v->input_buffer + v->input_buffer_index + v->input_buffer_size, rs, MSG_DONTWAIT);
- if (n < 0 && errno == ENOTSOCK)
- prefer_read = v->prefer_read_write = true;
+ if (v->allow_fd_passing_input) {
+ iov = (struct iovec) {
+ .iov_base = p,
+ .iov_len = rs,
+ };
+ mh = (struct msghdr) {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ n = recvmsg_safe(v->fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ } else {
+ bool prefer_read = v->prefer_read_write;
+ if (!prefer_read) {
+ n = recv(v->fd, p, rs, MSG_DONTWAIT);
+ if (n < 0 && errno == ENOTSOCK)
+ prefer_read = v->prefer_read_write = true;
+ }
+ if (prefer_read)
+ n = read(v->fd, p, rs);
}
- if (prefer_read)
- n = read(v->fd, v->input_buffer + v->input_buffer_index + v->input_buffer_size, rs);
if (n < 0) {
if (errno == EAGAIN)
return 0;
return -errno;
}
if (n == 0) { /* EOF */
+
+ if (v->allow_fd_passing_input)
+ cmsg_close_all(&mh);
+
v->read_disconnected = true;
return 1;
}
+ if (v->allow_fd_passing_input) {
+ struct cmsghdr* cmsg;
+
+ cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1);
+ if (cmsg) {
+ size_t add;
+
+ /* We only allow file descriptors to be passed along with the first byte of a
+ * message. If they are passed with any other byte this is a protocol violation. */
+ if (v->input_buffer_size != 0) {
+ cmsg_close_all(&mh);
+ return -EPROTO;
+ }
+
+ add = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ if (add > INT_MAX - v->n_input_fds) {
+ cmsg_close_all(&mh);
+ return -EBADF;
+ }
+
+ if (!GREEDY_REALLOC(v->input_fds, v->n_input_fds + add)) {
+ cmsg_close_all(&mh);
+ return -ENOMEM;
+ }
+
+ memcpy_safe(v->input_fds + v->n_input_fds, CMSG_TYPED_DATA(cmsg, int), add * sizeof(int));
+ v->n_input_fds += add;
+ }
+ }
+
v->input_buffer_size += n;
v->input_buffer_unscanned += n;
log_debug_errno(r, "Reply callback returned error, ignoring: %m");
}
- v->current = json_variant_unref(v->current);
+ varlink_clear_current(v);
if (v->state == VARLINK_PROCESSING_REPLY) {
case VARLINK_PROCESSED_METHOD: /* Method call is fully processed */
case VARLINK_PROCESSING_METHOD_ONEWAY: /* ditto */
- v->current = json_variant_unref(v->current);
+ varlink_clear_current(v);
varlink_set_state(v, VARLINK_IDLE_SERVER);
break;
return varlink_close_unref(v);
}
-static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
- _cleanup_free_ char *text = NULL;
+static int varlink_format_json(Varlink *v, JsonVariant *m) {
+ _cleanup_(erase_and_freep) char *text = NULL;
int r;
assert(v);
memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
v->output_buffer_size += r + 1;
-
} else {
char *n;
const size_t new_size = v->output_buffer_size + r + 1;
v->output_buffer_index = 0;
}
+ if (json_variant_is_sensitive(m))
+ v->output_buffer_sensitive = true; /* Propagate sensitive flag */
+ else
+ text = mfree(text); /* No point in the erase_and_free() destructor declared above */
+
+ return 0;
+}
+
+static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
+ VarlinkJsonQueueItem *q;
+
+ assert(v);
+ assert(m);
+
+ /* If there are no file descriptors to be queued and no queue entries yet we can shortcut things and
+ * append this entry directly to the output buffer */
+ if (v->n_pushed_fds == 0 && !v->output_queue)
+ return varlink_format_json(v, m);
+
+ /* Otherwise add a queue entry for this */
+ q = varlink_json_queue_item_new(m, v->pushed_fds, v->n_pushed_fds);
+ if (!q)
+ return -ENOMEM;
+
+ v->n_pushed_fds = 0; /* fds now belong to the queue entry */
+
+ LIST_INSERT_AFTER(queue, v->output_queue, v->output_queue_tail, q);
+ v->output_queue_tail = q;
+ return 0;
+}
+
+static int varlink_format_queue(Varlink *v) {
+ int r;
+
+ assert(v);
+
+ /* Takes entries out of the output queue and formats them into the output buffer. But only if this
+ * would not corrupt our fd message boundaries */
+
+ while (v->output_queue) {
+ _cleanup_free_ int *array = NULL;
+ VarlinkJsonQueueItem *q = v->output_queue;
+
+ if (v->n_output_fds > 0) /* unwritten fds? if we'd add more we'd corrupt the fd message boundaries, hence wait */
+ return 0;
+
+ if (q->n_fds > 0) {
+ array = newdup(int, q->fds, q->n_fds);
+ if (!array)
+ return -ENOMEM;
+ }
+
+ r = varlink_format_json(v, q->data);
+ if (r < 0)
+ return r;
+
+ /* Take possession of the queue element's fds */
+ free(v->output_fds);
+ v->output_fds = TAKE_PTR(array);
+ v->n_output_fds = q->n_fds;
+ q->n_fds = 0;
+
+ LIST_REMOVE(queue, v->output_queue, q);
+ if (!v->output_queue)
+ v->output_queue_tail = NULL;
+
+ varlink_json_queue_item_free(q);
+ }
+
return 0;
}
assert(v->n_pending == 0); /* n_pending can't be > 0 if we are in VARLINK_IDLE_CLIENT state */
+ /* If there was still a reply pinned from a previous call, now it's the time to get rid of it, so
+ * that we can assign a new reply shortly. */
+ varlink_clear_current(v);
+
r = varlink_sanitize_parameters(¶meters);
if (r < 0)
return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
case VARLINK_CALLED:
assert(v->current);
- json_variant_unref(v->reply);
- v->reply = TAKE_PTR(v->current);
-
varlink_set_state(v, VARLINK_IDLE_CLIENT);
assert(v->n_pending == 1);
v->n_pending--;
if (ret_parameters)
- *ret_parameters = json_variant_by_key(v->reply, "parameters");
+ *ret_parameters = json_variant_by_key(v->current, "parameters");
if (ret_error_id)
- *ret_error_id = json_variant_string(json_variant_by_key(v->reply, "error"));
+ *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error"));
if (ret_flags)
*ret_flags = 0;
/* We just replied to a method call that was let hanging for a while (i.e. we were outside of
* the varlink_dispatch_method() stack frame), which means with this reply we are ready to
* process further messages. */
- v->current = json_variant_unref(v->current);
+ varlink_clear_current(v);
varlink_set_state(v, VARLINK_IDLE_SERVER);
} else
/* We replied to a method call from within the varlink_dispatch_method() stack frame), which
VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE))
return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy.");
+ /* Reset the list of pushed file descriptors before sending an error reply. We do this here to
+ * simplify code that puts together a complex reply message with fds, and half-way something
+ * fails. In that case the pushed fds need to be flushed out again. Under the assumption that it
+ * never makes sense to send fds along with errors we simply flush them out here beforehand, so that
+ * the callers don't need to do this explicitly. */
+ varlink_reset_fds(v);
+
r = varlink_sanitize_parameters(¶meters);
if (r < 0)
return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) {
- v->current = json_variant_unref(v->current);
+ varlink_clear_current(v);
varlink_set_state(v, VARLINK_IDLE_SERVER);
} else
varlink_set_state(v, VARLINK_PROCESSED_METHOD);
return v->event;
}
+int varlink_push_fd(Varlink *v, int fd) {
+ int i;
+
+ assert_return(v, -EINVAL);
+ assert_return(fd >= 0, -EBADF);
+
+ /* Takes an fd to send along with the *next* varlink message sent via this varlink connection. This
+ * takes ownership of the specified fd. Use varlink_dup_fd() below to duplicate the fd first. */
+
+ if (!v->allow_fd_passing_output)
+ return -EPERM;
+
+ if (v->n_pushed_fds >= INT_MAX)
+ return -ENOMEM;
+
+ if (!GREEDY_REALLOC(v->pushed_fds, v->n_pushed_fds + 1))
+ return -ENOMEM;
+
+ i = (int) v->n_pushed_fds;
+ v->pushed_fds[v->n_pushed_fds++] = fd;
+ return i;
+}
+
+int varlink_dup_fd(Varlink *v, int fd) {
+ _cleanup_close_ int dp = -1;
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(fd >= 0, -EBADF);
+
+ /* Like varlink_push_fd() but duplicates the specified fd instead of taking possession of it */
+
+ dp = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (dp < 0)
+ return -errno;
+
+ r = varlink_push_fd(v, dp);
+ if (r < 0)
+ return r;
+
+ TAKE_FD(dp);
+ return r;
+}
+
+int varlink_reset_fds(Varlink *v) {
+ assert_return(v, -EINVAL);
+
+ /* Closes all currently pending fds to send. This may be used whenever the caller is in the process
+ * of putting together a message with fds, and then eventually something fails and they need to
+ * rollback the fds. Note that this is implicitly called whenever an error reply is sent, see above. */
+
+ close_many(v->output_fds, v->n_output_fds);
+ v->n_output_fds = 0;
+ return 0;
+}
+
+int varlink_peek_fd(Varlink *v, size_t i) {
+ assert_return(v, -EINVAL);
+
+ /* Returns one of the file descriptors that were received along with the current message. This does
+ * not duplicate the fd nor invalidate it, it hence remains in our possession. */
+
+ if (!v->allow_fd_passing_input)
+ return -EPERM;
+
+ if (i >= v->n_input_fds)
+ return -ENXIO;
+
+ return v->input_fds[i];
+}
+
+int varlink_take_fd(Varlink *v, size_t i) {
+ assert_return(v, -EINVAL);
+
+ /* Similar to varlink_peek_fd() but the file descriptor's ownership is passed to the caller, and
+ * we'll invalidate the reference to it under our possession. If called twice in a row will return
+ * -EBADF */
+
+ if (!v->allow_fd_passing_input)
+ return -EPERM;
+
+ if (i >= v->n_input_fds)
+ return -ENXIO;
+
+ return TAKE_FD(v->input_fds[i]);
+}
+
+static int verify_unix_socket(Varlink *v) {
+ assert(v);
+
+ if (v->af < 0) {
+ struct stat st;
+
+ if (fstat(v->fd, &st) < 0)
+ return -errno;
+ if (!S_ISSOCK(st.st_mode)) {
+ v->af = AF_UNSPEC;
+ return -ENOTSOCK;
+ }
+
+ v->af = socket_get_family(v->fd);
+ if (v->af < 0)
+ return v->af;
+ }
+
+ return v->af == AF_UNIX ? 0 : -ENOMEDIUM;
+}
+
+int varlink_set_allow_fd_passing_input(Varlink *v, bool b) {
+ int r;
+
+ assert_return(v, -EINVAL);
+
+ if (v->allow_fd_passing_input == b)
+ return 0;
+
+ if (!b) {
+ v->allow_fd_passing_input = false;
+ return 1;
+ }
+
+ r = verify_unix_socket(v);
+ if (r < 0)
+ return r;
+
+ v->allow_fd_passing_input = true;
+ return 0;
+}
+
+int varlink_set_allow_fd_passing_output(Varlink *v, bool b) {
+ int r;
+
+ assert_return(v, -EINVAL);
+
+ if (v->allow_fd_passing_output == b)
+ return 0;
+
+ if (!b) {
+ v->allow_fd_passing_output = false;
+ return 1;
+ }
+
+ r = verify_unix_socket(v);
+ if (r < 0)
+ return r;
+
+ v->allow_fd_passing_output = true;
+ return 0;
+}
+
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
VarlinkServer *s;
int varlink_notify(Varlink *v, JsonVariant *parameters);
int varlink_notifyb(Varlink *v, ...);
+/* Write outgoing fds into the socket (to be associated with the next enqueued message) */
+int varlink_push_fd(Varlink *v, int fd);
+int varlink_dup_fd(Varlink *v, int fd);
+int varlink_reset_fds(Varlink *v);
+
+/* Read incoming fds from the socket (associated with the currently handled message) */
+int varlink_peek_fd(Varlink *v, size_t i);
+int varlink_take_fd(Varlink *v, size_t i);
+
+int varlink_set_allow_fd_passing_input(Varlink *v, bool b);
+int varlink_set_allow_fd_passing_output(Varlink *v, bool b);
+
/* Bind a disconnect, reply or timeout callback */
int varlink_bind_reply(Varlink *v, VarlinkReply reply);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
+/* These are local errors that never cross the wire, and are our own invention */
#define VARLINK_ERROR_DISCONNECTED "io.systemd.Disconnected"
#define VARLINK_ERROR_TIMEOUT "io.systemd.TimedOut"
#define VARLINK_ERROR_PROTOCOL "io.systemd.Protocol"
#define VARLINK_ERROR_SYSTEM "io.systemd.System"
+/* These are errors defined in the Varlink spec */
#define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
#define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"
#define VARLINK_ERROR_METHOD_NOT_IMPLEMENTED "org.varlink.service.MethodNotImplemented"
#define VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter"
+
+/* These are errors we came up with and squatted the namespace with */
#define VARLINK_ERROR_SUBSCRIPTION_TAKEN "org.varlink.service.SubscriptionTaken"
#define VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied"
if (startswith(b, "!--")) {
/* A comment */
- e = strstr(b + 3, "-->");
+ e = strstrafter(b + 3, "-->");
if (!e)
return -EINVAL;
- inc_lines(line, b, e + 3 - b);
+ inc_lines(line, b, e - b);
- c = e + 3;
+ c = e;
continue;
}
if (*b == '?') {
/* Processing instruction */
- e = strstr(b + 1, "?>");
+ e = strstrafter(b + 1, "?>");
if (!e)
return -EINVAL;
- inc_lines(line, b, e + 2 - b);
+ inc_lines(line, b, e - b);
- c = e + 2;
+ c = e;
continue;
}
#include <sys/stat.h>
#include <unistd.h>
+#include "sd-daemon.h"
+
#include "alloc-util.h"
#include "async.h"
#include "binfmt-util.h"
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "getopt-defs.h"
#include "initrd-util.h"
#include "killall.h"
#include "log.h"
static int parse_argv(int argc, char *argv[]) {
enum {
- ARG_LOG_LEVEL = 0x100,
- ARG_LOG_TARGET,
- ARG_LOG_COLOR,
- ARG_LOG_LOCATION,
- ARG_LOG_TIME,
- ARG_EXIT_CODE,
- ARG_TIMEOUT,
+ COMMON_GETOPT_ARGS,
+ SHUTDOWN_GETOPT_ARGS,
};
static const struct option options[] = {
- { "log-level", required_argument, NULL, ARG_LOG_LEVEL },
- { "log-target", required_argument, NULL, ARG_LOG_TARGET },
- { "log-color", optional_argument, NULL, ARG_LOG_COLOR },
- { "log-location", optional_argument, NULL, ARG_LOG_LOCATION },
- { "log-time", optional_argument, NULL, ARG_LOG_TIME },
- { "exit-code", required_argument, NULL, ARG_EXIT_CODE },
- { "timeout", required_argument, NULL, ARG_TIMEOUT },
+ COMMON_GETOPT_OPTIONS,
+ SHUTDOWN_GETOPT_OPTIONS,
{}
};
assert(argc >= 1);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+
/* "-" prevents getopt from permuting argv[] and moving the verb away
* from argv[1]. Our interface to initrd promises it'll be there. */
while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0)
if (!in_container)
sync_with_progress();
+ /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with
+ * a mechanism to pick up systemd's exit status in the VM. */
+ (void) sd_notifyf(0, "EXIT_STATUS=%i", arg_exit_code);
+
if (streq(arg_verb, "exit")) {
if (in_container) {
log_info("Exiting container.");
#include "alloc-util.h"
#include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "constants.h"
#include "device-util.h"
+#include "devnum-util.h"
#include "dirent-util.h"
#include "escape.h"
#include "fd-util.h"
* /run/shutdown/mounts from there.
*/
if (!resolved_mounts_path)
- (void) chase_symlinks("/run/shutdown/mounts", NULL, 0, &resolved_mounts_path, NULL);
+ (void) chase("/run/shutdown/mounts", NULL, 0, &resolved_mounts_path, NULL);
if (!path_equal(dirname, resolved_mounts_path)) {
char newpath[STRLEN("/run/shutdown/mounts/") + 16 + 1];
continue;
}
- log_info("Detaching DM %s (%u:%u).", m->path, major(m->devnum), minor(m->devnum));
+ log_info("Detaching DM %s (" DEVNUM_FORMAT_STR ").", m->path, DEVNUM_FORMAT_VAL(m->devnum));
r = delete_dm(m);
if (r < 0) {
log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not detach DM %s: %m", m->path);
continue;
}
- log_info("Stopping MD %s (%u:%u).", m->path, major(m->devnum), minor(m->devnum));
+ log_info("Stopping MD %s (" DEVNUM_FORMAT_STR ").", m->path, DEVNUM_FORMAT_VAL(m->devnum));
r = delete_md(m);
if (r < 0) {
log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not stop MD %s: %m", m->path);
#include "bus-locator.h"
#include "bus-util.h"
#include "constants.h"
+#include "devnum-util.h"
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
char offset_str[DECIMAL_STR_MAX(uint64_t)];
- char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")];
+ const char *resume_str;
int r;
assert(hibernate_location);
assert(hibernate_location->swap);
- xsprintf(resume_str, "%u:%u", major(hibernate_location->devno), minor(hibernate_location->devno));
+ resume_str = FORMAT_DEVNUM(hibernate_location->devno);
+
r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m",
#include "bus-error.h"
#include "constants.h"
#include "env-util.h"
+#include "initrd-util.h"
#include "log.h"
#include "main-func.h"
#include "process-util.h"
return 0;
}
-static int default_target_is_inactive(sd_bus *bus) {
+static int target_is_inactive(sd_bus *bus, const char *target) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *path = NULL, *state = NULL;
int r;
- path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
+ path = unit_dbus_path_from_name(target);
if (!path)
return log_oom();
return streq_ptr(state, "inactive");
}
-static int start_default_target(sd_bus *bus) {
+static int start_target(sd_bus *bus, const char *target) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
- log_info("Starting "SPECIAL_DEFAULT_TARGET);
+ log_info("Starting %s", target);
/* Start this unit only if we can replace basic.target with it */
r = bus_call_method(
"StartUnit",
&error,
NULL,
- "ss", SPECIAL_DEFAULT_TARGET, "isolate");
+ "ss", target, "isolate");
if (r < 0)
- return log_error_errno(r, "Failed to start "SPECIAL_DEFAULT_TARGET": %s", bus_error_message(&error, r));
+ return log_error_errno(r, "Failed to start %s: %s", target, bus_error_message(&error, r));
return 0;
}
static void print_mode(const char* mode) {
printf("You are in %s mode. After logging in, type \"journalctl -xb\" to view\n"
- "system logs, \"systemctl reboot\" to reboot, \"systemctl default\" or \"exit\"\n"
- "to boot into default mode.\n", mode);
+ "system logs, \"systemctl reboot\" to reboot, or \"exit\"\n" "to continue bootup.\n", mode);
fflush(stdout);
}
if (reload_manager(bus) < 0)
goto fallback;
- r = default_target_is_inactive(bus);
+ const char *target = in_initrd() ? SPECIAL_INITRD_TARGET : SPECIAL_DEFAULT_TARGET;
+
+ r = target_is_inactive(bus, target);
if (r < 0)
goto fallback;
if (!r) {
- log_warning(SPECIAL_DEFAULT_TARGET" is not inactive. Please review the "SPECIAL_DEFAULT_TARGET" setting.\n");
+ log_warning("%s is not inactive. Please review the %s setting.\n", target, target);
goto fallback;
}
- if (start_default_target(bus) >= 0)
+ if (start_target(bus, target) >= 0)
break;
fallback:
# 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 "build.h"
#include "capability-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "devnum-util.h"
#include "discover-image.h"
#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;
+static int arg_noexec = -1;
+static ImagePolicy *arg_image_policy = NULL;
+
+/* 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);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_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;
+ const ImagePolicy *default_image_policy;
+ unsigned long default_mount_flags;
+} 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",
+ .default_image_policy = &image_policy_sysext,
+ .default_mount_flags = MS_RDONLY|MS_NODEV,
+ },
+ [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",
+ .default_image_policy = &image_policy_confext,
+ .default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
+ }
+};
static int is_our_mount_point(const char *p) {
_cleanup_free_ char *buf = NULL, *f = NULL;
/* 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);
STRV_FOREACH(p, arg_hierarchies) {
_cleanup_free_ char *resolved = NULL;
- r = chase_symlinks(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
+ r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r == -ENOENT) {
log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
continue;
_cleanup_strv_free_ char **l = NULL;
struct stat st;
- r = chase_symlinks(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
+ r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r == -ENOENT) {
log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
continue;
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();
_cleanup_free_ char *options = NULL;
bool separator = false;
+ unsigned long flags;
int r;
assert(where);
separator = true;
}
+ flags = image_class_info[arg_image_class].default_mount_flags;
+ if (arg_noexec >= 0)
+ SET_FLAG(flags, MS_NOEXEC, arg_noexec);
+
/* 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", flags, options);
if (r < 0)
return r;
/* Resolve the path of the host's version of the hierarchy, i.e. what we want to use as lowest layer
* in the overlayfs stack. */
- r = chase_symlinks(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
+ r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
if (r == -ENOENT)
log_debug_errno(r, "Hierarchy '%s' on host doesn't exist, not merging.", hierarchy);
else if (r < 0)
/* 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();
STRV_FOREACH(p, paths) {
_cleanup_free_ char *resolved = NULL;
- r = chase_symlinks(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
+ r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r == -ENOENT) {
log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p);
continue;
/* 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();
- r = write_string_filef(f, WRITE_STRING_FILE_CREATE, "%u:%u", major(st.st_dev), minor(st.st_dev));
+ r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE);
if (r < 0)
return log_error_errno(r, "Failed to write '%s': %m", f);
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);
+static const ImagePolicy *pick_image_policy(const Image *img) {
assert(img);
+ assert(img->path);
- if (arg_force) {
- log_debug("Force mode enabled, skipping version validation.");
- return 1;
- }
+ /* Explicitly specified policy always wins */
+ if (arg_image_policy)
+ return arg_image_policy;
- /* 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_symlinks("/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");
+ /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
+ * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
+ * other directories we assume the appropriate level of trust was already established already. */
+
+ if (in_initrd() && path_startswith(img->path, "/.extra/sysext/"))
+ return &image_policy_sysext_strict;
- return r;
+ return image_class_info[img->class].default_image_policy;
}
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,
r = dissect_loop_device_and_warn(
d,
&verity_settings,
- NULL,
+ /* mount_options= */ NULL,
+ pick_image_policy(img),
flags,
&m);
if (r < 0)
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();
STRV_FOREACH(h, arg_hierarchies) {
_cleanup_free_ char *resolved = NULL;
- r = chase_symlinks(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+ r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
continue;
}
- r = chase_symlinks(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+ r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
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);
+ r = image_read_metadata(img, &image_policy_sysext);
if (r < 0)
return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
}
STRV_FOREACH(p, arg_hierarchies) {
_cleanup_free_ char *resolved = NULL;
- r = chase_symlinks(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
+ r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r == -ENOENT) {
log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
continue;
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"
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --force Ignore version incompatibilities\n"
+ " --image-policy=POLICY\n"
+ " Specify disk image dissection policy\n"
+ " --noexec=BOOL Whether to mount extension overlay with noexec\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ARG_ROOT,
ARG_JSON,
ARG_FORCE,
+ ARG_IMAGE_POLICY,
+ ARG_NOEXEC,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
- { "root", required_argument, NULL, ARG_ROOT },
- { "json", required_argument, NULL, ARG_JSON },
- { "force", no_argument, NULL, ARG_FORCE },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "force", no_argument, NULL, ARG_FORCE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "noexec", required_argument, NULL, ARG_NOEXEC },
{}
};
arg_force = true;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_NOEXEC:
+ r = parse_boolean_argument("--noexec", optarg, NULL);
+ if (r < 0)
+ return r;
+
+ arg_noexec = r;
+ break;
+
case '?':
return -EINVAL;
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);
}
#include "fs-util.h"
#include "generator.h"
#include "log.h"
+#include "path-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "string-util.h"
static const char *arg_dest = NULL;
static int generate_symlink(void) {
- const char *p = NULL;
+ _cleanup_free_ char *j = NULL;
- if (laccess("/system-update", F_OK) < 0) {
- if (errno == ENOENT)
- return 0;
+ FOREACH_STRING(p, "/system-update", "/etc/system-update") {
+ if (laccess(p, F_OK) >= 0)
+ goto link_found;
- log_error_errno(errno, "Failed to check for system update: %m");
- return -EINVAL;
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to check if %s symlink exists, ignoring: %m", p);
}
- p = strjoina(arg_dest, "/" SPECIAL_DEFAULT_TARGET);
- if (symlink(SYSTEM_DATA_UNIT_DIR "/system-update.target", p) < 0)
- return log_error_errno(errno, "Failed to create symlink %s: %m", p);
+ return 0;
+
+link_found:
+ j = path_join(arg_dest, SPECIAL_DEFAULT_TARGET);
+ if (!j)
+ return log_oom();
+
+ if (symlink(SYSTEM_DATA_UNIT_DIR "/system-update.target", j) < 0)
+ return log_error_errno(errno, "Failed to create symlink %s: %m", j);
return 1;
}
_cleanup_strv_free_ char **names = NULL;
_cleanup_free_ char *target = NULL;
const char *verb = argv[0];
- InstallChange *changes = NULL;
- size_t n_changes = 0;
UnitDependency dep;
int r;
assert_not_reached();
if (install_client_side()) {
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = unit_file_add_dependency(arg_runtime_scope, unit_file_flags_from_args(), arg_root, names, target, dep, &changes, &n_changes);
install_changes_dump(r, "add dependency on", changes, n_changes, arg_quiet);
-
- if (r > 0)
- r = 0;
+ if (r < 0)
+ return r;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (r < 0)
return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r));
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
- if (arg_no_reload) {
- r = 0;
- goto finish;
+ if (!arg_no_reload) {
+ r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
+ if (r < 0)
+ return r;
}
-
- r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
- if (r > 0)
- r = 0;
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r;
+ return 0;
}
polkit_agent_open_maybe();
if (!arg_clean_what) {
- arg_clean_what = strv_new("cache", "runtime");
+ arg_clean_what = strv_new("cache", "runtime", "fdstore");
if (!arg_clean_what)
return log_oom();
}
#include "systemctl.h"
#include "terminal-util.h"
-#define EDIT_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file"
-#define EDIT_MARKER_END "### Edits below this comment will be discarded"
-
int verb_cat(int argc, char *argv[], void *userdata) {
_cleanup_(hashmap_freep) Hashmap *cached_name_map = NULL, *cached_id_map = NULL;
_cleanup_(lookup_paths_free) LookupPaths lp = {};
int verb_edit(int argc, char *argv[], void *userdata) {
_cleanup_(edit_file_context_done) EditFileContext context = {
- .marker_start = EDIT_MARKER_START,
- .marker_end = EDIT_MARKER_END,
+ .marker_start = DROPIN_MARKER_START,
+ .marker_end = DROPIN_MARKER_END,
.remove_parent = !arg_full,
+ .overwrite_with_origin = true,
};
_cleanup_(lookup_paths_free) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL;
int verb_enable(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
const char *verb = argv[0];
- InstallChange *changes = NULL;
- size_t n_changes = 0;
int carries_install_info = -1;
bool ignore_carries_install_info = arg_quiet || arg_no_warn;
int r;
if (install_client_side()) {
UnitFileFlags flags;
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
flags = unit_file_flags_from_args();
if (streq(verb, "enable")) {
install_changes_dump(r, verb, changes, n_changes, arg_quiet);
if (r < 0)
- goto finish;
- r = 0;
+ return r;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
return bus_log_parse_error(r);
}
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
/* Try to reload if enabled */
if (!arg_no_reload) {
r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
- if (r > 0)
- r = 0;
- } else
- r = 0;
+ if (r < 0)
+ return r;
+ }
}
if (carries_install_info == 0 && !ignore_carries_install_info)
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
- goto finish;
+ return r;
len = strv_length(names);
{
}
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r;
+ return 0;
}
char **p;
int r;
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
p = STRV_MAKE(name);
flags = UNIT_FILE_DRY_RUN |
(arg_runtime ? UNIT_FILE_RUNTIME : 0);
return true;
}
+static const char* preset_action_to_color(PresetAction action, bool underline) {
+ assert(action >= 0);
+
+ switch (action) {
+ case PRESET_ENABLE:
+ return underline ? ansi_highlight_green_underline() : ansi_highlight_green();
+ case PRESET_DISABLE:
+ return underline ? ansi_highlight_red_underline() : ansi_highlight_red();
+ case PRESET_IGNORE:
+ return underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
+ default:
+ return NULL;
+ }
+}
+
static int output_unit_file_list(const UnitFileList *units, unsigned c) {
_cleanup_(table_unrefp) Table *table = NULL;
- _cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
+ _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
int r;
table = table_new("unit file", "state", "preset");
return table_log_add_error(r);
if (show_preset_for_state(u->state)) {
- const char *unit_preset_str, *on_preset_color;
+ const char *on_preset_color = underline ? on_underline : ansi_normal();
r = unit_file_query_preset(arg_runtime_scope, arg_root, id, &presets);
- if (r < 0) {
- unit_preset_str = "n/a";
- on_preset_color = underline ? on_underline : ansi_normal();
- } else if (r == 0) {
- unit_preset_str = "disabled";
- on_preset_color = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
- } else {
- unit_preset_str = "enabled";
- on_preset_color = underline ? ansi_highlight_green_underline() : ansi_highlight_green();
- }
+ if (r >= 0)
+ on_preset_color = preset_action_to_color(r, underline);
r = table_add_many(table,
- TABLE_STRING, unit_preset_str,
+ TABLE_STRING, strna(preset_action_past_tense_to_string(r)),
TABLE_SET_BOTH_COLORS, strempty(on_preset_color));
} else
r = table_add_many(table,
int verb_list_unit_files(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ UnitFileList *units = NULL;
+ _cleanup_(hashmap_freep) Hashmap *h = NULL;
unsigned c = 0;
const char *state;
char *path;
bool fallback = false;
if (install_client_side()) {
- Hashmap *h;
UnitFileList *u;
unsigned n_units;
- h = hashmap_new(&string_hash_ops);
+ h = hashmap_new(&unit_file_list_hash_ops_free);
if (!h)
return log_oom();
r = unit_file_get_list(arg_runtime_scope, arg_root, h, arg_states, strv_skip(argv, 1));
- if (r < 0) {
- unit_file_list_free(h);
+ if (r < 0)
return log_error_errno(r, "Failed to get unit file list: %m");
- }
n_units = hashmap_size(h);
- units = new(UnitFileList, n_units ?: 1); /* avoid malloc(0) */
- if (!units) {
- unit_file_list_free(h);
+ units = new(UnitFileList, n_units);
+ if (!units)
return log_oom();
- }
HASHMAP_FOREACH(u, h) {
if (!output_show_unit_file(u, NULL, NULL))
continue;
units[c++] = *u;
- free(u);
}
assert(c <= n_units);
- hashmap_free(h);
-
- r = 0;
} else {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (r < 0)
return r;
- if (install_client_side())
- for (UnitFileList *unit = units; unit < units + c; unit++)
- free(unit->path);
-
if (c == 0)
return -ENOENT;
#include "systemctl.h"
int verb_preset_all(int argc, char *argv[], void *userdata) {
- InstallChange *changes = NULL;
- size_t n_changes = 0;
int r;
if (install_client_side()) {
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = unit_file_preset_all(arg_runtime_scope, unit_file_flags_from_args(), arg_root, arg_preset_mode, &changes, &n_changes);
install_changes_dump(r, "preset", changes, n_changes, arg_quiet);
if (r < 0)
return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r));
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
- if (arg_no_reload) {
- r = 0;
- goto finish;
+ if (!arg_no_reload) {
+ r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
+ if (r < 0)
+ return r;
}
-
- r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
- if (r > 0)
- r = 0;
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r;
+ return 0;
}
int verb_set_default(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *unit = NULL;
- InstallChange *changes = NULL;
- size_t n_changes = 0;
int r;
assert(argc >= 2);
return log_error_errno(r, "Failed to mangle unit name: %m");
if (install_client_side()) {
+ InstallChange *changes = NULL;
+ size_t n_changes = 0;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
r = unit_file_set_default(arg_runtime_scope, UNIT_FILE_FORCE, arg_root, unit, &changes, &n_changes);
install_changes_dump(r, "set default", changes, n_changes, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
} else {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
if (r < 0)
return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r));
- r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+ r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
if (r < 0)
- goto finish;
+ return r;
/* Try to reload if enabled */
if (!arg_no_reload) {
r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
if (r < 0)
- goto finish;
+ return r;
}
}
r = determine_default(&final);
if (r < 0)
- goto finish;
+ return r;
if (!streq(final, unit))
log_notice("Note: \"%s\" is the default unit (possibly a runtime override).", final);
}
-finish:
- install_changes_free(changes, n_changes);
-
- return r < 0 ? r : 0;
+ return 0;
}
bool running:1;
int status_errno;
+ uint32_t fd_store_max;
+ uint32_t n_fd_store;
+
usec_t start_timestamp;
usec_t exit_timestamp;
if (!i->condition_result && i->condition_timestamp > 0) {
int n = 0;
- printf(" Condition: start %scondition failed%s at %s; %s\n",
+ printf(" Condition: start %scondition unmet%s at %s; %s\n",
ansi_highlight_yellow(), ansi_normal(),
FORMAT_TIMESTAMP_STYLE(i->condition_timestamp, arg_timestamp_style),
FORMAT_TIMESTAMP_RELATIVE(i->condition_timestamp));
}
if (i->status_text)
- printf(" Status: \"%s\"\n", i->status_text);
+ printf(" Status: \"%s%s%s\"\n", ansi_highlight_cyan(), i->status_text, ansi_normal());
if (i->status_errno > 0) {
errno = i->status_errno;
printf(" Error: %i (%m)\n", i->status_errno);
printf("\n");
}
+ if (i->n_fd_store > 0 || i->fd_store_max > 0)
+ printf(" FD Store: %u%s (limit: %u)%s\n", i->n_fd_store, ansi_grey(), i->fd_store_max, ansi_normal());
+
if (i->memory_current != UINT64_MAX) {
printf(" Memory: %s", FORMAT_BYTES(i->memory_current));
{ "StatusText", "s", NULL, offsetof(UnitStatusInfo, status_text) },
{ "PIDFile", "s", NULL, offsetof(UnitStatusInfo, pid_file) },
{ "StatusErrno", "i", NULL, offsetof(UnitStatusInfo, status_errno) },
+ { "FileDescriptorStoreMax", "u", NULL, offsetof(UnitStatusInfo, fd_store_max) },
+ { "NFileDescriptorStore", "u", NULL, offsetof(UnitStatusInfo, n_fd_store) },
{ "ExecMainStartTimestamp", "t", NULL, offsetof(UnitStatusInfo, start_timestamp) },
{ "ExecMainExitTimestamp", "t", NULL, offsetof(UnitStatusInfo, exit_timestamp) },
{ "ExecMainCode", "i", NULL, offsetof(UnitStatusInfo, exit_code) },
.runtime_max_sec = USEC_INFINITY,
.memory_current = UINT64_MAX,
.memory_high = CGROUP_LIMIT_MAX,
+ .startup_memory_high = CGROUP_LIMIT_MAX,
.memory_max = CGROUP_LIMIT_MAX,
+ .startup_memory_max = CGROUP_LIMIT_MAX,
.memory_swap_max = CGROUP_LIMIT_MAX,
+ .startup_memory_swap_max = CGROUP_LIMIT_MAX,
.memory_zswap_max = CGROUP_LIMIT_MAX,
- .memory_limit = UINT64_MAX,
+ .startup_memory_zswap_max = CGROUP_LIMIT_MAX,
+ .memory_limit = CGROUP_LIMIT_MAX,
.memory_available = CGROUP_LIMIT_MAX,
.cpu_usage_nsec = UINT64_MAX,
.tasks_current = UINT64_MAX,
if (arg_action != ACTION_SYSTEMCTL)
return r;
- log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
+ if (sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) &&
+ STR_IN_SET(method, "TryRestartUnit", "ReloadOrTryRestartUnit")) {
+ /* Ignore masked unit if try-* is requested */
+
+ log_debug_errno(r, "Failed to %s %s, ignoring: %s", job_type, name, bus_error_message(error, r));
+ return 0;
+ } else
+ log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
if (!sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT,
BUS_ERROR_UNIT_MASKED,
if (arg_marked)
ret = enqueue_marked_jobs(bus, w);
-
else
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
#include "argv-util.h"
#include "bus-error.h"
#include "bus-locator.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
struct stat sta, stb;
int r;
- r = chase_symlinks_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta);
+ r = chase_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta);
if (r < 0)
return r;
- r = chase_symlinks_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb);
+ r = chase_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb);
if (r < 0)
return r;
#include "bus-locator.h"
#include "bus-map-properties.h"
#include "bus-unit-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dropin.h"
#include "env-util.h"
#include "exit-status.h"
if (!path)
return log_oom();
- r = chase_symlinks(path, arg_root, 0, &lpath, NULL);
+ r = chase(path, arg_root, 0, &lpath, NULL);
if (r == -ENOENT)
continue;
if (r == -ENOMEM)
bool arg_mkdir = false;
bool arg_marked = false;
const char *arg_drop_in = NULL;
+ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp);
STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_drop_in, unsetp);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int systemctl_help(void) {
_cleanup_free_ char *link = NULL;
" --root=PATH Edit/enable/disable/mask unit files in the specified\n"
" root directory\n"
" --image=PATH Edit/enable/disable/mask unit files in the specified\n"
- " image\n"
+ " disk image\n"
+ " --image-policy=POLICY\n"
+ " Specify disk image dissection policy\n"
" -n --lines=INTEGER Number of journal entries to show\n"
" -o --output=STRING Change journal output mode (short, short-precise,\n"
" short-iso, short-iso-precise, short-full,\n"
ARG_NO_WALL,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_NO_RELOAD,
ARG_KILL_WHOM,
ARG_KILL_VALUE,
{ "no-warn", no_argument, NULL, ARG_NO_WARN },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "force", no_argument, NULL, 'f' },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "kill-whom", required_argument, NULL, ARG_KILL_WHOM },
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case 'l':
arg_full = true;
break;
"state\n"
"cache\n"
"logs\n"
- "configuration");
+ "configuration\n"
+ "fdstore");
return 0;
}
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
#include "bus-print-properties.h"
#include "bus-util.h"
+#include "image-policy.h"
#include "install.h"
#include "output-mode.h"
#include "pager.h"
extern bool arg_mkdir;
extern bool arg_marked;
extern const char *arg_drop_in;
+extern ImagePolicy *arg_image_policy;
static inline const char* arg_job_mode(void) {
return _arg_job_mode ?: "replace";
/* 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
readable error message. Example: "STATUS=Completed
66% of file system check..."
+ NOTIFYACCESS=...
+ Reset the access to the service status notification socket.
+ Example: "NOTIFYACCESS=main"
+
ERRNO=... If a daemon fails, the errno-style error code,
formatted as string. Example: "ERRNO=2" for ENOENT.
*/
int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds);
+/*
+ Combination of sd_pid_notifyf() and sd_pid_notify_with_fds()
+*/
+int sd_pid_notifyf_with_fds(pid_t pid, int unset_environment, const int *fds, size_t n_fds, const char *format, ...) _sd_printf_(5,6);
+
/*
Returns > 0 if synchronization with systemd succeeded. Returns < 0
on error. Returns 0 if $NOTIFY_SOCKET was not set. Note that the
#include "alloc-util.h"
#include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "device-util.h"
#include "devnum-util.h"
#include "dirent-util.h"
NULL
};
- (void) unsetenv("NOTIFY_SOCKET");
execv(pull_binary_path(), (char *const*) cmdline);
log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path());
_exit(EXIT_FAILURE);
_cleanup_free_ char *resolved = NULL;
struct stat st;
- r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd);
+ r = chase(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd);
if (r < 0)
return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
} else if (RESOURCE_IS_FILESYSTEM(rr->type) && root) {
_cleanup_free_ char *resolved = NULL;
- r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
+ r = chase(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
#include "alloc-util.h"
#include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-parser.h"
#include "dirent-util.h"
#include "fd-util.h"
if (r == 0) {
/* Child */
- (void) unsetenv("NOTIFY_SOCKET");
execv(path, (char *const*) cmdline);
log_error_errno(errno, "Failed to execute %s tool: %m", path);
_exit(EXIT_FAILURE);
assert_not_reached();
if (resolve_link_path && root) {
- r = chase_symlinks(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+ r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "constants.h"
#include "dirent-util.h"
static bool arg_reboot = false;
static char *arg_component = NULL;
static int arg_verify = -1;
+static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef struct Context {
Transfer **transfers;
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
(ro ? DISSECT_IMAGE_READ_ONLY : 0) |
DISSECT_IMAGE_FSCK |
DISSECT_IMAGE_MKDIR |
if (arg_image || arg_root)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "The --root=/--image switches may not be combined with the '%s' operation.", argv[0]);
+ "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
r = context_make_offline(&context, NULL);
if (r < 0)
_cleanup_closedir_ DIR *d = NULL;
_cleanup_free_ char *p = NULL;
- r = chase_symlinks_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
+ r = chase_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
if (r == -ENOENT)
continue;
if (r < 0)
"\n%3$sOptions:%4$s\n"
" -C --component=NAME Select component to update\n"
" --definitions=DIR Find transfer definitions in specified directory\n"
- " --root=PATH Operate relative to root path\n"
- " --image=PATH Operate relative to image file\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ " --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY\n"
+ " Specify disk image dissection policy\n"
" -m --instances-max=INT How many instances to maintain\n"
" --sync=BOOL Controls whether to sync data to disk\n"
" --verify=BOOL Force signature verification on or off\n"
ARG_JSON,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_REBOOT,
ARG_VERIFY,
};
{ "json", required_argument, NULL, ARG_JSON },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "reboot", no_argument, NULL, ARG_REBOOT },
{ "component", required_argument, NULL, 'C' },
{ "verify", required_argument, NULL, ARG_VERIFY },
return r;
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_REBOOT:
arg_reboot = true;
break;
#include "alloc-util.h"
#include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "constants.h"
#include "copy.h"
static bool arg_dry_run = false;
static bool arg_inline = false;
static PagerFlags arg_pager_flags = 0;
+static ImagePolicy *arg_image_policy = NULL;
static OrderedHashmap *users = NULL, *groups = NULL;
static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
STATIC_DESTRUCTOR_REGISTER(uid_range, uid_range_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int errno_is_not_exists(int code) {
/* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
!(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
_cleanup_free_ char *pa = NULL, *pb = NULL;
- r = chase_symlinks(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
+ r = chase(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
if (r < 0) {
log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
r, "Failed to look up path '%s%s%s': %m",
return ERRNO_IS_RESOURCE(r) ? r : false;
}
- r = chase_symlinks(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
+ r = chase(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
if (r < 0) {
log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
r, "Failed to look up path '%s%s%s': %m",
" --cat-config Show configuration files\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
" --replace=PATH Treat arguments as replacement for PATH\n"
" --dry-run Just print what would be done\n"
" --inline Treat arguments as configuration lines\n"
ARG_CAT_CONFIG,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_REPLACE,
ARG_DRY_RUN,
ARG_INLINE,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
- { "root", required_argument, NULL, ARG_ROOT },
- { "image", required_argument, NULL, ARG_IMAGE },
- { "replace", required_argument, NULL, ARG_REPLACE },
- { "dry-run", no_argument, NULL, ARG_DRY_RUN },
- { "inline", no_argument, NULL, ARG_INLINE },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "replace", required_argument, NULL, ARG_REPLACE },
+ { "dry-run", no_argument, NULL, ARG_DRY_RUN },
+ { "inline", no_argument, NULL, ARG_INLINE },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
{}
};
break;
#endif
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_REPLACE:
if (!path_is_absolute(optarg) ||
!endswith(optarg, ".conf"))
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |
'test-cgroup-setup.c',
'test-cgroup-util.c',
'test-cgroup.c',
+ 'test-chase.c',
'test-clock.c',
'test-compare-operator.c',
'test-condition.c',
'test-hostname-setup.c',
'test-hostname-util.c',
'test-id128.c',
+ 'test-image-policy.c',
'test-import-util.c',
'test-in-addr-prefix-util.c',
'test-in-addr-util.c',
'test-list.c',
'test-local-addresses.c',
'test-locale-util.c',
+ 'test-lock-util.c',
'test-log.c',
'test-logarithm.c',
'test-macro.c',
'test-rm-rf.c',
'test-sd-hwdb.c',
'test-sd-path.c',
+ 'test-secure-bits.c',
'test-selinux.c',
'test-serialize.c',
'test-set.c',
'sources' : files('test-boot-timestamps.c'),
'condition' : 'ENABLE_EFI',
},
- {
- 'sources' : files('test-bpf-devices.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-bpf-firewall.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-bpf-foreign-programs.c'),
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-bpf-lsm.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
{
'sources' : files('test-btrfs.c'),
'type' : 'manual',
'dependencies' : libcap,
},
{
- 'sources' : files('test-cgroup-cpu.c'),
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-cgroup-mask.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-cgroup-unit-default.c'),
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-chase-symlinks.c'),
+ 'sources' : files('test-chase-manual.c'),
'type' : 'manual',
},
- {
- 'sources' : files('test-chown-rec.c'),
- 'base' : test_core_base,
- },
{
'sources' : files('test-compress-benchmark.c'),
'link_with' : [
'sources' : files('test-dlopen-so.c'),
'dependencies' : libp11kit_cflags
},
- {
- 'sources' : files('test-emergency-action.c'),
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-engine.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
{
'sources' : [
files('test-errno-list.c'),
generated_gperf_headers,
],
},
- {
- 'sources' : files('test-execute.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- 'timeout' : 360,
- },
{
'sources' : files('test-fd-util.c'),
'dependencies' : libseccomp,
],
'timeout' : 180,
},
- {
- 'sources' : files('test-install.c'),
- 'base' : test_core_base,
- 'type' : 'manual',
- },
{
'sources' : [
files('test-ip-protocol-list.c'),
'sources' : files('test-ipcrm.c'),
'type' : 'unsafe',
},
- {
- 'sources' : files('test-job-type.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
{
'sources' : files('test-json.c'),
'dependencies' : libm,
threads,
],
},
- {
- 'sources' : files('test-load-fragment.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
- {
- 'sources' : files('test-loop-block.c'),
- 'dependencies' : [threads, libblkid],
- 'base' : test_core_base,
- 'parallel' : false,
- },
{
'sources' : files('test-loopback.c'),
'dependencies' : common_test_dependencies,
},
- {
- 'sources' : files('test-manager.c'),
- 'base' : test_core_base,
- },
{
'sources' : files('test-math-util.c'),
'dependencies' : libm,
'sources' : files('test-mempress.c'),
'dependencies' : threads,
},
- {
- 'sources' : files('test-namespace.c'),
- 'dependencies' : [
- libblkid,
- threads,
- ],
- 'base' : test_core_base,
- },
{
'sources' : files('test-netlink-manual.c'),
'dependencies' : libkmod,
'condition' : 'HAVE_KMOD',
'type' : 'manual',
},
- {
- 'sources' : files('test-ns.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- 'type' : 'manual',
- },
{
'sources' : files('test-nscd-flush.c'),
'condition' : 'ENABLE_NSCD',
'sources' : files('test-parse-util.c'),
'dependencies' : libm,
},
- {
- 'sources' : files('test-path.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- 'timeout' : 120,
- },
{
'sources' : files('test-process-util.c'),
'dependencies' : threads,
'condition' : 'ENABLE_BOOTLOADER',
'c_args' : '-I@0@'.format(efi_config_h_dir),
},
- {
- 'sources' : files('test-sched-prio.c'),
- 'dependencies' : common_test_dependencies,
- 'base' : test_core_base,
- },
{
'sources' : files('test-seccomp.c'),
'dependencies' : libseccomp,
'type' : 'manual',
},
{
- 'sources' : files('test-unit-name.c'),
+ 'sources' : files('test-utmp.c'),
+ 'condition' : 'ENABLE_UTMP',
+ },
+ {
+ 'sources' : files('test-varlink.c'),
+ 'dependencies' : threads,
+ },
+ {
+ 'sources' : files('test-watchdog.c'),
+ 'type' : 'unsafe',
+ },
+
+
+ # Tests that link to libcore, i.e. tests for pid1 code.
+ {
+ 'sources' : files('test-bpf-devices.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
- 'sources' : files('test-unit-serialize.c'),
+ 'sources' : files('test-bpf-firewall.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
- 'sources' : files('test-utmp.c'),
- 'condition' : 'ENABLE_UTMP',
+ 'sources' : files('test-bpf-foreign-programs.c'),
+ 'base' : test_core_base,
},
{
- 'sources' : files('test-varlink.c'),
- 'dependencies' : threads,
+ 'sources' : files('test-bpf-lsm.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
},
{
- 'sources' : files('test-watch-pid.c'),
+ 'sources' : files('test-cgroup-cpu.c'),
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-cgroup-mask.c'),
'dependencies' : common_test_dependencies,
'base' : test_core_base,
},
{
- 'sources' : files('test-watchdog.c'),
- 'type' : 'unsafe',
+ 'sources' : files('test-cgroup-unit-default.c'),
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-chown-rec.c'),
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-core-unit.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-emergency-action.c'),
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-engine.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-execute.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ 'timeout' : 360,
+ },
+ {
+ 'sources' : files('test-install.c'),
+ 'base' : test_core_base,
+ 'type' : 'manual',
+ },
+ {
+ 'sources' : files('test-job-type.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-load-fragment.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-loop-block.c'),
+ 'dependencies' : [threads, libblkid],
+ 'base' : test_core_base,
+ 'parallel' : false,
+ },
+ {
+ 'sources' : files('test-manager.c'),
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-namespace.c'),
+ 'dependencies' : [
+ libblkid,
+ threads,
+ ],
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-ns.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ 'type' : 'manual',
+ },
+ {
+ 'sources' : files('test-path.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ 'timeout' : 120,
+ },
+ {
+ 'sources' : files('test-sched-prio.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-socket-bind.c'),
+ 'dependencies' : libdl,
+ 'condition' : 'BPF_FRAMEWORK',
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-unit-name.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-unit-serialize.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
+ },
+ {
+ 'sources' : files('test-watch-pid.c'),
+ 'dependencies' : common_test_dependencies,
+ 'base' : test_core_base,
},
-]
-
-############################################################
-
-# define some tests here, because the link_with deps were not defined earlier
-tests += [
+ # Tests from other directories that have link_with deps that were not defined earlier
{
'sources' : files('../libsystemd/sd-bus/test-bus-error.c'),
'link_with' : [
'link_with' : libudev,
'dependencies' : threads,
},
- {
- 'sources' : files('test-socket-bind.c'),
- 'dependencies' : libdl,
- 'condition' : 'BPF_FRAMEWORK',
- 'base' : test_core_base,
- },
]
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
+#include "fs-util.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "user-util.h"
TEST_RET(add_acls_for_user) {
- char fn[] = "/tmp/test-empty.XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-empty.XXXXXX";
_cleanup_close_ int fd = -EBADF;
char *cmd;
uid_t uid;
cmd = strjoina("getfacl -p ", fn);
assert_se(system(cmd) == 0);
- (void) unlink(fn);
return 0;
}
#include <unistd.h>
#include "async.h"
-#include "macro.h"
+#include "fs-util.h"
#include "tmpfile-util.h"
+#include "tests.h"
static bool test_async = false;
return NULL;
}
-int main(int argc, char *argv[]) {
+TEST(test_async) {
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-asynchronous_close.XXXXXX";
int fd;
- char name[] = "/tmp/test-asynchronous_close.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
asynchronous_close(fd);
assert_se(asynchronous_job(async_func, NULL) >= 0);
-
assert_se(asynchronous_sync(NULL) >= 0);
sleep(1);
assert_se(fcntl(fd, F_GETFD) == -1);
+ assert_se(errno == EBADF);
assert_se(test_async);
-
- (void) unlink(name);
-
- return 0;
}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
assert_se(b);
assert_se(bitmap_ensure_allocated(&b) == 0);
- bitmap_free(b);
- b = NULL;
+ b = bitmap_free(b);
assert_se(bitmap_ensure_allocated(&b) == 0);
assert_se(bitmap_isset(b, 0) == false);
bitmap_clear(b);
assert_se(bitmap_isclear(b) == true);
assert_se(bitmap_equal(b, b2) == false);
- bitmap_free(b2);
- b2 = NULL;
+ b2 = bitmap_free(b2);
assert_se(bitmap_set(b, UINT_MAX) == -ERANGE);
- bitmap_free(b);
- b = NULL;
+ b = bitmap_free(b);
assert_se(bitmap_ensure_allocated(&b) == 0);
assert_se(bitmap_ensure_allocated(&b2) == 0);
#include "tests.h"
static void _test_one(int line, const char *input, const char *output) {
- CalendarSpec *c;
+ _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
_cleanup_free_ char *p = NULL, *q = NULL;
usec_t u;
int r;
u = now(CLOCK_REALTIME);
r = calendar_spec_next_usec(c, u, &u);
log_info("Next: %s", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP(u));
- calendar_spec_free(c);
+ c = calendar_spec_free(c);
assert_se(calendar_spec_from_string(p, &c) >= 0);
assert_se(calendar_spec_to_string(c, &q) >= 0);
- calendar_spec_free(c);
assert_se(streq(q, p));
}
#define test_one(input, output) _test_one(__LINE__, input, output)
static void _test_next(int line, const char *input, const char *new_tz, usec_t after, usec_t expect) {
- CalendarSpec *c;
+ _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
usec_t u;
char *old_tz;
int r;
else
assert_se(r == -ENOENT);
- calendar_spec_free(c);
-
assert_se(set_unset_env("TZ", old_tz, true) == 0);
tzset();
}
TEST(timestamp) {
char buf[FORMAT_TIMESTAMP_MAX];
_cleanup_free_ char *t = NULL;
- CalendarSpec *c;
+ _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
usec_t x, y;
/* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */
log_info("%s", buf);
assert_se(calendar_spec_from_string(buf, &c) >= 0);
assert_se(calendar_spec_to_string(c, &t) >= 0);
- calendar_spec_free(c);
log_info("%s", t);
assert_se(parse_timestamp(t, &y) >= 0);
}
TEST(hourly_bug_4031) {
- CalendarSpec *c;
+ _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
usec_t n, u, w;
int r;
assert_se(u <= n + USEC_PER_HOUR);
assert_se(u < w);
assert_se(w <= u + USEC_PER_HOUR);
-
- calendar_spec_free(c);
}
TEST(calendar_spec_one) {
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "log.h"
#include "main-func.h"
printf("%s ", argv[i]);
fflush(stdout);
- r = chase_symlinks(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
+ r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
if (r < 0)
log_error_errno(r, "failed: %m");
else {
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "chase.h"
+#include "dirent-util.h"
+#include "fd-util.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 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);
+
+ /* If the file descriptor points to the root directory, the result will be absolute. */
+
+ fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH);
+ assert_se(fd >= 0);
+
+ assert_se(chaseat(fd, p, 0, &result, NULL) >= 0);
+ assert_se(streq(result, "/usr"));
+ result = mfree(result);
+
+ assert_se(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "/usr"));
+ result = mfree(result);
+
+ fd = safe_close(fd);
+
+ /* If the file descriptor does not point to the root directory, the result will be relative
+ * unless the result is outside of the specified file descriptor. */
+
+ assert_se(chaseat(tfd, "abc", 0, &result, NULL) >= 0);
+ assert_se(streq(result, "/usr"));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "/abc", 0, &result, NULL) >= 0);
+ assert_se(streq(result, "/usr"));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT);
+ assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT);
+
+ assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0);
+ assert_se(streq(result, "usr"));
+ result = mfree(result);
+
+ assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &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|CHASE_NONEXISTENT, 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;
+}
+
+TEST(chaseat_prefix_root) {
+ _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL;
+
+ assert_se(safe_getcwd(&cwd) >= 0);
+
+ assert_se(chaseat_prefix_root("/hoge", NULL, &ret) >= 0);
+ assert_se(streq(ret, "/hoge"));
+
+ ret = mfree(ret);
+
+ assert_se(chaseat_prefix_root("/hoge", "a/b/c", &ret) >= 0);
+ assert_se(streq(ret, "/hoge"));
+
+ ret = mfree(ret);
+
+ assert_se(chaseat_prefix_root("hoge", "/a/b//./c///", &ret) >= 0);
+ assert_se(streq(ret, "/a/b/c/hoge"));
+
+ ret = mfree(ret);
+
+ assert_se(chaseat_prefix_root("hoge", "a/b//./c///", &ret) >= 0);
+ assert_se(expected = path_join(cwd, "a/b/c/hoge"));
+ assert_se(streq(ret, expected));
+
+ ret = mfree(ret);
+ expected = mfree(expected);
+
+ assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0);
+ assert_se(streq(ret, "/a/b/c/hoge/aaa/../././b"));
+
+ ret = mfree(ret);
+
+ assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret) >= 0);
+ assert_se(expected = path_join(cwd, "a/b/c/hoge/aaa/../././b"));
+ assert_se(streq(ret, expected));
+}
+
+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) {
r = compress(text, size, buf, size, &j);
/* assume compression must be successful except for small or random inputs */
- assert_se(r > 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
+ assert_se(r >= 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
/* check for overwrites */
assert_se(buf[size] == 0);
#if HAVE_COMPRESSION
_unused_ static void test_compress_decompress(
- int flag,
const char *compression,
compress_blob_t compress,
decompress_blob_t decompress,
log_info_errno(r, "compression failed: %m");
assert_se(may_fail);
} else {
- assert_se(r == flag);
+ assert_se(r >= 0);
r = decompress(compressed, csize,
(void **) &decompressed, &csize, 0);
assert_se(r == 0);
assert_se(compressed2);
r = compress(data, data_len, compressed, BUFSIZE_2, &csize);
}
- assert_se(r > 0);
+ assert_se(r >= 0);
len = strlen(data);
log_info("/* %s with %s */", __func__, compression);
r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize);
- assert_se(r > 0);
+ assert_se(r >= 0);
for (size_t i = 1; i < strlen(TEXT); i++) {
_cleanup_free_ void *buf2 = NULL;
}
}
-_unused_ static void test_compress_stream(int flag,
- const char *compression,
+_unused_ static void test_compress_stream(const char *compression,
const char *cat,
compress_stream_t compress,
decompress_stream_t decompress,
assert_se((dst = mkostemp_safe(pattern)) >= 0);
- assert_se(compress(src, dst, -1, &uncompressed_size) == flag);
+ assert_se(compress(src, dst, -1, &uncompressed_size) >= 0);
if (cat) {
assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0);
random_bytes(data + 7, sizeof(data) - 7);
#if HAVE_XZ
- test_compress_decompress(COMPRESSION_XZ, "XZ",
- compress_blob_xz, decompress_blob_xz,
+ test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz,
text, sizeof(text), false);
- test_compress_decompress(COMPRESSION_XZ, "XZ",
- compress_blob_xz, decompress_blob_xz,
+ test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz,
data, sizeof(data), true);
test_decompress_startswith("XZ",
compress_blob_xz, decompress_startswith_xz,
huge, HUGE_SIZE, true);
- test_compress_stream(COMPRESSION_XZ, "XZ", "xzcat",
+ test_compress_stream("XZ", "xzcat",
compress_stream_xz, decompress_stream_xz, srcfile);
test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz);
#endif
#if HAVE_LZ4
- test_compress_decompress(COMPRESSION_LZ4, "LZ4",
- compress_blob_lz4, decompress_blob_lz4,
+ test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4,
text, sizeof(text), false);
- test_compress_decompress(COMPRESSION_LZ4, "LZ4",
- compress_blob_lz4, decompress_blob_lz4,
+ test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4,
data, sizeof(data), true);
test_decompress_startswith("LZ4",
compress_blob_lz4, decompress_startswith_lz4,
huge, HUGE_SIZE, true);
- test_compress_stream(COMPRESSION_LZ4, "LZ4", "lz4cat",
+ test_compress_stream("LZ4", "lz4cat",
compress_stream_lz4, decompress_stream_lz4, srcfile);
test_lz4_decompress_partial();
#endif
#if HAVE_ZSTD
- test_compress_decompress(COMPRESSION_ZSTD, "ZSTD",
- compress_blob_zstd, decompress_blob_zstd,
+ test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd,
text, sizeof(text), false);
- test_compress_decompress(COMPRESSION_ZSTD, "ZSTD",
- compress_blob_zstd, decompress_blob_zstd,
+ test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd,
data, sizeof(data), true);
test_decompress_startswith("ZSTD",
compress_blob_zstd, decompress_startswith_zstd,
huge, HUGE_SIZE, true);
- test_compress_stream(COMPRESSION_ZSTD, "ZSTD", "zstdcat",
+ test_compress_stream("ZSTD", "zstdcat",
compress_stream_zstd, decompress_stream_zstd, srcfile);
test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd);
#include "alloc-util.h"
#include "conf-files.h"
+#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "macro.h"
#include "mkdir.h"
-#include "parse-util.h"
#include "path-util.h"
#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
-#include "user-util.h"
+#include "tmpfile-util.h"
-static void setup_test_dir(char *tmp_dir, const char *files, ...) {
- va_list ap;
+TEST(conf_files_list) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF;
+ _cleanup_strv_free_ char **result = NULL;
+ const char *search1, *search2, *search1_a, *search1_b, *search1_c, *search2_aa;
+
+ tfd = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t);
+ assert(tfd >= 0);
- assert_se(mkdtemp(tmp_dir));
+ assert_se(mkdirat(tfd, "dir1", 0755) >= 0);
+ assert_se(mkdirat(tfd, "dir2", 0755) >= 0);
- va_start(ap, files);
- while (files) {
- _cleanup_free_ char *path;
+ search1 = strjoina(t, "/dir1/");
+ search2 = strjoina(t, "/dir2/");
- assert_se(path = path_join(tmp_dir, files));
- assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0);
+ FOREACH_STRING(p, "a.conf", "b.conf", "c.foo") {
+ _cleanup_free_ char *path = NULL;
- files = va_arg(ap, const char *);
+ assert_se(path = path_join(search1, p));
+ assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0);
}
- va_end(ap);
-}
-static void test_conf_files_list_one(bool use_root) {
- char tmp_dir[] = "/tmp/test-conf-files-XXXXXX";
- _cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL;
- const char *root_dir, *search, *expect_a, *expect_b, *expect_c, *mask;
+ assert_se(symlinkat("/dev/null", tfd, "dir1/m.conf") >= 0);
+
+ FOREACH_STRING(p, "a.conf", "aa.conf", "m.conf") {
+ _cleanup_free_ char *path = NULL;
- log_info("/* %s(%s) */", __func__, yes_no(use_root));
+ assert_se(path = path_join(search2, p));
+ assert_se(write_string_file(path, "hogehoge", WRITE_STRING_FILE_CREATE) >= 0);
+ }
- setup_test_dir(tmp_dir,
- "/dir/a.conf",
- "/dir/b.conf",
- "/dir/c.foo",
- NULL);
+ search1_a = strjoina(search1, "a.conf");
+ search1_b = strjoina(search1, "b.conf");
+ search1_c = strjoina(search1, "c.foo");
+ search2_aa = strjoina(search2, "aa.conf");
- mask = strjoina(tmp_dir, "/dir/d.conf");
- assert_se(symlink("/dev/null", mask) >= 0);
+ /* search dir1 without suffix */
+ assert_se(conf_files_list(&result, NULL, NULL, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
- if (use_root) {
- root_dir = tmp_dir;
- search = "/dir";
- } else {
- root_dir = NULL;
- search = strjoina(tmp_dir, "/dir");
- }
+ result = strv_free(result);
- expect_a = strjoina(tmp_dir, "/dir/a.conf");
- expect_b = strjoina(tmp_dir, "/dir/b.conf");
- expect_c = strjoina(tmp_dir, "/dir/c.foo");
+ assert_se(conf_files_list(&result, NULL, t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
- log_debug("/* Check when filtered by suffix */");
+ result = strv_free(result);
- assert_se(conf_files_list(&found_files, ".conf", root_dir, CONF_FILES_FILTER_MASKED, search) == 0);
- strv_print(found_files);
+ assert_se(conf_files_list_at(&result, NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
- assert_se(found_files);
- assert_se(streq_ptr(found_files[0], expect_a));
- assert_se(streq_ptr(found_files[1], expect_b));
- assert_se(!found_files[2]);
+ result = strv_free(result);
- log_debug("/* Check when unfiltered */");
- assert_se(conf_files_list(&found_files2, NULL, root_dir, CONF_FILES_FILTER_MASKED, search) == 0);
- strv_print(found_files2);
+ assert_se(conf_files_list_at(&result, NULL, tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf", "dir1/c.foo")));
- assert_se(found_files2);
- assert_se(streq_ptr(found_files2[0], expect_a));
- assert_se(streq_ptr(found_files2[1], expect_b));
- assert_se(streq_ptr(found_files2[2], expect_c));
- assert_se(!found_files2[3]);
+ result = strv_free(result);
- assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
-}
+ /* search dir1 with suffix */
+ assert_se(conf_files_list(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
-TEST(conf_files_list) {
- test_conf_files_list_one(false);
- test_conf_files_list_one(true);
+ result = strv_free(result);
+
+ assert_se(conf_files_list(&result, ".conf", t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf")));
+
+ result = strv_free(result);
+
+ /* search two dirs */
+ assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir2/aa.conf", "dir1/b.conf")));
+
+ result = strv_free(result);
+
+ /* filename only */
+ assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+ result = strv_free(result);
+
+ assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+ strv_print(result);
+ assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
}
static void test_conf_files_insert_one(const char *root) {
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "copy.h"
#include "fd-util.h"
#include "fileio.h"
TEST(copy_file) {
_cleanup_free_ char *buf = NULL;
- char fn[] = "/tmp/test-copy_file.XXXXXX";
- char fn_copy[] = "/tmp/test-copy_file.XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-copy_file.XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn_copy[] = "/tmp/test-copy_file.XXXXXX";
size_t sz = 0;
int fd;
assert_se(write_string_file(fn, "foo bar bar bar foo", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(copy_file(fn, fn_copy, 0, 0644, 0, 0, COPY_REFLINK) == 0);
+ assert_se(copy_file(fn, fn_copy, 0, 0644, COPY_REFLINK) == 0);
assert_se(read_full_file(fn_copy, &buf, &sz) == 0);
assert_se(streq(buf, "foo bar bar bar foo\n"));
assert_se(sz == 20);
-
- unlink(fn);
- unlink(fn_copy);
}
static bool read_file_at_and_streq(int dir_fd, const char *path, const char *expected) {
}
TEST(copy_file_fd) {
- char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
- char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
+ _cleanup_(unlink_tempfilep) char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
+ _cleanup_(unlink_tempfilep) char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
_cleanup_close_ int in_fd = -EBADF, out_fd = -EBADF;
const char *text = "boohoo\nfoo\n\tbar\n";
char buf[64] = {};
assert_se(read(out_fd, buf, sizeof buf) == (ssize_t) strlen(text));
assert_se(streq(buf, text));
-
- unlink(in_fn);
- unlink(out_fn);
}
TEST(copy_tree) {
assert_se(f = strjoin(original_dir, *p));
assert_se(l = strjoin(copy_dir, *ll));
- assert_se(chase_symlinks(l, NULL, 0, &target, NULL) == 1);
+ assert_se(chase(l, NULL, 0, &target, NULL) == 1);
assert_se(path_equal(f, target));
}
}
static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink, uint64_t max_bytes) {
- char fn2[] = "/tmp/test-copy-file-XXXXXX";
- char fn3[] = "/tmp/test-copy-file-XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn2[] = "/tmp/test-copy-file-XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn3[] = "/tmp/test-copy-file-XXXXXX";
_cleanup_close_ int fd = -EBADF, fd2 = -EBADF, fd3 = -EBADF;
int r;
struct stat buf, buf2, buf3;
log_info("%s try_reflink=%s max_bytes=%" PRIu64, __func__, yes_no(try_reflink), max_bytes);
- fd = open(src, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ fd = open(src, O_CLOEXEC | O_PATH);
assert_se(fd >= 0);
fd2 = mkostemp_safe(fn2);
assert_se(buf3.st_size == buf2.st_size);
else
assert_se((uint64_t) buf3.st_size == max_bytes);
-
- unlink(fn2);
- unlink(fn3);
}
TEST(copy_bytes_regular_file) {
q = strjoina(p, "/fstab");
- r = copy_file_atomic("/etc/fstab", q, 0644, 0, 0, COPY_REFLINK);
+ r = copy_file_atomic("/etc/fstab", q, 0644, COPY_REFLINK);
if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r))
return;
- assert_se(copy_file_atomic("/etc/fstab", q, 0644, 0, 0, COPY_REFLINK) == -EEXIST);
+ assert_se(copy_file_atomic("/etc/fstab", q, 0644, COPY_REFLINK) == -EEXIST);
- assert_se(copy_file_atomic("/etc/fstab", q, 0644, 0, 0, COPY_REPLACE) >= 0);
+ assert_se(copy_file_atomic("/etc/fstab", q, 0644, COPY_REPLACE) >= 0);
}
TEST(copy_proc) {
assert_se(mkdtemp_malloc(NULL, &p) >= 0);
assert_se(f = path_join(p, "version"));
- assert_se(copy_file("/proc/version", f, 0, MODE_INVALID, 0, 0, 0) >= 0);
+ assert_se(copy_file("/proc/version", f, 0, MODE_INVALID, 0) >= 0);
assert_se(read_one_line_file("/proc/version", &a) >= 0);
assert_se(read_one_line_file(f, &b) >= 0);
}
TEST_RET(copy_holes) {
- char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
- char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
+ _cleanup_(unlink_tempfilep) char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
struct stat stat;
off_t blksz;
int r, fd, fd_copy;
close(fd);
close(fd_copy);
- unlink(fn);
- unlink(fn_copy);
-
return 0;
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "tests.h"
+#include "unit.h"
+
+static void test_unit_escape_setting_one(
+ const char *s,
+ const char *expected_exec_env,
+ const char *expected_exec,
+ const char *expected_c) {
+
+ _cleanup_free_ char *a = NULL, *b, *c, *d,
+ *s_esc, *a_esc, *b_esc, *c_esc, *d_esc;
+ const char *t;
+
+ if (!expected_exec_env)
+ expected_exec_env = s;
+ if (!expected_exec)
+ expected_exec = expected_exec_env;
+ if (!expected_c)
+ expected_c = expected_exec;
+ assert_se(s_esc = cescape(s));
+
+ assert_se(t = unit_escape_setting(s, 0, &a));
+ assert_se(a_esc = cescape(t));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc);
+ assert_se(a == NULL);
+ assert_se(t == s);
+
+ assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV, &b));
+ assert_se(b_esc = cescape(t));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc);
+ assert_se(b == NULL || streq(b, t));
+ assert_se(streq(t, expected_exec_env));
+
+ assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX, &c));
+ assert_se(c_esc = cescape(t));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc);
+ assert_se(c == NULL || streq(c, t));
+ assert_se(streq(t, expected_exec));
+
+ assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_C, &d));
+ assert_se(d_esc = cescape(t));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc);
+ assert_se(d == NULL || streq(d, t));
+ assert_se(streq(t, expected_c));
+}
+
+TEST(unit_escape_setting) {
+ test_unit_escape_setting_one("/sbin/sbash", NULL, NULL, NULL);
+ test_unit_escape_setting_one("$", "$$", "$", "$");
+ test_unit_escape_setting_one("$$", "$$$$", "$$", "$$");
+ test_unit_escape_setting_one("'", "'", NULL, "\\'");
+ test_unit_escape_setting_one("\"", "\\\"", NULL, NULL);
+ test_unit_escape_setting_one("\t", "\\t", NULL, NULL);
+ test_unit_escape_setting_one(" ", NULL, NULL, NULL);
+ test_unit_escape_setting_one("$;'\"\t\n", "$$;'\\\"\\t\\n", "$;'\\\"\\t\\n", "$;\\'\\\"\\t\\n");
+}
+
+static void test_unit_concat_strv_one(
+ char **s,
+ const char *expected_none,
+ const char *expected_exec_env,
+ const char *expected_exec,
+ const char *expected_c) {
+
+ _cleanup_free_ char *a, *b, *c, *d,
+ *s_ser, *s_esc, *a_esc, *b_esc, *c_esc, *d_esc;
+
+ assert_se(s_ser = strv_join(s, "_"));
+ assert_se(s_esc = cescape(s_ser));
+ if (!expected_exec_env)
+ expected_exec_env = expected_none;
+ if (!expected_exec)
+ expected_exec = expected_none;
+ if (!expected_c)
+ expected_c = expected_none;
+
+ assert_se(a = unit_concat_strv(s, 0));
+ assert_se(a_esc = cescape(a));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc);
+ assert_se(streq(a, expected_none));
+
+ assert_se(b = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV));
+ assert_se(b_esc = cescape(b));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc);
+ assert_se(streq(b, expected_exec_env));
+
+ assert_se(c = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX));
+ assert_se(c_esc = cescape(c));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc);
+ assert_se(streq(c, expected_exec));
+
+ assert_se(d = unit_concat_strv(s, UNIT_ESCAPE_C));
+ assert_se(d_esc = cescape(d));
+ log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc);
+ assert_se(streq(d, expected_c));
+}
+
+TEST(unit_concat_strv) {
+ test_unit_concat_strv_one(STRV_MAKE("a", "b", "c"),
+ "\"a\" \"b\" \"c\"",
+ NULL,
+ NULL,
+ NULL);
+ test_unit_concat_strv_one(STRV_MAKE("a", " ", "$", "$$", ""),
+ "\"a\" \" \" \"$\" \"$$\" \"\"",
+ "\"a\" \" \" \"$$\" \"$$$$\" \"\"",
+ NULL,
+ NULL);
+ test_unit_concat_strv_one(STRV_MAKE("\n", " ", "\t"),
+ "\"\n\" \" \" \"\t\"",
+ "\"\\n\" \" \" \"\\t\"",
+ "\"\\n\" \" \" \"\\t\"",
+ "\"\\n\" \" \" \"\\t\"");
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <elf.h>
+
#include "alloc-util.h"
#include "coredump-util.h"
+#include "fileio.h"
+#include "fd-util.h"
+#include "format-util.h"
#include "macro.h"
#include "tests.h"
1 << COREDUMP_FILTER_SHARED_DAX)));
}
+static void test_parse_auxv_two(
+ uint8_t elf_class,
+ size_t offset,
+ const char *data,
+ size_t data_size,
+ int expect_at_secure,
+ uid_t expect_uid,
+ uid_t expect_euid,
+ gid_t expect_gid,
+ gid_t expect_egid) {
+
+ int at_secure;
+ uid_t uid, euid;
+ gid_t gid, egid;
+ assert_se(parse_auxv(LOG_ERR, elf_class, data, data_size,
+ &at_secure, &uid, &euid, &gid, &egid) == 0);
+
+ log_debug("[offset=%zu] at_secure=%d, uid="UID_FMT", euid="UID_FMT", gid="GID_FMT", egid="GID_FMT,
+ offset,
+ at_secure, uid, euid, gid, egid);
+
+ assert_se(uid == expect_uid);
+ assert_se(euid == expect_euid);
+ assert_se(gid == expect_gid);
+ assert_se(egid == expect_egid);
+}
+
+static void test_parse_auxv_one(
+ uint8_t elf_class,
+ int dir_fd,
+ const char *filename,
+ int expect_at_secure,
+ uid_t expect_uid,
+ uid_t expect_euid,
+ gid_t expect_gid,
+ gid_t expect_egid) {
+
+ _cleanup_free_ char *buf;
+ const char *data;
+ size_t data_size;
+ log_info("Parsing %s…", filename);
+ assert_se(read_full_file_at(dir_fd, filename, &buf, &data_size) >= 0);
+
+ for (size_t offset = 0; offset < 8; offset++) {
+ _cleanup_free_ char *buf2 = NULL;
+
+ if (offset == 0)
+ data = buf;
+ else {
+ assert_se(buf2 = malloc(offset + data_size));
+ memcpy(buf2 + offset, buf, data_size);
+ data = buf2 + offset;
+ }
+
+ test_parse_auxv_two(elf_class, offset, data, data_size,
+ expect_at_secure, expect_uid, expect_euid, expect_gid, expect_egid);
+ }
+}
+
+TEST(test_parse_auxv) {
+ _cleanup_free_ char *dir = NULL;
+ _cleanup_close_ int dir_fd = -EBADF;
+
+ assert_se(get_testdata_dir("auxv", &dir) >= 0);
+ dir_fd = open(dir, O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_PATH);
+ assert_se(dir_fd >= 0);
+
+ if (__BYTE_ORDER == __LITTLE_ENDIAN) {
+ test_parse_auxv_one(ELFCLASS32, dir_fd, "resolved.arm32", 0, 193, 193, 193, 193);
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "bash.riscv64", 0, 1001, 1001, 1001, 1001);
+ test_parse_auxv_one(ELFCLASS32, dir_fd, "sleep.i686", 0, 1000, 1000, 1000, 1000);
+ /* after chgrp and chmod g+s */
+ test_parse_auxv_one(ELFCLASS32, dir_fd, "sleep32.i686", 1, 1000, 1000, 1000, 10);
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "sleep64.amd64", 1, 1000, 1000, 1000, 10);
+
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "sudo.aarch64", 1, 1494200408, 0, 1494200408, 1494200408);
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "sudo.amd64", 1, 1000, 0, 1000, 1000);
+
+ /* Those run unprivileged, but start as root. */
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "dbus-broker-launch.amd64", 0, 0, 0, 0, 0);
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "dbus-broker-launch.aarch64", 0, 0, 0, 0, 0);
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "polkitd.aarch64", 0, 0, 0, 0, 0);
+ } else {
+ test_parse_auxv_one(ELFCLASS64, dir_fd, "cat.s390x", 0, 3481, 3481, 3481, 3481);
+ }
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
"d= \" \\n\\t\\$\\`\\\\\n" \
"\" \n"
-
TEST(load_env_file_1) {
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX";
assert_se(write_tmpfile(name, env_file_1) == 0);
assert_se(data[4] == NULL);
}
+TEST(load_env_file_invalid_utf8) {
+ /* Test out a couple of assignments where the key/value has an invalid
+ * UTF-8 character ("noncharacter")
+ *
+ * See: https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Non-characters
+ */
+ FOREACH_STRING(s,
+ "fo\ufffeo=bar",
+ "foo=b\uffffar",
+ "baz=hello world\ufffe") {
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX";
+ assert_se(write_tmpfile(name, s) == 0);
+
+ _cleanup_strv_free_ char **data = NULL;
+ assert_se(load_env_file(NULL, name, &data) == -EINVAL);
+ assert_se(!data);
+ }
+}
+
TEST(write_and_load_env_file) {
/* Make sure that our writer, parser and the shell agree on what our env var files mean */
assert_se(streq(a[0], "a=A"));
}
+TEST(strv_env_assign_many) {
+ _cleanup_strv_free_ char **a = NULL;
+
+ assert_se(strv_env_assign_many(&a, "a", "a", "b", "b") >= 0);
+
+ assert_se(strv_length(a) == 2);
+ assert_se(strv_contains(a, "a=a"));
+ assert_se(strv_contains(a, "b=b"));
+
+ assert_se(strv_env_assign_many(&a, "a", "A", "b", "b", "c", "c") >= 0);
+ assert_se(strv_length(a) == 3);
+ assert_se(strv_contains(a, "a=A"));
+ assert_se(strv_contains(a, "b=b"));
+ assert_se(strv_contains(a, "c=c"));
+
+ assert_se(strv_env_assign_many(&a, "b", NULL, "c", "C") >= 0);
+ assert_se(strv_length(a) == 2);
+ assert_se(strv_contains(a, "a=A"));
+ assert_se(strv_contains(a, "c=C"));
+
+ assert_se(strv_env_assign_many(&a, "a=", "B") == -EINVAL);
+ assert_se(strv_length(a) == 2);
+ assert_se(strv_contains(a, "a=A"));
+ assert_se(strv_contains(a, "c=C"));
+}
+
TEST(env_strv_get_n) {
const char *_env[] = {
"FOO=NO NO NO",
assert_se(r > 0);
}
+TEST(strv_env_name_is_valid) {
+ assert_se(strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "PATH")));
+ assert_se(!strv_env_name_is_valid(STRV_MAKE("", "PATH", "home", "user", "SHELL")));
+ assert_se(!strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "USER")));
+}
+
+TEST(getenv_path_list) {
+ _cleanup_strv_free_ char **path_list = NULL;
+
+ /* Empty paths */
+ FOREACH_STRING(s, "", ":", ":::::", " : ::: :: :") {
+ assert_se(setenv("TEST_GETENV_PATH_LIST", s, 1) >= 0);
+ assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL);
+ assert_se(!path_list);
+ }
+
+ /* Invalid paths */
+ FOREACH_STRING(s, ".", "..", "/../", "/", "/foo/bar/baz/../foo", "foo/bar/baz") {
+ assert_se(setenv("TEST_GETENV_PATH_LIST", s, 1) >= 0);
+ assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL);
+ assert_se(!path_list);
+ }
+
+ /* Valid paths mixed with invalid ones */
+ assert_se(setenv("TEST_GETENV_PATH_LIST", "/foo:/bar/baz:/../:/hello", 1) >= 0);
+ assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL);
+ assert_se(!path_list);
+
+ /* Finally some valid paths */
+ assert_se(setenv("TEST_GETENV_PATH_LIST", "/foo:/bar/baz:/hello/world:/path with spaces:/final", 1) >= 0);
+ assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) >= 0);
+ assert_se(streq(path_list[0], "/foo"));
+ assert_se(streq(path_list[1], "/bar/baz"));
+ assert_se(streq(path_list[2], "/hello/world"));
+ assert_se(streq(path_list[3], "/path with spaces"));
+ assert_se(streq(path_list[4], "/final"));
+ assert_se(path_list[5] == NULL);
+
+ assert_se(unsetenv("TEST_GETENV_PATH_LIST") >= 0);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
static void test_exec_execsearchpath(Manager *m) {
assert_se(mkdir_p("/tmp/test-exec_execsearchpath", 0755) >= 0);
- assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, 0, 0, COPY_REPLACE) >= 0);
+ assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, COPY_REPLACE) >= 0);
test(m, "exec-execsearchpath.service", 0, CLD_EXITED);
static void test_exec_privatetmp(Manager *m) {
assert_se(touch("/tmp/test-exec_privatetmp") >= 0);
- test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
test(m, "exec-privatetmp-no.service", 0, CLD_EXITED);
- test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
unlink("/tmp/test-exec_privatetmp");
}
return;
}
- test(m, "exec-privatedevices-yes.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-privatedevices-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
test(m, "exec-privatedevices-no.service", 0, CLD_EXITED);
- test(m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
- test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_GROUP, CLD_EXITED);
+ test(m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
+ test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
/* We use capsh to test if the capabilities are
* properly set, so be sure that it exists */
return;
}
- test(m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED);
- test(m, "exec-privatedevices-no-capability-mknod.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
- test(m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED);
- test(m, "exec-privatedevices-no-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-privatedevices-yes-capability-mknod.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+ test(m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED);
+ test(m, "exec-privatedevices-yes-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+ test(m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED);
}
static void test_exec_protecthome(Manager *m) {
return;
}
- test(m, "exec-protectkernelmodules-no-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
- test(m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED);
- test(m, "exec-protectkernelmodules-yes-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED);
+ test(m, "exec-protectkernelmodules-yes-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+ test(m, "exec-protectkernelmodules-yes-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_readonlypaths(Manager *m) {
- test(m, "exec-readonlypaths-simple.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-readonlypaths-simple.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
if (path_is_read_only_fs("/var") > 0) {
log_notice("Directory /var is readonly, skipping remaining tests in %s", __func__);
return;
}
- test(m, "exec-readonlypaths.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-readonlypaths.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
test(m, "exec-readonlypaths-with-bindpaths.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED);
- test(m, "exec-readonlypaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-readonlypaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_readwritepaths(Manager *m) {
return;
}
- test(m, "exec-readwritepaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-readwritepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_inaccessiblepaths(Manager *m) {
return;
}
- test(m, "exec-inaccessiblepaths-sys.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-inaccessiblepaths-sys.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
if (path_is_read_only_fs("/") > 0) {
log_notice("Root directory is readonly, skipping remaining tests in %s", __func__);
return;
}
- test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
}
static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
assert_se(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755) >= 0);
- test(m, "exec-mount-apivfs-no.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+ test(m, "exec-mount-apivfs-no.service", can_unshare || !MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
(void) rm_rf("/tmp/test-exec-mount-apivfs-no/root", REMOVE_ROOT|REMOVE_PHYSICAL);
}
static void test_exec_noexecpaths(Manager *m) {
- test(m, "exec-noexecpaths-simple.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-noexecpaths-simple.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_temporaryfilesystem(Manager *m) {
}
static void test_exec_umask(Manager *m) {
- test(m, "exec-umask-default.service", 0, CLD_EXITED);
- test(m, "exec-umask-0177.service", 0, CLD_EXITED);
+ test(m, "exec-umask-default.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+ test(m, "exec-umask-0177.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_runtimedirectory(Manager *m) {
}
static void test_exec_basic(Manager *m) {
- test(m, "exec-basic.service", 0, CLD_EXITED);
+ test(m, "exec-basic.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_ambientcapabilities(Manager *m) {
}
static void test_exec_privatenetwork(Manager *m) {
- int r, status;
+ int r;
r = find_executable("ip", NULL);
if (r < 0) {
return;
}
- status = can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_FAILURE;
- test(m, "exec-privatenetwork-yes-privatemounts-no.service", status, CLD_EXITED);
- test(m, "exec-privatenetwork-yes-privatemounts-yes.service", status, CLD_EXITED);
+ test(m, "exec-privatenetwork-yes-privatemounts-no.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-privatenetwork-yes-privatemounts-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_networknamespacepath(Manager *m) {
}
test(m, "exec-networknamespacepath-privatemounts-no.service", MANAGER_IS_SYSTEM(m) ? EXIT_SUCCESS : EXIT_FAILURE, CLD_EXITED);
- test(m, "exec-networknamespacepath-privatemounts-yes.service", can_unshare ? EXIT_SUCCESS : EXIT_FAILURE, CLD_EXITED);
+ test(m, "exec-networknamespacepath-privatemounts-yes.service", can_unshare ? EXIT_SUCCESS : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
}
static void test_exec_oomscoreadjust(Manager *m) {
}
static void test_exec_specifier(Manager *m) {
- test(m, "exec-specifier.service", 0, CLD_EXITED);
+ test(m, "exec-specifier.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
if (MANAGER_IS_SYSTEM(m))
test(m, "exec-specifier-system.service", 0, CLD_EXITED);
else
test(m, "exec-specifier-user.service", 0, CLD_EXITED);
- test(m, "exec-specifier@foo-bar.service", 0, CLD_EXITED);
+ test(m, "exec-specifier@foo-bar.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
test(m, "exec-specifier-interpolation.service", 0, CLD_EXITED);
}
#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"
#include "rm-rf.h"
#include "seccomp-util.h"
#include "serialize.h"
+#include "stat-util.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
TEST(close_many) {
int fds[3];
- char name0[] = "/tmp/test-close-many.XXXXXX";
- char name1[] = "/tmp/test-close-many.XXXXXX";
- char name2[] = "/tmp/test-close-many.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name0[] = "/tmp/test-close-many.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name1[] = "/tmp/test-close-many.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name2[] = "/tmp/test-close-many.XXXXXX";
fds[0] = mkostemp_safe(name0);
fds[1] = mkostemp_safe(name1);
assert_se(fcntl(fds[2], F_GETFD) >= 0);
safe_close(fds[2]);
-
- unlink(name0);
- unlink(name1);
- unlink(name2);
}
TEST(close_nointr) {
- char name[] = "/tmp/test-test-close_nointr.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-test-close_nointr.XXXXXX";
int fd;
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(close_nointr(fd) >= 0);
assert_se(close_nointr(fd) < 0);
-
- unlink(name);
}
TEST(same_fd) {
/* Close logging fd first, so that we don't confuse it by closing its fd */
log_close();
log_set_open_when_needed(true);
+ log_settle_target();
/* Close all but the ones to keep */
assert_se(close_all_fds(keep, n_keep) >= 0);
assert_se(FLAGS_SET(fl, O_DIRECTORY));
assert_se(FLAGS_SET(fl, O_PATH));
+ /* fd_reopen() with O_NOFOLLOW will systematically fail, since it is implemented via a symlink in /proc/self/fd/ */
+ assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC|O_NOFOLLOW) == -ELOOP);
+ assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW) == -ELOOP);
+
fd2 = fd_reopen(fd1, O_RDONLY|O_DIRECTORY|O_CLOEXEC); /* drop the O_PATH */
assert_se(fd2 >= 0);
assert_se(fstat(fd2, &st2) >= 0);
assert_se(S_ISDIR(st2.st_mode));
- assert_se(st1.st_ino == st2.st_ino);
- assert_se(st1.st_rdev == st2.st_rdev);
+ assert_se(stat_inode_same(&st1, &st2));
fl = fcntl(fd2, F_GETFL);
assert_se(fl >= 0);
assert_se(fstat(fd1, &st1) >= 0);
assert_se(S_ISDIR(st1.st_mode));
- assert_se(st1.st_ino == st2.st_ino);
- assert_se(st1.st_rdev == st2.st_rdev);
+ assert_se(stat_inode_same(&st1, &st2));
fl = fcntl(fd1, F_GETFL);
assert_se(fl >= 0);
assert_se(fstat(fd2, &st2) >= 0);
assert_se(S_ISREG(st2.st_mode));
- assert_se(st1.st_ino == st2.st_ino);
- assert_se(st1.st_rdev == st2.st_rdev);
+ assert_se(stat_inode_same(&st1, &st2));
fl = fcntl(fd2, F_GETFL);
assert_se(fl >= 0);
assert_se(fstat(fd1, &st1) >= 0);
assert_se(S_ISREG(st1.st_mode));
- assert_se(st1.st_ino == st2.st_ino);
- assert_se(st1.st_rdev == st2.st_rdev);
+ assert_se(stat_inode_same(&st1, &st2));
fl = fcntl(fd1, F_GETFL);
assert_se(fl >= 0);
safe_close(fd1);
assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC) == -EBADF);
fd1 = -EBADF;
+
+ /* Validate what happens if we reopen a symlink */
+ fd1 = open("/proc/self", O_PATH|O_CLOEXEC|O_NOFOLLOW);
+ assert_se(fd1 >= 0);
+ assert_se(fstat(fd1, &st1) >= 0);
+ assert_se(S_ISLNK(st1.st_mode));
+
+ fd2 = fd_reopen(fd1, O_PATH|O_CLOEXEC);
+ assert_se(fd2 >= 0);
+ assert_se(fstat(fd2, &st2) >= 0);
+ assert_se(S_ISLNK(st2.st_mode));
+ assert_se(stat_inode_same(&st1, &st2));
+ fd2 = safe_close(fd2);
+
+ /* So here's the thing: if we have an O_PATH fd to a symlink, we *cannot* convert it to a regular fd
+ * with that. i.e. you cannot have the VFS follow a symlink pinned via an O_PATH fd. */
+ assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC) == -ELOOP);
}
TEST(fd_reopen_condition) {
_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 "fd-util.h"
#include "fdset.h"
+#include "fs-util.h"
#include "macro.h"
#include "tests.h"
#include "tmpfile-util.h"
TEST(fdset_new_fill) {
int fd = -EBADF;
_cleanup_fdset_free_ FDSet *fdset = NULL;
- char name[] = "/tmp/test-fdset_new_fill.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_new_fill.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(fdset_new_fill(&fdset) >= 0);
assert_se(fdset_contains(fdset, fd));
-
- unlink(name);
}
TEST(fdset_put_dup) {
_cleanup_close_ int fd = -EBADF;
int copyfd = -EBADF;
_cleanup_fdset_free_ FDSet *fdset = NULL;
- char name[] = "/tmp/test-fdset_put_dup.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_put_dup.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(copyfd >= 0 && copyfd != fd);
assert_se(fdset_contains(fdset, copyfd));
assert_se(!fdset_contains(fdset, fd));
-
- unlink(name);
}
TEST(fdset_cloexec) {
int fd = -EBADF;
_cleanup_fdset_free_ FDSet *fdset = NULL;
int flags = -1;
- char name[] = "/tmp/test-fdset_cloexec.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_cloexec.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
flags = fcntl(fd, F_GETFD);
assert_se(flags >= 0);
assert_se(flags & FD_CLOEXEC);
-
- unlink(name);
}
TEST(fdset_close_others) {
int copyfd = -EBADF;
_cleanup_fdset_free_ FDSet *fdset = NULL;
int flags = -1;
- char name[] = "/tmp/test-fdset_close_others.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_close_others.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(flags < 0);
flags = fcntl(copyfd, F_GETFD);
assert_se(flags >= 0);
-
- unlink(name);
}
TEST(fdset_remove) {
_cleanup_close_ int fd = -EBADF;
- FDSet *fdset = NULL;
- char name[] = "/tmp/test-fdset_remove.XXXXXX";
+ _cleanup_fdset_free_ FDSet *fdset = NULL;
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_remove.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(fdset_put(fdset, fd) >= 0);
assert_se(fdset_remove(fdset, fd) >= 0);
assert_se(!fdset_contains(fdset, fd));
- fdset_free(fdset);
assert_se(fcntl(fd, F_GETFD) >= 0);
-
- unlink(name);
}
TEST(fdset_iterate) {
int fd = -EBADF;
- FDSet *fdset = NULL;
- char name[] = "/tmp/test-fdset_iterate.XXXXXX";
+ _cleanup_fdset_free_ FDSet *fdset = NULL;
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_iterate.XXXXXX";
int c = 0;
int a;
assert_se(a == fd);
}
assert_se(c == 1);
-
- fdset_free(fdset);
-
- unlink(name);
}
TEST(fdset_isempty) {
int fd;
_cleanup_fdset_free_ FDSet *fdset = NULL;
- char name[] = "/tmp/test-fdset_isempty.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_isempty.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(fdset_isempty(fdset));
assert_se(fdset_put(fdset, fd) >= 0);
assert_se(!fdset_isempty(fdset));
-
- unlink(name);
}
TEST(fdset_steal_first) {
int fd;
_cleanup_fdset_free_ FDSet *fdset = NULL;
- char name[] = "/tmp/test-fdset_steal_first.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_steal_first.XXXXXX";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(fdset_steal_first(fdset) == fd);
assert_se(fdset_steal_first(fdset) < 0);
assert_se(fdset_put(fdset, fd) >= 0);
-
- unlink(name);
}
TEST(fdset_new_array) {
#include "fileio.h"
#include "fs-util.h"
#include "io-util.h"
+#include "memfd-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
}
}
+TEST(read_one_line_file) {
+ _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-fileio-1lf-XXXXXX";
+ int fd;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *buf, *buf2, *buf3, *buf4, *buf5;
+
+ fd = mkostemp_safe(fn);
+ assert_se(fd >= 0);
+
+ f = fdopen(fd, "we");
+ assert_se(f);
+
+ assert_se(read_one_line_file(fn, &buf) == 0);
+ assert_se(streq_ptr(buf, ""));
+ assert_se(read_one_line_file(fn, &buf2) == 0);
+ assert_se(streq_ptr(buf2, ""));
+
+ assert_se(write_string_stream(f, "x", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+ fflush(f);
+
+ assert_se(read_one_line_file(fn, &buf3) == 1);
+ assert_se(streq_ptr(buf3, "x"));
+
+ assert_se(write_string_stream(f, "\n", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+ fflush(f);
+
+ assert_se(read_one_line_file(fn, &buf4) == 2);
+ assert_se(streq_ptr(buf4, "x"));
+
+ assert_se(write_string_stream(f, "\n", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+ fflush(f);
+
+ assert_se(read_one_line_file(fn, &buf5) == 2);
+ assert_se(streq_ptr(buf5, "x"));
+}
+
TEST(write_string_stream) {
_cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-write_string_stream-XXXXXX";
_cleanup_fclose_ FILE *f = NULL;
assert_se(write_string_file("/proc/version", buf2, WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_AVOID_NEWLINE) == 0);
}
+static void check_file_pairs_one(char **l) {
+ assert_se(l);
+ assert_se(strv_length(l) == 14);
+
+ STRV_FOREACH_PAIR(k, v, l) {
+ assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL"));
+ printf("%s=%s\n", *k, *v);
+ assert_se(!streq(*k, "NAME") || streq(*v, "Arch Linux"));
+ assert_se(!streq(*k, "ID") || streq(*v, "arch"));
+ assert_se(!streq(*k, "PRETTY_NAME") || streq(*v, "Arch Linux"));
+ assert_se(!streq(*k, "ANSI_COLOR") || streq(*v, "0;36"));
+ assert_se(!streq(*k, "HOME_URL") || streq(*v, "https://www.archlinux.org/"));
+ assert_se(!streq(*k, "SUPPORT_URL") || streq(*v, "https://bbs.archlinux.org/"));
+ assert_se(!streq(*k, "BUG_REPORT_URL") || streq(*v, "https://bugs.archlinux.org/"));
+ }
+}
+
TEST(load_env_file_pairs) {
_cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-load_env_file_pairs-XXXXXX";
int fd, r;
WRITE_STRING_FILE_CREATE);
assert_se(r == 0);
+ r = load_env_file_pairs_fd(fd, fn, &l);
+ assert_se(r >= 0);
+ check_file_pairs_one(l);
+ l = strv_free(l);
+
f = fdopen(fd, "r");
assert_se(f);
r = load_env_file_pairs(f, fn, &l);
assert_se(r >= 0);
-
- assert_se(strv_length(l) == 14);
- STRV_FOREACH_PAIR(k, v, l) {
- assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL"));
- printf("%s=%s\n", *k, *v);
- if (streq(*k, "NAME")) assert_se(streq(*v, "Arch Linux"));
- if (streq(*k, "ID")) assert_se(streq(*v, "arch"));
- if (streq(*k, "PRETTY_NAME")) assert_se(streq(*v, "Arch Linux"));
- if (streq(*k, "ANSI_COLOR")) assert_se(streq(*v, "0;36"));
- if (streq(*k, "HOME_URL")) assert_se(streq(*v, "https://www.archlinux.org/"));
- if (streq(*k, "SUPPORT_URL")) assert_se(streq(*v, "https://bbs.archlinux.org/"));
- if (streq(*k, "BUG_REPORT_URL")) assert_se(streq(*v, "https://bugs.archlinux.org/"));
- }
+ check_file_pairs_one(l);
}
TEST(search_and_fopen) {
IN_SET(r,
-ENOENT, /* Some of the files might be absent */
-EINVAL, /* too small reads from /proc/self/pagemap trigger EINVAL */
- -EFBIG)); /* /proc/kcore and /proc/self/pagemap should be too large */
+ -EFBIG, /* /proc/kcore and /proc/self/pagemap should be too large */
+ -EBADF)); /* /proc/kcore is masked when we are running in docker. */
} else
log_info("read_virtual_file(\"%s\", %zu): %s (%zu bytes)", filename, max_size, r ? "non-truncated" : "truncated", size);
}
test_read_virtual_file_one(SIZE_MAX);
}
+TEST(test_fdopen_independent) {
+#define TEST_TEXT "this is some random test text we are going to write to a memfd"
+ _cleanup_close_ int fd = -EBADF;
+ _cleanup_fclose_ FILE *f = NULL;
+ char buf[STRLEN(TEST_TEXT) + 1];
+
+ fd = memfd_new("fdopen_independent");
+ if (fd < 0) {
+ assert_se(ERRNO_IS_NOT_SUPPORTED(fd));
+ return;
+ }
+
+ assert_se(write(fd, TEST_TEXT, strlen(TEST_TEXT)) == strlen(TEST_TEXT));
+ /* we'll leave the read offset at the end of the memfd, the fdopen_independent() descriptors should
+ * start at the beginning anyway */
+
+ assert_se(fdopen_independent(fd, "re", &f) >= 0);
+ zero(buf);
+ assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT));
+ assert_se(streq(buf, TEST_TEXT));
+ assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDONLY);
+ assert_se(FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC));
+ f = safe_fclose(f);
+
+ assert_se(fdopen_independent(fd, "r", &f) >= 0);
+ zero(buf);
+ assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT));
+ assert_se(streq(buf, TEST_TEXT));
+ assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDONLY);
+ assert_se(!FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC));
+ f = safe_fclose(f);
+
+ assert_se(fdopen_independent(fd, "r+e", &f) >= 0);
+ zero(buf);
+ assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT));
+ assert_se(streq(buf, TEST_TEXT));
+ assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDWR);
+ assert_se(FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC));
+ f = safe_fclose(f);
+}
+
+
DEFINE_TEST_MAIN(LOG_DEBUG);
assert_se(streq(formatted, "bar\nbar\nbaz\n"));
}
+TEST(dup_cell) {
+ _cleanup_(table_unrefp) Table *t = NULL;
+ _cleanup_free_ char *formatted = NULL;
+
+ assert_se(t = table_new("foo", "bar", "x", "baz", ".", "%", "!", "~", "+"));
+ table_set_width(t, 75);
+
+ assert_se(table_add_many(t,
+ TABLE_STRING, "hello",
+ TABLE_UINT8, UINT8_C(42),
+ TABLE_UINT16, UINT16_C(666),
+ TABLE_UINT32, UINT32_C(253),
+ TABLE_PERCENT, 0,
+ TABLE_PATH_BASENAME, "/foo/bar",
+ TABLE_STRING, "aaa",
+ TABLE_STRING, "bbb",
+ TABLE_STRING, "ccc") >= 0);
+
+ /* Add the second row by duping cells */
+ for (size_t i = 0; i < table_get_columns(t); i++)
+ assert_se(table_dup_cell(t, table_get_cell(t, 1, i)) >= 0);
+
+ /* Another row, but dupe the last three strings from the same cell */
+ assert_se(table_add_many(t,
+ TABLE_STRING, "aaa",
+ TABLE_UINT8, UINT8_C(0),
+ TABLE_UINT16, UINT16_C(65535),
+ TABLE_UINT32, UINT32_C(4294967295),
+ TABLE_PERCENT, 100,
+ TABLE_PATH_BASENAME, "../") >= 0);
+
+ for (size_t i = 6; i < table_get_columns(t); i++)
+ assert_se(table_dup_cell(t, table_get_cell(t, 2, 0)) >= 0);
+
+ assert_se(table_format(t, &formatted) >= 0);
+ printf("%s\n", formatted);
+ assert_se(streq(formatted,
+ "FOO BAR X BAZ . % ! ~ +\n"
+ "hello 42 666 253 0% bar aaa bbb ccc\n"
+ "hello 42 666 253 0% bar aaa bbb ccc\n"
+ "aaa 0 65535 4294967295 100% ../ hello hello hello\n"));
+}
+
static int intro(void) {
assert_se(setenv("SYSTEMD_COLORS", "0", 1) >= 0);
assert_se(setenv("COLUMNS", "40", 1) >= 0);
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.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_symlinks) {
- _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_symlinks(p, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/usr"));
- result = mfree(result);
-
- pslash = strjoina(p, "/");
- r = chase_symlinks(pslash, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/usr/"));
- result = mfree(result);
-
- r = chase_symlinks(p, temp, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase_symlinks(pslash, temp, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- q = strjoina(temp, "/usr");
-
- r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(path_equal(result, q));
- result = mfree(result);
-
- qslash = strjoina(q, "/");
-
- r = chase_symlinks(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_symlinks(p, temp, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, q));
- result = mfree(result);
-
- r = chase_symlinks(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_symlinks(p, NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/"));
- result = mfree(result);
-
- r = chase_symlinks(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_symlinks(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_symlinks(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_symlinks(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_symlinks(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_symlinks(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_symlinks(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_symlinks(p, q, CHASE_SAFE, &result, NULL);
- assert_se(r > 0);
- result = mfree(result);
- }
-
- /* Paths using . */
-
- r = chase_symlinks("/etc/./.././", NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(path_equal(result, "/"));
- result = mfree(result);
-
- r = chase_symlinks("/etc/./.././", "/etc", 0, &result, NULL);
- assert_se(r > 0 && path_equal(result, "/etc"));
- result = mfree(result);
-
- r = chase_symlinks("/../.././//../../etc", NULL, 0, &result, NULL);
- assert_se(r > 0);
- assert_se(streq(result, "/etc"));
- result = mfree(result);
-
- r = chase_symlinks("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
- assert_se(r == 0);
- assert_se(streq(result, "/test-chase.fsldajfl"));
- result = mfree(result);
-
- r = chase_symlinks("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
- assert_se(r > 0);
- assert_se(streq(result, "/etc"));
- result = mfree(result);
-
- r = chase_symlinks("/../.././//../../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_symlinks("/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_symlinks(p, NULL, 0, &result, NULL);
- assert_se(r == -ELOOP);
-
- /* Path which doesn't exist */
-
- p = strjoina(temp, "/idontexist");
- r = chase_symlinks(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase_symlinks(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_symlinks(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase_symlinks(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_symlinks(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_symlinks(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_symlinks(p, NULL, 0, &result, NULL);
- assert_se(r == -ENOENT);
-
- r = chase_symlinks(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_symlinks(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_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
- assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
- assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
- assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
- assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
- assert_se(chown(q, 0, 0) >= 0);
- assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
- assert_se(rmdir(q) >= 0);
- assert_se(symlink("/etc/passwd", q) >= 0);
- assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
- assert_se(chown(p, 0, 0) >= 0);
- assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
- }
-
- p = strjoina(temp, "/machine-id-test");
- assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
-
- r = chase_symlinks(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_symlinks_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_symlinks(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_symlinks(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_symlinks(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_symlinks(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_symlinks(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_symlinks(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/a");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- p = strjoina(temp, "/b");
- assert_se(streq(p, result));
- result = mfree(result);
-
- r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
- assert_se(r == 0);
- assert_se(streq("/usr", result));
- result = mfree(result);
-
- r = chase_symlinks("/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_symlinks(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_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
- assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
- assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
- assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
- assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
- assert_se(chase_symlinks("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(chase_symlinks_at) {
- _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
- _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
- _cleanup_free_ char *result = NULL;
- 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(chase_symlinks_at(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(chase_symlinks_at(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "def"));
- result = mfree(result);
- assert_se(chase_symlinks_at(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(chase_symlinks_at(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(chase_symlinks_at(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(chase_symlinks_at(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(chase_symlinks_at(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(chase_symlinks_at(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "."));
- result = mfree(result);
-
- assert_se(chase_symlinks_at(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
- assert_se(streq(result, "."));
- result = mfree(result);
-
- /* Test CHASE_MKDIR_0755 */
-
- assert_se(chase_symlinks_at(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(chase_symlinks_at(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(chase_symlinks_at(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT);
-
- /* Test chase_symlinks_at_and_open() */
-
- fd = chase_symlinks_at_and_open(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_symlinks_at_and_open(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(readlink_and_make_absolute) {
const char *tempdir, *name, *name2, *name_alias;
_cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL;
assert_se(access(q, F_OK) < 0 && errno == ENOENT);
/* Check that a manual copy is detected */
- assert_se(copy_file(p, q, 0, MODE_INVALID, 0, 0, COPY_REFLINK) >= 0);
+ assert_se(copy_file(p, q, 0, MODE_INVALID, COPY_REFLINK) >= 0);
assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0);
assert_se(access(q, F_OK) < 0 && errno == ENOENT);
TEST(open_mkdir_at) {
_cleanup_close_ int fd = -EBADF, subdir_fd = -EBADF, subsubdir_fd = -EBADF;
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ struct stat sta, stb;
+
+ assert_se(open_mkdir_at(AT_FDCWD, "/", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
+ assert_se(open_mkdir_at(AT_FDCWD, ".", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
+
+ fd = open_mkdir_at(AT_FDCWD, "/", O_CLOEXEC, 0);
+ assert_se(fd >= 0);
+ assert_se(stat("/", &sta) >= 0);
+ assert_se(fstat(fd, &stb) >= 0);
+ assert_se(stat_inode_same(&sta, &stb));
+ fd = safe_close(fd);
+
+ fd = open_mkdir_at(AT_FDCWD, ".", O_CLOEXEC, 0);
+ assert_se(stat(".", &sta) >= 0);
+ assert_se(fstat(fd, &stb) >= 0);
+ assert_se(stat_inode_same(&sta, &stb));
+ fd = safe_close(fd);
assert_se(open_mkdir_at(AT_FDCWD, "/proc", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
}
TEST(xopenat) {
- _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF;
assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
assert_se((fd = xopenat(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0644)) >= 0);
assert_se(fd_verify_regular(fd) >= 0);
fd = safe_close(fd);
+
+ /* Test that we can reopen an existing fd with xopenat() by specifying an empty path. */
+
+ assert_se((fd = xopenat(tfd, "def", O_PATH|O_CLOEXEC, 0)) >= 0);
+ assert_se((fd2 = xopenat(fd, "", O_RDWR|O_CLOEXEC, 0644)) >= 0);
+}
+
+TEST(xopenat_lock) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+ siginfo_t si;
+
+ assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+ /* Test that we can acquire an exclusive lock on a directory in one process, remove the directory,
+ * and close the file descriptor and still properly create the directory and acquire the lock in
+ * another process. */
+
+ fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX);
+ assert_se(fd >= 0);
+ assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0);
+ assert_se(fd_verify_directory(fd) >= 0);
+ assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN);
+
+ pid_t pid = fork();
+ assert_se(pid >= 0);
+
+ if (pid == 0) {
+ safe_close(fd);
+
+ fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX);
+ assert_se(fd >= 0);
+ assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0);
+ assert_se(fd_verify_directory(fd) >= 0);
+ assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ /* We need to give the child process some time to get past the xopenat() call in xopenat_lock() and
+ * block in the call to lock_generic() waiting for the lock to become free. We can't modify
+ * xopenat_lock() to signal an eventfd to let us know when that has happened, so we just sleep for a
+ * little and assume that's enough time for the child process to get along far enough. It doesn't
+ * matter if it doesn't get far enough, in that case we just won't trigger the fallback logic in
+ * xopenat_lock(), but the test will still succeed. */
+ assert_se(usleep(20 * USEC_PER_MSEC) >= 0);
+
+ assert_se(unlinkat(tfd, "abc", AT_REMOVEDIR) >= 0);
+ fd = safe_close(fd);
+
+ assert_se(wait_for_terminate(pid, &si) >= 0);
+ assert_se(si.si_code == CLD_EXITED);
+
+ assert_se(xopenat_lock(tfd, "abc", 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF);
+ assert_se(xopenat_lock(tfd, "def", O_DIRECTORY, 0755, LOCK_POSIX, LOCK_EX) == -EBADF);
}
static int intro(void) {
#include "tests.h"
TEST(hashmap_replace) {
- Hashmap *m;
- char *val1, *val2, *val3, *val4, *val5, *r;
+ _cleanup_(hashmap_freep) Hashmap *m = NULL;
+ _cleanup_free_ char *val1 = NULL, *val2 = NULL, *val3 = NULL, *val4 = NULL, *val5 = NULL;
+ char *r;
m = hashmap_new(&string_hash_ops);
hashmap_replace(m, "key 5", val5);
r = hashmap_get(m, "key 5");
assert_se(streq(r, "val5"));
-
- free(val1);
- free(val2);
- free(val3);
- free(val4);
- free(val5);
- hashmap_free(m);
}
TEST(hashmap_copy) {
- Hashmap *m, *copy;
+ _cleanup_(hashmap_freep) Hashmap *m = NULL;
+ _cleanup_(hashmap_free_freep) Hashmap *copy = NULL;
char *val1, *val2, *val3, *val4, *r;
val1 = strdup("val1");
assert_se(streq(r, "val3"));
r = hashmap_get(copy, "key 4");
assert_se(streq(r, "val4"));
-
- hashmap_free_free(copy);
- hashmap_free(m);
}
TEST(hashmap_get_strv) {
- Hashmap *m;
- char **strv;
+ _cleanup_(hashmap_freep) Hashmap *m = NULL;
+ _cleanup_(strv_freep) char **strv = NULL;
char *val1, *val2, *val3, *val4;
val1 = strdup("val1");
assert_se(streq(strv[1], "val2"));
assert_se(streq(strv[2], "val3"));
assert_se(streq(strv[3], "val4"));
-
- strv_free(strv);
-
- hashmap_free(m);
}
TEST(hashmap_move_one) {
- Hashmap *m, *n;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL, *n = NULL;
char *val1, *val2, *val3, *val4, *r;
val1 = strdup("val1");
assert_se(!r);
assert_se(hashmap_move_one(n, m, "key 3") == -EEXIST);
-
- hashmap_free_free(m);
- hashmap_free_free(n);
}
TEST(hashmap_move) {
- Hashmap *m, *n;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL, *n = NULL;
char *val1, *val2, *val3, *val4, *r;
val1 = strdup("val1");
assert_se(r && streq(r, "val3"));
r = hashmap_get(n, "key 4");
assert_se(r && streq(r, "val4"));
-
- hashmap_free_free(m);
- hashmap_free_free(n);
}
TEST(hashmap_update) {
- Hashmap *m;
- char *val1, *val2, *r;
+ _cleanup_(hashmap_freep) Hashmap *m = NULL;
+ _cleanup_free_ char *val1 = NULL, *val2 = NULL;
+ char *r;
m = hashmap_new(&string_hash_ops);
val1 = strdup("old_value");
assert_se(hashmap_update(m, "key 1", val2) == 0);
r = hashmap_get(m, "key 1");
assert_se(streq(r, "new_value"));
-
- free(val1);
- free(val2);
- hashmap_free(m);
}
TEST(hashmap_put) {
- Hashmap *m = NULL;
+ _cleanup_(hashmap_freep) Hashmap *m = NULL;
int valid_hashmap_put;
void *val1 = (void*) "val 1";
void *val2 = (void*) "val 2";
key1 = strdup("key 1");
assert_se(hashmap_put(m, key1, val1) == 0);
assert_se(hashmap_put(m, key1, val2) == -EEXIST);
-
- hashmap_free(m);
}
TEST(hashmap_remove1) {
}
TEST(hashmap_foreach_key) {
- Hashmap *m;
+ _cleanup_(hashmap_freep) Hashmap *m = NULL;
bool key_found[] = { false, false, false, false };
const char *s;
const char *key;
assert_se(m);
assert_se(key_found[0] && key_found[1] && key_found[2] && !key_found[3]);
-
- hashmap_free(m);
}
TEST(hashmap_foreach) {
- Hashmap *m;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
bool value_found[] = { false, false, false, false };
char *val1, *val2, *val3, *val4, *s;
unsigned count;
val4 = strdup("my val4");
assert_se(val4);
- m = NULL;
-
count = 0;
HASHMAP_FOREACH(s, m)
count++;
assert_se(m);
assert_se(value_found[0] && value_found[1] && value_found[2] && value_found[3]);
-
- hashmap_free_free(m);
}
TEST(hashmap_merge) {
- Hashmap *m, *n;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
+ _cleanup_(hashmap_freep) Hashmap *n = NULL;
char *val1, *val2, *val3, *val4, *r;
val1 = strdup("my val1");
assert_se(m);
assert_se(n);
- hashmap_free(n);
- hashmap_free_free(m);
}
TEST(hashmap_contains) {
- Hashmap *m;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
char *val1;
val1 = strdup("my val");
assert_se(!hashmap_contains(NULL, "Key 1"));
assert_se(m);
- hashmap_free_free(m);
}
TEST(hashmap_isempty) {
- Hashmap *m;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
char *val1;
val1 = strdup("my val");
assert_se(!hashmap_isempty(m));
assert_se(m);
- hashmap_free_free(m);
}
TEST(hashmap_size) {
- Hashmap *m;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
char *val1, *val2, *val3, *val4;
val1 = strdup("my val");
assert_se(m);
assert_se(hashmap_size(m) == 4);
assert_se(hashmap_buckets(m) >= 4);
- hashmap_free_free(m);
}
TEST(hashmap_get) {
- Hashmap *m;
+ _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
char *r;
char *val;
assert_se(r == NULL);
assert_se(m);
- hashmap_free_free(m);
}
TEST(hashmap_get2) {
- Hashmap *m;
+ _cleanup_(hashmap_free_free_freep) Hashmap *m = NULL;
char *r;
char *val;
char key_orig[] = "Key 1";
assert_se(r == NULL);
assert_se(m);
- hashmap_free_free_free(m);
}
static void crippled_hashmap_func(const void *p, struct siphash *state) {
#include "alloc-util.h"
#include "fileio.h"
+#include "fs-util.h"
#include "hostname-setup.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
TEST(read_etc_hostname) {
- char path[] = "/tmp/hostname.XXXXXX";
+ _cleanup_(unlink_tempfilep) char path[] = "/tmp/hostname.XXXXXX";
char *hostname;
int fd;
/* nonexisting file */
assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT);
assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */
-
- unlink(path);
}
TEST(hostname_setup) {
#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);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "image-policy.h"
+#include "pretty-print.h"
+#include "string-util.h"
+#include "tests.h"
+#include "pager.h"
+
+static void test_policy(const ImagePolicy *p, const char *name) {
+ _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL;
+ _cleanup_free_ ImagePolicy *parsed = NULL;
+
+ assert_se(image_policy_to_string(p, /* simplified= */ false, &as_string) >= 0);
+ assert_se(image_policy_to_string(p, /* simplified= */ true, &as_string_simplified) >= 0);
+
+ printf("%s%s", ansi_underline(), name);
+
+ if (!streq(as_string_simplified, name)) {
+ printf(" → %s", as_string_simplified);
+
+ if (!streq(as_string, as_string_simplified))
+ printf(" (aka %s)", as_string);
+ }
+
+ printf("%s\n", ansi_normal());
+
+ assert_se(image_policy_from_string(as_string, &parsed) >= 0);
+ assert_se(image_policy_equal(p, parsed));
+ parsed = image_policy_free(parsed);
+
+ assert_se(image_policy_from_string(as_string_simplified, &parsed) >= 0);
+ assert_se(image_policy_equivalent(p, parsed));
+ parsed = image_policy_free(parsed);
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ _cleanup_free_ char *k = NULL;
+ PartitionPolicyFlags f;
+
+ f = image_policy_get(p, d);
+ if (f < 0) {
+ f = image_policy_get_exhaustively(p, d);
+ assert_se(f >= 0);
+ assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0);
+
+ printf("%s\t%s → n/a (exhaustively: %s)%s\n", ansi_grey(), partition_designator_to_string(d), k, ansi_normal());
+ } else {
+ assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0);
+ printf("\t%s → %s\n", partition_designator_to_string(d), k);
+ }
+ }
+
+ _cleanup_free_ char *w = NULL;
+ assert_se(partition_policy_flags_to_string(image_policy_default(p), /* simplified= */ true, &w) >= 0);
+ printf("\tdefault → %s\n", w);
+}
+
+static void test_policy_string(const char *t) {
+ _cleanup_free_ ImagePolicy *parsed = NULL;
+
+ assert_se(image_policy_from_string(t, &parsed) >= 0);
+ test_policy(parsed, t);
+}
+
+static void test_policy_equiv(const char *s, bool (*func)(const ImagePolicy *p)) {
+ _cleanup_(image_policy_freep) ImagePolicy *p = NULL;
+
+ assert_se(image_policy_from_string(s, &p) >= 0);
+
+ assert_se(func(p));
+ assert_se(func == image_policy_equiv_ignore || !image_policy_equiv_ignore(p));
+ assert_se(func == image_policy_equiv_allow || !image_policy_equiv_allow(p));
+ assert_se(func == image_policy_equiv_deny || !image_policy_equiv_deny(p));
+}
+
+TEST_RET(test_image_policy_to_string) {
+ test_policy(&image_policy_allow, "*");
+ test_policy(&image_policy_ignore, "-");
+ test_policy(&image_policy_deny, "~");
+ test_policy(&image_policy_sysext, "sysext");
+ test_policy(&image_policy_sysext_strict, "sysext-strict");
+ test_policy(&image_policy_container, "container");
+ test_policy(&image_policy_host, "host");
+ test_policy(&image_policy_service, "service");
+ test_policy(NULL, "null");
+
+ test_policy_string("");
+ test_policy_string("-");
+ test_policy_string("*");
+ test_policy_string("~");
+ test_policy_string("swap=open");
+ test_policy_string("swap=open:root=signed");
+ test_policy_string("swap=open:root=signed+read-only-on+growfs-off:=absent");
+ test_policy_string("=-");
+ test_policy_string("=");
+
+ test_policy_equiv("", image_policy_equiv_ignore);
+ test_policy_equiv("-", image_policy_equiv_ignore);
+ test_policy_equiv("*", image_policy_equiv_allow);
+ test_policy_equiv("~", image_policy_equiv_deny);
+ test_policy_equiv("=absent", image_policy_equiv_deny);
+ test_policy_equiv("=open", image_policy_equiv_allow);
+ test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow);
+ test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow);
+ test_policy_equiv("=ignore", image_policy_equiv_ignore);
+ test_policy_equiv("=absent+unused", image_policy_equiv_ignore);
+ test_policy_equiv("=unused+absent", image_policy_equiv_ignore);
+ test_policy_equiv("root=ignore:=ignore", image_policy_equiv_ignore);
+
+ assert_se(image_policy_from_string("pfft", NULL) == -EINVAL);
+ assert_se(image_policy_from_string("öäüß", NULL) == -EINVAL);
+ assert_se(image_policy_from_string(":", NULL) == -EINVAL);
+ assert_se(image_policy_from_string("a=", NULL) == -EBADSLT);
+ assert_se(image_policy_from_string("=a", NULL) == -EBADRQC);
+ assert_se(image_policy_from_string("==", NULL) == -EBADRQC);
+ assert_se(image_policy_from_string("root=verity:root=encrypted", NULL) == -ENOTUNIQ);
+ assert_se(image_policy_from_string("root=grbl", NULL) == -EBADRQC);
+ assert_se(image_policy_from_string("wowza=grbl", NULL) == -EBADSLT);
+
+ return 0;
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
UnitFileState state;
bool got_yes = false, got_no = false;
UnitFileList *fl;
- Hashmap *h;
+ _cleanup_(hashmap_freep) Hashmap *h = NULL;
+
+ CLEANUP_ARRAY(changes, n_changes, install_changes_free);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) == -ENOENT);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) == -ENOENT);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) == -ENOENT);
p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
assert_se(write_string_file(p,
"[Install]\n"
"WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
+ p = strjoina(root, "/usr/lib/systemd/system/preset-ignore.service");
+ assert_se(write_string_file(p,
+ "[Install]\n"
+ "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
+
p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset");
assert_se(write_string_file(p,
"enable *-yes.*\n"
+ "ignore *-ignore.*\n"
"disable *\n", WRITE_STRING_FILE_CREATE) >= 0);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
assert_se(n_changes == 1);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0);
assert_se(n_changes == 1);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
assert_se(n_changes == 0);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
assert_se(unit_file_preset_all(RUNTIME_SCOPE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
- assert_se(h = hashmap_new(&string_hash_ops));
+ assert_se(h = hashmap_new(&unit_file_list_hash_ops_free));
assert_se(unit_file_get_list(RUNTIME_SCOPE_SYSTEM, root, h, NULL, NULL) >= 0);
p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
assert_se(IN_SET(fl->state, UNIT_FILE_DISABLED, UNIT_FILE_STATIC, UNIT_FILE_INDIRECT, UNIT_FILE_ALIAS));
}
- unit_file_list_free(h);
-
assert_se(got_yes && got_no);
+
+ assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), &changes, &n_changes) >= 0);
+ assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
+ assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
}
TEST(revert) {
}
int main(int argc, char* argv[]) {
- Hashmap *h;
+ _cleanup_(hashmap_freep) Hashmap *h = NULL;
UnitFileList *p;
int r;
const char *const files[] = { "avahi-daemon.service", NULL };
test_setup_logging(LOG_DEBUG);
- h = hashmap_new(&string_hash_ops);
+ h = hashmap_new(&unit_file_list_hash_ops_free);
r = unit_file_get_list(RUNTIME_SCOPE_SYSTEM, NULL, h, NULL, NULL);
assert_se(r == 0);
unit_file_state_to_string(p->state));
}
- unit_file_list_free(h);
-
log_info("/*** enable **/");
r = unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes);
TEST_RET(unit_file_get_set) {
int r;
- Hashmap *h;
+ _cleanup_(hashmap_freep) Hashmap *h = NULL;
UnitFileList *p;
- h = hashmap_new(&string_hash_ops);
+ h = hashmap_new(&unit_file_list_hash_ops_free);
assert_se(h);
r = unit_file_get_list(RUNTIME_SCOPE_SYSTEM, NULL, h, NULL, NULL);
HASHMAP_FOREACH(p, h)
printf("%s = %s\n", p->path, unit_file_state_to_string(p->state));
- unit_file_list_free(h);
-
return 0;
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "lock-util.h"
+#include "rm-rf.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+
+TEST(make_lock_file) {
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_close_ int tfd = -EBADF;
+ _cleanup_(release_lock_file) LockFile lock1 = LOCK_FILE_INIT, lock2 = LOCK_FILE_INIT;
+
+ assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+ assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock1) >= 0);
+ assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0);
+ assert_se(make_lock_file_at(tfd, "lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY);
+ release_lock_file(&lock1);
+ assert_se(RET_NERRNO(faccessat(tfd, "lock", F_OK, 0)) == -ENOENT);
+ assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock2) >= 0);
+ release_lock_file(&lock2);
+ assert_se(make_lock_file_at(tfd, "lock", LOCK_SH, &lock1) >= 0);
+ assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0);
+ assert_se(make_lock_file_at(tfd, "lock", LOCK_SH, &lock2) >= 0);
+ release_lock_file(&lock1);
+ assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0);
+ release_lock_file(&lock2);
+
+ assert_se(fchdir(tfd) >= 0);
+ assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock1) >= 0);
+ assert_se(make_lock_file("lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
LOG_CONTEXT_PUSH_STRV(strv);
LOG_CONTEXT_PUSH_STRV(strv);
- /* Test that the log context was set up correctly. */
- assert_se(log_context_num_contexts() == 4);
- assert_se(log_context_num_fields() == 6);
+ /* Test that the log context was set up correctly. The strv we pushed twice should only
+ * result in one log context which is reused. */
+ assert_se(log_context_num_contexts() == 3);
+ assert_se(log_context_num_fields() == 4);
/* Test that everything still works with modifications to the log context. */
test_log_struct();
LOG_CONTEXT_PUSH_STRV(strv);
/* Check that our nested fields got added correctly. */
- assert_se(log_context_num_contexts() == 6);
- assert_se(log_context_num_fields() == 9);
+ assert_se(log_context_num_contexts() == 4);
+ assert_se(log_context_num_fields() == 5);
/* Test that everything still works in a nested block. */
test_log_struct();
}
/* Check that only the fields from the nested block got removed. */
- assert_se(log_context_num_contexts() == 4);
- assert_se(log_context_num_fields() == 6);
+ assert_se(log_context_num_contexts() == 3);
+ assert_se(log_context_num_fields() == 4);
}
assert_se(log_context_num_contexts() == 0);
assert_se(log_context_num_fields() == 0);
{
- _cleanup_(log_context_freep) LogContext *ctx = NULL;
+ _cleanup_(log_context_unrefp) LogContext *ctx = NULL;
char **strv = STRV_MAKE("SIXTH=ijn", "SEVENTH=PRP");
- assert_se(ctx = log_context_new(strv, /*owned=*/ false));
+ assert_se(ctx = log_context_new_strv(strv, /*owned=*/ false));
assert_se(log_context_num_contexts() == 1);
assert_se(log_context_num_fields() == 2);
assert_se(iovw);
assert_se(iovw_consume(iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1) == 0);
+ LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov));
LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov));
LOG_CONTEXT_CONSUME_IOV(iovw->iovec, iovw->count);
LOG_CONTEXT_PUSH("STU=vwx");
test_log_syntax();
}
+ {
+ LOG_CONTEXT_PUSH_KEY_VALUE("ABC=", "QED");
+ LOG_CONTEXT_PUSH_KEY_VALUE("ABC=", "QED");
+ assert_se(log_context_num_contexts() == 1);
+ assert_se(log_context_num_fields() == 1);
+
+ test_log_struct();
+ test_long_lines();
+ test_log_syntax();
+ }
+
assert_se(log_context_num_contexts() == 0);
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;
assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
}
+TEST(popcount) {
+ uint16_t u16a = 0x0000;
+ uint16_t u16b = 0xFFFF;
+ uint32_t u32a = 0x00000010;
+ uint32_t u32b = 0xFFFFFFFF;
+ uint64_t u64a = 0x0000000000000010;
+ uint64_t u64b = 0x0100000000100010;
+
+ assert_se(popcount(u16a) == 0);
+ assert_se(popcount(u16b) == 16);
+ assert_se(popcount(u32a) == 1);
+ assert_se(popcount(u32b) == 32);
+ assert_se(popcount(u64a) == 1);
+ assert_se(popcount(u64b) == 3);
+
+ /* This would fail:
+ * error: ‘_Generic’ selector of type ‘int’ is not compatible with any association
+ * assert_se(popcount(0x10) == 1);
+ */
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted);
- r = dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected);
+ r = dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected);
if (r < 0)
log_error_errno(r, "Failed dissect loopback device %s: %m", loop->node);
assert_se(r >= 0);
sfdisk = NULL;
#if HAVE_BLKID
- assert_se(dissect_image_file(p, NULL, NULL, 0, &dissected) >= 0);
+ assert_se(dissect_image_file(p, NULL, NULL, NULL, 0, &dissected) >= 0);
verify_dissected_image(dissected);
dissected = dissected_image_unref(dissected);
#endif
assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0);
#if HAVE_BLKID
- assert_se(dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
+ assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
verify_dissected_image(dissected);
FOREACH_STRING(fs, "vfat", "ext4") {
/* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block
* device. */
- assert_se(dissect_loop_device(loop, NULL, NULL, 0, &dissected) >= 0);
+ assert_se(dissect_loop_device(loop, NULL, NULL, NULL, 0, &dissected) >= 0);
verify_dissected_image_harder(dissected);
dissected = dissected_image_unref(dissected);
/* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */
- assert_se(dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
+ assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
verify_dissected_image_harder(dissected);
assert_se(mkdtemp_malloc(NULL, &mounted) >= 0);
assert_se(!fstype_can_umask("tmpfs"));
}
+TEST(path_get_mnt_id_at_null) {
+ _cleanup_close_ int root_fd = -EBADF, run_fd = -EBADF;
+ int id1, id2;
+
+ assert_se(path_get_mnt_id_at(AT_FDCWD, "/run/", &id1) >= 0);
+ assert_se(id1 > 0);
+
+ assert_se(path_get_mnt_id_at(AT_FDCWD, "/run", &id2) >= 0);
+ assert_se(id1 == id2);
+ id2 = -1;
+
+ root_fd = open("/", O_DIRECTORY|O_CLOEXEC);
+ assert_se(root_fd >= 0);
+
+ assert_se(path_get_mnt_id_at(root_fd, "/run/", &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+
+ assert_se(path_get_mnt_id_at(root_fd, "/run", &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+
+ assert_se(path_get_mnt_id_at(root_fd, "run", &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+
+ assert_se(path_get_mnt_id_at(root_fd, "run/", &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+
+ run_fd = openat(root_fd, "run", O_DIRECTORY|O_CLOEXEC);
+ assert_se(run_fd >= 0);
+
+ id2 = -1;
+ assert_se(path_get_mnt_id_at(run_fd, "", &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+
+ assert_se(path_get_mnt_id_at(run_fd, NULL, &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+
+ assert_se(path_get_mnt_id_at(run_fd, ".", &id2) >= 0);
+ assert_se(id1 = id2);
+ id2 = -1;
+}
+
static int intro(void) {
/* let's move into our own mount namespace with all propagation from the host turned off, so
* that /proc/self/mountinfo is static and constant for the whole time our test runs. */
assert_se(fd > 0);
r = setup_namespace(NULL,
+ NULL,
NULL,
NULL,
&ns_info,
NULL,
NULL,
NULL,
+ NULL,
0,
NULL,
0,
NULL,
NULL,
NULL,
+ NULL,
NULL);
assert_se(r == 0);
log_info("Not chrooted");
r = setup_namespace(root_directory,
+ NULL,
NULL,
NULL,
&ns_info,
&(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
NULL,
0,
+ NULL,
tmp_dir,
var_tmp_dir,
NULL,
NULL,
NULL,
NULL,
+ NULL,
NULL);
if (r < 0) {
log_error_errno(r, "Failed to set up namespace: %m");
.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,
assert_se(streq(l[3], "str3"));
}
-TEST(strv_parse_nulstr) {
- _cleanup_strv_free_ char **l = NULL;
- const char nulstr[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx";
+#define strv_parse_nulstr_full_one(s, n, e0, e1) \
+ ({ \
+ _cleanup_strv_free_ char **v0 = NULL, **v1 = NULL; \
+ \
+ assert_se(v0 = strv_parse_nulstr_full(s, n, false)); \
+ assert_se(strv_equal(v0, e0)); \
+ assert_se(v1 = strv_parse_nulstr_full(s, n, true)); \
+ assert_se(strv_equal(v1, e1)); \
+ })
- l = strv_parse_nulstr(nulstr, sizeof(nulstr)-1);
- assert_se(l);
- puts("Parse nulstr:");
- strv_print(l);
-
- assert_se(streq(l[0], "hoge"));
- assert_se(streq(l[1], "hoge2"));
- assert_se(streq(l[2], "hoge3"));
- assert_se(streq(l[3], ""));
- assert_se(streq(l[4], "hoge5"));
- assert_se(streq(l[5], ""));
- assert_se(streq(l[6], "xxx"));
- strv_free(l);
-
- l = strv_parse_nulstr((const char[0]) {}, 0);
- assert_se(l);
- assert_se(strv_isempty(l));
- strv_free(l);
+TEST(strv_parse_nulstr_full) {
+ const char nulstr1[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx";
+ const char nulstr2[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx\0\0\0";
- l = strv_parse_nulstr((const char[1]) { 0 }, 1);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("")));
- strv_free(l);
+ strv_parse_nulstr_full_one(nulstr1, sizeof(nulstr1) - 1,
+ STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"),
+ STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"));
- l = strv_parse_nulstr((const char[1]) { 'x' }, 1);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("x")));
- strv_free(l);
+ strv_parse_nulstr_full_one(nulstr2, sizeof(nulstr2) - 1,
+ STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx", "", ""),
+ STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"));
- l = strv_parse_nulstr((const char[2]) { 0, 0 }, 2);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("", "")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[0]) {}), 0,
+ STRV_MAKE_EMPTY, STRV_MAKE_EMPTY);
- l = strv_parse_nulstr((const char[2]) { 'x', 0 }, 2);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("x")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[1]) { 0 }), 1,
+ STRV_MAKE(""), STRV_MAKE_EMPTY);
- l = strv_parse_nulstr((const char[3]) { 0, 0, 0 }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("", "", "")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[1]) { 'x' }), 1,
+ STRV_MAKE("x"), STRV_MAKE("x"));
- l = strv_parse_nulstr((const char[3]) { 'x', 0, 0 }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("x", "")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[2]) { 0, 0 }), 2,
+ STRV_MAKE("", ""), STRV_MAKE_EMPTY);
- l = strv_parse_nulstr((const char[3]) { 0, 'x', 0 }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("", "x")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[2]) { 'x', 0 }), 2,
+ STRV_MAKE("x"), STRV_MAKE("x"));
- l = strv_parse_nulstr((const char[3]) { 0, 0, 'x' }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("", "", "x")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 0 }), 3,
+ STRV_MAKE("", "", ""), STRV_MAKE_EMPTY);
- l = strv_parse_nulstr((const char[3]) { 'x', 'x', 0 }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("xx")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 0 }), 3,
+ STRV_MAKE("x", ""), STRV_MAKE("x"));
- l = strv_parse_nulstr((const char[3]) { 0, 'x', 'x' }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("", "xx")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 0 }), 3,
+ STRV_MAKE("", "x"), STRV_MAKE("", "x"));
- l = strv_parse_nulstr((const char[3]) { 'x', 0, 'x' }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("x", "x")));
- strv_free(l);
+ strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 'x' }), 3,
+ STRV_MAKE("", "", "x"), STRV_MAKE("", "", "x"));
- l = strv_parse_nulstr((const char[3]) { 'x', 'x', 'x' }, 3);
- assert_se(l);
- assert_se(strv_equal(l, STRV_MAKE("xxx")));
+ strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 0 }), 3,
+ STRV_MAKE("xx"), STRV_MAKE("xx"));
+
+ strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 'x' }), 3,
+ STRV_MAKE("", "xx"), STRV_MAKE("", "xx"));
+
+ strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 'x' }), 3,
+ STRV_MAKE("x", "x"), STRV_MAKE("x", "x"));
+
+ strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 'x' }), 3,
+ STRV_MAKE("xxx"), STRV_MAKE("xxx"));
}
static void test_strv_make_nulstr_one(char **l) {
/* 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, "test", false, "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, "tester", false, "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, "tester", false, "FOOBAR", &foobar) == 0);
+ log_info("FOOBAR: %s", strnull(foobar));
+ assert_se(foobar == NULL);
+
+ assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "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,
assert_se(!path_equal_ptr("/a", "/b"));
assert_se(!path_equal_ptr("/a", NULL));
assert_se(!path_equal_ptr(NULL, "/a"));
-
- assert_se(path_equal_filename("/a/c", "/b/c"));
- assert_se(path_equal_filename("/a", "/a"));
- assert_se(!path_equal_filename("/a/b", "/a/c"));
- assert_se(!path_equal_filename("/b", "/c"));
}
static void test_path_simplify_one(const char *in, const char *out) {
test_path_compare_one("/foo/a/b", "/foo/aaa", -1);
}
+static void test_path_compare_filename_one(const char *a, const char *b, int expected) {
+ int r;
+
+ assert_se(path_compare_filename(a, a) == 0);
+ assert_se(path_compare_filename(b, b) == 0);
+
+ r = path_compare_filename(a, b);
+ assert_se((r > 0) == (expected > 0) && (r < 0) == (expected < 0));
+ r = path_compare_filename(b, a);
+ assert_se((r < 0) == (expected > 0) && (r > 0) == (expected < 0));
+
+ assert_se(path_equal_filename(a, a) == 1);
+ assert_se(path_equal_filename(b, b) == 1);
+ assert_se(path_equal_filename(a, b) == (expected == 0));
+ assert_se(path_equal_filename(b, a) == (expected == 0));
+}
+
+TEST(path_compare_filename) {
+ test_path_compare_filename_one("/goo", "/goo", 0);
+ test_path_compare_filename_one("/goo", "/goo", 0);
+ test_path_compare_filename_one("//goo", "/goo", 0);
+ test_path_compare_filename_one("//goo/////", "/goo", 0);
+ test_path_compare_filename_one("goo/////", "goo", 0);
+ test_path_compare_filename_one("/goo/boo", "/goo//boo", 0);
+ test_path_compare_filename_one("//goo/boo", "/goo/boo//", 0);
+ test_path_compare_filename_one("//goo/././//./boo//././//", "/goo/boo//.", 0);
+ test_path_compare_filename_one("/.", "//.///", -1);
+ test_path_compare_filename_one("/x", "x/", 0);
+ test_path_compare_filename_one("x/", "/", 1);
+ test_path_compare_filename_one("/x/./y", "x/y", 0);
+ test_path_compare_filename_one("/x/./y", "/x/y", 0);
+ test_path_compare_filename_one("/x/./././y", "/x/y/././.", 0);
+ test_path_compare_filename_one("./x/./././y", "./x/y/././.", 0);
+ test_path_compare_filename_one(".", "./.", -1);
+ test_path_compare_filename_one(".", "././.", -1);
+ test_path_compare_filename_one("./..", ".", 1);
+ test_path_compare_filename_one("x/.y", "x/y", -1);
+ test_path_compare_filename_one("foo", "/foo", 0);
+ test_path_compare_filename_one("/foo", "/foo/bar", 1);
+ test_path_compare_filename_one("/foo/aaa", "/foo/b", -1);
+ test_path_compare_filename_one("/foo/aaa", "/foo/b/a", 1);
+ test_path_compare_filename_one("/foo/a", "/foo/aaa", -1);
+ test_path_compare_filename_one("/foo/a/b", "/foo/aaa", 1);
+ test_path_compare_filename_one("/a/c", "/b/c", 0);
+ test_path_compare_filename_one("/a", "/a", 0);
+ test_path_compare_filename_one("/a/b", "/a/c", -1);
+ test_path_compare_filename_one("/b", "/c", -1);
+}
+
TEST(path_equal_root) {
/* Nail down the details of how path_equal("/", ...) works. */
r = path_find_first_component(&p, accept_dot_dot, &e);
if (r <= 0) {
if (r == 0) {
- if (path)
+ if (path) {
assert_se(p == path + strlen_ptr(path));
- else
+ assert_se(isempty(p));
+ } else
assert_se(!p);
assert_se(!e);
}
assert_se(strcspn(e, "/") == (size_t) r);
assert_se(strlen_ptr(*expected) == (size_t) r);
assert_se(strneq(e, *expected++, r));
+
+ assert_se(p);
+ log_debug("p=%s", p);
+ if (!isempty(*expected))
+ assert_se(startswith(p, *expected));
+ else if (ret >= 0) {
+ assert_se(p == path + strlen_ptr(path));
+ assert_se(isempty(p));
+ }
}
}
test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0);
test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL);
test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL);
- test_path_find_first_component_one("a/foo./b", false, STRV_MAKE("a", "foo.", "b"), 0);
+ test_path_find_first_component_one("a/foo./b//././/", false, STRV_MAKE("a", "foo.", "b"), 0);
test_path_find_first_component_one(NULL, true, NULL, 0);
test_path_find_first_component_one("", true, NULL, 0);
test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0);
test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0);
test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0);
- test_path_find_first_component_one("a/foo./b", true, STRV_MAKE("a", "foo.", "b"), 0);
+ test_path_find_first_component_one("a/foo./b//././/", true, STRV_MAKE("a", "foo.", "b"), 0);
memset(foo, 'a', sizeof(foo) -1);
char_array_0(foo);
assert_se(strcspn(e, "/") == (size_t) r);
assert_se(strlen_ptr(*expected) == (size_t) r);
assert_se(strneq(e, *expected++, r));
+
+ assert_se(next);
+ log_debug("path=%s\nnext=%s", path, next);
+ if (!isempty(*expected)) {
+ assert_se(next < path + strlen(path));
+ assert_se(next >= path + strlen(*expected));
+ assert_se(startswith(next - strlen(*expected), *expected));
+ } else if (ret >= 0)
+ assert_se(next == path);
}
}
#include "initrd-util.h"
#include "log.h"
#include "macro.h"
+#include "nulstr-util.h"
#include "proc-cmdline.h"
+#include "process-util.h"
#include "special.h"
#include "string-util.h"
+#include "strv.h"
#include "tests.h"
static int obj;
}
TEST(proc_cmdline_override) {
+ _cleanup_free_ char *line = NULL, *value = NULL;
+ _cleanup_strv_free_ char **args = NULL;
+
assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=different") == 0);
/* First test if the overrides for /proc/cmdline still work */
- _cleanup_free_ char *line = NULL, *value = NULL;
assert_se(proc_cmdline(&line) >= 0);
+ assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
+ line = mfree(line);
+ assert_se(proc_cmdline_strv(&args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("foo_bar=quux", "wuff-piep=tuet", "zumm", "some_arg_with_space=foo bar", "and_one_more=zzz aaa")));
+ args = strv_free(args);
/* Test if parsing makes uses of the override */
- assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
value = mfree(value);
assert_se(proc_cmdline_get_key("and_one_more", 0, &value) > 0 && streq_ptr(value, "zzz aaa"));
value = mfree(value);
- assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=") == 0);
- assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
+ assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=hoge") == 0);
+ assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
+
+ assert_se(proc_cmdline(&line) >= 0);
+ assert_se(streq(line, "hoge"));
+ line = mfree(line);
+ assert_se(proc_cmdline_strv(&args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("hoge")));
+ args = strv_free(args);
- assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
value = mfree(value);
in_initrd_force(!in_initrd());
bool t = true, f = false;
- assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"",
- parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
-
- assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"",
- parse_item_given, &f, 0) >= 0);
+ assert_se(proc_cmdline_parse(parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
+ assert_se(proc_cmdline_parse(parse_item_given, &f, 0) >= 0);
if (flip_initrd)
in_initrd_force(!in_initrd());
}
TEST(test_proc_cmdline_given) {
+ assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"") == 0);
+ assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=miepf=\"uuu\"") == 0);
+
test_proc_cmdline_given_one(false);
/* Repeat the same thing, but now flip our ininitrdness */
test_proc_cmdline_given_one(true);
TEST(proc_cmdline_get_key) {
_cleanup_free_ char *value = NULL;
- assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm spaaace='ö ü ß' ticks=\"''\"\n\nkkk=uuu\n\n\n") == 0);
+ assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm-ghh spaaace='ö ü ß' ticks=\"''\"\n\nkkk=uuu\n\n\n") == 0);
assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL);
assert_se(proc_cmdline_get_key("abc", 0, NULL) == 0);
value = mfree(value);
assert_se(proc_cmdline_get_key("foo_bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux"));
value = mfree(value);
+ assert_se(proc_cmdline_get_key("foo_bar", 0, NULL) == 0);
assert_se(proc_cmdline_get_key("foo-bar", 0, &value) > 0 && streq_ptr(value, "quux"));
value = mfree(value);
assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux"));
assert_se(proc_cmdline_get_key("wuff_piep", 0, NULL) == 0);
assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL);
- assert_se(proc_cmdline_get_key("zumm", 0, &value) == 0 && value == NULL);
- assert_se(proc_cmdline_get_key("zumm", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL);
- assert_se(proc_cmdline_get_key("zumm", 0, NULL) > 0);
+ assert_se(proc_cmdline_get_key("zumm-ghh", 0, &value) == 0 && value == NULL);
+ assert_se(proc_cmdline_get_key("zumm-ghh", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL);
+ assert_se(proc_cmdline_get_key("zumm-ghh", 0, NULL) > 0);
+ assert_se(proc_cmdline_get_key("zumm_ghh", 0, &value) == 0 && value == NULL);
+ assert_se(proc_cmdline_get_key("zumm_ghh", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL);
+ assert_se(proc_cmdline_get_key("zumm_ghh", 0, NULL) > 0);
assert_se(proc_cmdline_get_key("spaaace", 0, &value) > 0 && streq_ptr(value, "ö ü ß"));
value = mfree(value);
assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx"));
}
+#define test_proc_cmdline_filter_pid1_args_one(nulstr, expected) \
+ ({ \
+ _cleanup_strv_free_ char **a = NULL, **b = NULL; \
+ const char s[] = (nulstr); \
+ \
+ /* This emulates get_process_cmdline_strv(). */ \
+ assert_se(a = strv_parse_nulstr_full(s, ELEMENTSOF(s), \
+ /* drop_trailing_nuls = */ true)); \
+ assert_se(proc_cmdline_filter_pid1_args(a, &b) >= 0); \
+ assert_se(strv_equal(b, expected)); \
+ })
+
+TEST(proc_cmdline_filter_pid1_args) {
+ test_proc_cmdline_filter_pid1_args_one("systemd\0",
+ STRV_MAKE_EMPTY);
+
+ test_proc_cmdline_filter_pid1_args_one("systemd\0"
+ "hoge\0"
+ "-x\0"
+ "foo\0"
+ "--aaa\0"
+ "var\0",
+ STRV_MAKE("hoge", "foo", "var"));
+
+ test_proc_cmdline_filter_pid1_args_one("/usr/lib/systemd/systemd\0"
+ "--switched-root\0"
+ "--system\0"
+ "--deserialize\030\0" /* followed with space */
+ "--deserialize=31\0" /* followed with '=' */
+ "--exit-code=42\0"
+ "\0\0\0"
+ "systemd.log_level=debug\0"
+ "--unit\0foo.target\0"
+ " ' quoted '\0"
+ "systemd.log_target=console\0"
+ "\t\0"
+ " arg with space \0"
+ "3\0"
+ "\0\0\0",
+ STRV_MAKE("", "", "", "systemd.log_level=debug", " ' quoted '", "systemd.log_target=console", "\t", " arg with space ", "3"));
+}
+
static int intro(void) {
if (access("/proc/cmdline", R_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
return log_tests_skipped("can't read /proc/cmdline");
}
static void test_get_process_cmdline_one(pid_t pid) {
- _cleanup_free_ char *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL, *h = NULL;
+ _cleanup_free_ char *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL, *h = NULL, *joined = NULL;
+ _cleanup_strv_free_ char **strv_a = NULL, **strv_b = NULL;
int r;
r = get_process_cmdline(pid, SIZE_MAX, 0, &c);
r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX | PROCESS_CMDLINE_COMM_FALLBACK, &h);
log_info(" %s", r >= 0 ? h : errno_to_name(r));
+
+ r = get_process_cmdline_strv(pid, 0, &strv_a);
+ if (r >= 0)
+ assert_se(joined = strv_join(strv_a, "\", \""));
+ log_info(" \"%s\"", r >= 0 ? joined : errno_to_name(r));
+
+ joined = mfree(joined);
+
+ r = get_process_cmdline_strv(pid, PROCESS_CMDLINE_COMM_FALLBACK, &strv_b);
+ if (r >= 0)
+ assert_se(joined = strv_join(strv_b, "\", \""));
+ log_info(" \"%s\"", r >= 0 ? joined : errno_to_name(r));
}
TEST(get_process_cmdline) {
char path[] = "/tmp/test-cmdlineXXXXXX";
_cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *line = NULL;
+ _cleanup_strv_free_ char **args = NULL;
pid_t pid;
int r;
assert_se(streq(line, "[testa]"));
line = mfree(line);
+ assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("[testa]")));
+ args = strv_free(args);
+
/* Test with multiple arguments that don't require quoting */
assert_se(write(fd, "foo\0bar", 8) == 8);
assert_se(streq(line, "foo bar"));
line = mfree(line);
+ assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("foo", "bar")));
+ args = strv_free(args);
+
assert_se(write(fd, "quux", 4) == 4);
assert_se(get_process_cmdline(0, SIZE_MAX, 0, &line) >= 0);
log_debug("'%s'", line);
assert_se(streq(line, "foo bar quux"));
line = mfree(line);
+ assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("foo", "bar", "quux")));
+ args = strv_free(args);
+
assert_se(ftruncate(fd, 0) >= 0);
assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0);
assert_se(streq(line, "[aaaa bbbb …"));
line = mfree(line);
+ assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+ assert_se(strv_equal(args, STRV_MAKE("[aaaa bbbb cccc]")));
+ args = strv_free(args);
+
/* Test with multiple arguments that do require quoting */
#define CMDLINE1 "foo\0'bar'\0\"bar$\"\0x y z\0!``\0"
#define EXPECT1 "foo \"'bar'\" \"\\\"bar\\$\\\"\" \"x y z\" \"!\\`\\`\""
-#define EXPECT1p "foo $'\\'bar\\'' $'\"bar$\"' $'x y z' $'!``'"
+#define EXPECT1p "foo $'\\'bar\\'' $'\"bar$\"' $'x y z' $'!``'"
+#define EXPECT1v STRV_MAKE("foo", "'bar'", "\"bar$\"", "x y z", "!``")
+
assert_se(lseek(fd, SEEK_SET, 0) == 0);
assert_se(write(fd, CMDLINE1, sizeof CMDLINE1) == sizeof CMDLINE1);
assert_se(ftruncate(fd, sizeof CMDLINE1) == 0);
assert_se(streq(line, EXPECT1p));
line = mfree(line);
+ assert_se(get_process_cmdline_strv(0, 0, &args) >= 0);
+ assert_se(strv_equal(args, EXPECT1v));
+ args = strv_free(args);
+
#define CMDLINE2 "foo\0\1\2\3\0\0"
#define EXPECT2 "foo \"\\001\\002\\003\""
-#define EXPECT2p "foo $'\\001\\002\\003'"
+#define EXPECT2p "foo $'\\001\\002\\003'"
+#define EXPECT2v STRV_MAKE("foo", "\1\2\3")
+
assert_se(lseek(fd, SEEK_SET, 0) == 0);
assert_se(write(fd, CMDLINE2, sizeof CMDLINE2) == sizeof CMDLINE2);
assert_se(ftruncate(fd, sizeof CMDLINE2) == 0);
assert_se(streq(line, EXPECT2p));
line = mfree(line);
+ assert_se(get_process_cmdline_strv(0, 0, &args) >= 0);
+ assert_se(strv_equal(args, EXPECT2v));
+ args = strv_free(args);
+
safe_close(fd);
_exit(EXIT_SUCCESS);
}
#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);
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "securebits-util.h"
+#include "strv.h"
+#include "tests.h"
+#include "unit-file.h"
+
+static const char * const string_bits[] = {
+ "keep-caps",
+ "keep-caps-locked",
+ "no-setuid-fixup",
+ "no-setuid-fixup-locked",
+ "noroot",
+ "noroot-locked",
+ NULL
+};
+
+TEST(secure_bits_basic) {
+ _cleanup_free_ char *joined = NULL, *str = NULL;
+ int r;
+
+ /* Check if converting each bit from string and back to string yields
+ * the same value */
+ STRV_FOREACH(bit, string_bits) {
+ _cleanup_free_ char *s = NULL;
+
+ r = secure_bits_from_string(*bit);
+ assert_se(r > 0);
+ assert_se(secure_bits_is_valid(r));
+ assert_se(secure_bits_to_string_alloc(r, &s) >= 0);
+ printf("%s = 0x%x = %s\n", *bit, (unsigned)r, s);
+ assert_se(streq(*bit, s));
+ }
+
+ /* Ditto, but with all bits at once */
+ joined = strv_join((char**)string_bits, " ");
+ assert_se(joined);
+ r = secure_bits_from_string(joined);
+ assert_se(r > 0);
+ assert_se(secure_bits_is_valid(r));
+ assert_se(secure_bits_to_string_alloc(r, &str) >= 0);
+ printf("%s = 0x%x = %s\n", joined, (unsigned)r, str);
+ assert_se(streq(joined, str));
+
+ str = mfree(str);
+
+ /* Empty string */
+ assert_se(secure_bits_from_string("") == 0);
+ assert_se(secure_bits_from_string(" ") == 0);
+
+ /* Only invalid entries */
+ assert_se(secure_bits_from_string("foo bar baz") == 0);
+
+ /* Empty secure bits */
+ assert_se(secure_bits_to_string_alloc(0, &str) >= 0);
+ assert_se(isempty(str));
+
+ str = mfree(str);
+
+ /* Bits to string with check */
+ assert_se(secure_bits_to_string_alloc_with_check(INT_MAX, &str) == -EINVAL);
+ assert_se(str == NULL);
+ assert_se(secure_bits_to_string_alloc_with_check(
+ (1 << SECURE_KEEP_CAPS) | (1 << SECURE_KEEP_CAPS_LOCKED),
+ &str) >= 0);
+ assert_se(streq(str, "keep-caps keep-caps-locked"));
+}
+
+TEST(secure_bits_mix) {
+ static struct sbit_table {
+ const char *input;
+ const char *expected;
+ } sbit_table[] = {
+ { "keep-caps keep-caps keep-caps", "keep-caps" },
+ { "keep-caps noroot keep-caps", "keep-caps noroot" },
+ { "noroot foo bar baz noroot", "noroot" },
+ { "noroot \"foo\" \"bar keep-caps", "noroot" },
+ { "\"noroot foo\" bar keep-caps", "keep-caps" },
+ {}
+ };
+
+ for (const struct sbit_table *s = sbit_table; s->input; s++) {
+ _cleanup_free_ char *str = NULL;
+ int r;
+
+ r = secure_bits_from_string(s->input);
+ assert_se(r > 0);
+ assert_se(secure_bits_is_valid(r));
+ assert_se(secure_bits_to_string_alloc(r, &str) >= 0);
+ printf("%s = 0x%x = %s\n", s->input, (unsigned)r, str);
+ assert_se(streq(s->expected, str));
+ }
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
}
TEST(set_copy) {
- Set *s, *copy;
+ _cleanup_(set_freep) Set *s = NULL;
+ _cleanup_(set_free_freep) Set *copy = NULL;
char *key1, *key2, *key3, *key4;
key1 = strdup("key1");
assert_se(copy);
assert_se(set_equal(s, copy));
-
- set_free(s);
- set_free_free(copy);
}
TEST(set_ensure_put) {
DISABLE_WARNING_TYPE_LIMITS;
#define info_no_sign(t) \
- printf("%s → %zu bits, %zu byte alignment\n", STRINGIFY(t), \
+ printf("%s → %zu bits, %zu byte alignment\n", STRINGIFY(t), \
sizeof(t)*CHAR_BIT, \
- __alignof__(t))
+ alignof(t))
#define info(t) \
- printf("%s → %zu bits%s, %zu byte alignment\n", STRINGIFY(t), \
+ printf("%s → %zu bits%s, %zu byte alignment\n", STRINGIFY(t), \
sizeof(t)*CHAR_BIT, \
strstr(STRINGIFY(t), "signed") ? "" : \
(t)-1 < (t)0 ? ", signed" : ", unsigned", \
- __alignof__(t))
+ alignof(t))
enum Enum {
enum_value,
int main(void) {
int (*function_pointer)(void);
- info_no_sign(function_pointer);
+ info_no_sign(typeof(function_pointer));
info_no_sign(void*);
info(char*);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "sd-id128.h"
+
#include "alloc-util.h"
#include "log.h"
#include "specifier.h"
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);
}
}
+/* Bunch of specifiers that are not part of the common lists */
+TEST(specifiers_assorted) {
+ const sd_id128_t id = SD_ID128_ALLF;
+ const uint64_t llu = UINT64_MAX;
+ const Specifier table[] = {
+ /* Used in src/partition/repart.c */
+ { 'a', specifier_uuid, &id },
+ { 'b', specifier_uint64, &llu },
+ {}
+ };
+
+ for (const Specifier *s = table; s->specifier; s++) {
+ char spec[3];
+ _cleanup_free_ char *resolved = NULL;
+ int r;
+
+ xsprintf(spec, "%%%c", s->specifier);
+
+ r = specifier_printf(spec, SIZE_MAX, table, NULL, NULL, &resolved);
+ assert_se(r >= 0);
+
+ log_info("%%%c → %s", s->specifier, resolved);
+ }
+}
+
TEST(specifiers_missing_data_ok) {
_cleanup_free_ char *resolved = NULL;
TEST(files_same) {
_cleanup_close_ int fd = -EBADF;
- char name[] = "/tmp/test-files_same.XXXXXX";
- char name_alias[] = "/tmp/test-files_same.alias";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-files_same.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name_alias[] = "/tmp/test-files_same.alias";
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(files_same(name, name, AT_SYMLINK_NOFOLLOW));
assert_se(files_same(name, name_alias, 0));
assert_se(!files_same(name, name_alias, AT_SYMLINK_NOFOLLOW));
-
- unlink(name);
- unlink(name_alias);
}
TEST(is_symlink) {
- char name[] = "/tmp/test-is_symlink.XXXXXX";
- char name_link[] = "/tmp/test-is_symlink.link";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-is_symlink.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name_link[] = "/tmp/test-is_symlink.link";
_cleanup_close_ int fd = -EBADF;
fd = mkostemp_safe(name);
assert_se(is_symlink(name) == 0);
assert_se(is_symlink(name_link) == 1);
assert_se(is_symlink("/a/file/which/does/not/exist/i/guess") < 0);
-
- unlink(name);
- unlink(name_link);
}
TEST(path_is_fs_type) {
TEST_MAKE_CSTRING_ONE(test8, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
}
+TEST(find_line_startswith) {
+ static const char text[] =
+ "foobar\n"
+ "this is a test\n"
+ "foobar: waldo\n"
+ "more\n"
+ "\n"
+ "piff\n"
+ "foobarfoobar\n"
+ "iff\n";
+ static const char emptystring[] = "";
+
+ assert_se(find_line_startswith(text, "") == text);
+ assert_se(find_line_startswith(text, "f") == text+1);
+ assert_se(find_line_startswith(text, "foobar") == text+6);
+ assert_se(!find_line_startswith(text, "foobarx"));
+ assert_se(!find_line_startswith(text, "oobar"));
+ assert_se(find_line_startswith(text, "t") == text + 8);
+ assert_se(find_line_startswith(text, "th") == text + 9);
+ assert_se(find_line_startswith(text, "this") == text + 11);
+ assert_se(find_line_startswith(text, "foobarf") == text + 54);
+ assert_se(find_line_startswith(text, "more\n") == text + 41);
+ assert_se(find_line_startswith(text, "\n") == text + 42);
+ assert_se(find_line_startswith(text, "iff") == text + 63);
+
+ assert_se(find_line_startswith(emptystring, "") == emptystring);
+ assert_se(!find_line_startswith(emptystring, "x"));
+}
+
+TEST(strstrafter) {
+ static const char buffer[] = "abcdefghijklmnopqrstuvwxyz";
+
+ assert_se(!strstrafter(NULL, NULL));
+ assert_se(!strstrafter("", NULL));
+ assert_se(!strstrafter(NULL, ""));
+ assert_se(streq_ptr(strstrafter("", ""), ""));
+
+ assert_se(strstrafter(buffer, "a") == buffer + 1);
+ assert_se(strstrafter(buffer, "") == buffer);
+ assert_se(strstrafter(buffer, "ab") == buffer + 2);
+ assert_se(strstrafter(buffer, "cde") == buffer + 5);
+ assert_se(strstrafter(buffer, "xyz") == strchr(buffer, 0));
+ assert_se(strstrafter(buffer, buffer) == strchr(buffer, 0));
+ assert_se(!strstrafter(buffer, "-"));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e")));
}
+TEST(strv_find_first_field) {
+ char **haystack = STRV_MAKE("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
+
+ assert_se(strv_find_first_field(NULL, NULL) == NULL);
+ assert_se(strv_find_first_field(NULL, haystack) == NULL);
+ assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), NULL) == NULL);
+ assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), haystack) == NULL);
+ assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "a", "c"), haystack), "b"));
+ assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "c", "a"), haystack), "d"));
+ assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"));
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
#include "alloc-util.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "macro.h"
#include "path-util.h"
#include "strv.h"
_cleanup_fclose_ FILE *file = NULL;
char r;
bool need_nl;
- char name[] = "/tmp/test-read_one_char.XXXXXX";
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-read_one_char.XXXXXX";
assert_se(fmkostemp_safe(name, "r+", &file) == 0);
assert_se(fputs("\n", file) >= 0);
rewind(file);
assert_se(read_one_char(file, &r, 1000000, &need_nl) < 0);
-
- assert_se(unlink(name) >= 0);
}
TEST(getttyname_malloc) {
TEST(FORMAT_TIMESTAMP_with_tz) {
_cleanup_strv_free_ char **timezones = NULL;
+ test_format_timestamp_with_tz_one("UTC");
+
+ if (!slow_tests_enabled())
+ return (void) log_tests_skipped("slow tests are disabled");
+
assert_se(get_timezones(&timezones) >= 0);
STRV_FOREACH(tz, timezones)
test_format_timestamp_with_tz_one(*tz);
TEST(parse_timestamp_with_tz) {
_cleanup_strv_free_ char **timezones = NULL;
+ test_parse_timestamp_with_tz_one("UTC");
+
+ if (!slow_tests_enabled())
+ return (void) log_tests_skipped("slow tests are disabled");
+
assert_se(get_timezones(&timezones) >= 0);
STRV_FOREACH(tz, timezones)
test_parse_timestamp_with_tz_one(*tz);
test_tpm2_pcr_mask_from_string_one("0,2", 5, 0);
test_tpm2_pcr_mask_from_string_one("0+2", 5, 0);
test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL);
+ test_tpm2_pcr_mask_from_string_one("7+application-support", 8388736, 0);
+ test_tpm2_pcr_mask_from_string_one("8+boot-loader-code", 272, 0);
+ test_tpm2_pcr_mask_from_string_one("6+boot-loader-code,44", 0, -EINVAL);
+ test_tpm2_pcr_mask_from_string_one("7,shim-policy,4", 16528, 0);
+ test_tpm2_pcr_mask_from_string_one("sysexts,shim-policy+kernel-boot", 26624, 0);
+ test_tpm2_pcr_mask_from_string_one("sysexts,shim+kernel-boot", 0, -EINVAL);
+ test_tpm2_pcr_mask_from_string_one("sysexts+17+23", 8527872, 0);
+ test_tpm2_pcr_mask_from_string_one("debug+24", 16842752, 0);
+}
+
+TEST(pcr_index_from_string) {
+ assert_se(pcr_index_from_string("platform-code") == 0);
+ assert_se(pcr_index_from_string("0") == 0);
+ assert_se(pcr_index_from_string("platform-config") == 1);
+ assert_se(pcr_index_from_string("1") == 1);
+ assert_se(pcr_index_from_string("external-code") == 2);
+ assert_se(pcr_index_from_string("2") == 2);
+ assert_se(pcr_index_from_string("external-config") == 3);
+ assert_se(pcr_index_from_string("3") == 3);
+ assert_se(pcr_index_from_string("boot-loader-code") == 4);
+ assert_se(pcr_index_from_string("4") == 4);
+ assert_se(pcr_index_from_string("boot-loader-config") == 5);
+ assert_se(pcr_index_from_string("5") == 5);
+ assert_se(pcr_index_from_string("secure-boot-policy") == 7);
+ assert_se(pcr_index_from_string("7") == 7);
+ assert_se(pcr_index_from_string("kernel-initrd") == 9);
+ assert_se(pcr_index_from_string("9") == 9);
+ assert_se(pcr_index_from_string("ima") == 10);
+ assert_se(pcr_index_from_string("10") == 10);
+ assert_se(pcr_index_from_string("kernel-boot") == 11);
+ assert_se(pcr_index_from_string("11") == 11);
+ assert_se(pcr_index_from_string("kernel-config") == 12);
+ assert_se(pcr_index_from_string("12") == 12);
+ assert_se(pcr_index_from_string("sysexts") == 13);
+ assert_se(pcr_index_from_string("13") == 13);
+ assert_se(pcr_index_from_string("shim-policy") == 14);
+ assert_se(pcr_index_from_string("14") == 14);
+ assert_se(pcr_index_from_string("system-identity") == 15);
+ assert_se(pcr_index_from_string("15") == 15);
+ assert_se(pcr_index_from_string("debug") == 16);
+ assert_se(pcr_index_from_string("16") == 16);
+ assert_se(pcr_index_from_string("application-support") == 23);
+ assert_se(pcr_index_from_string("23") == 23);
+ assert_se(pcr_index_from_string("hello") == -EINVAL);
+ assert_se(pcr_index_from_string("8") == 8);
+ assert_se(pcr_index_from_string("44") == -EINVAL);
+ assert_se(pcr_index_from_string("-5") == -EINVAL);
}
TEST(tpm2_util_pbkdf2_hmac_sha256) {
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);
#include "sd-event.h"
+#include "data-fd-util.h"
#include "fd-util.h"
#include "json.h"
#include "rm-rf.h"
return varlink_reply(link, ret);
}
+static void test_fd(int fd, const void *buf, size_t n) {
+ char rbuf[n + 1];
+ ssize_t m;
+
+ m = read(fd, rbuf, n + 1);
+ assert_se(m >= 0);
+ assert_se(memcmp_nn(buf, n, rbuf, m) == 0);
+}
+
+static int method_passfd(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL;
+ JsonVariant *a;
+ int r;
+
+ a = json_variant_by_key(parameters, "fd");
+ if (!a)
+ return varlink_error(link, "io.test.BadParameters", NULL);
+
+ assert_se(streq_ptr(json_variant_string(a), "whoop"));
+
+ int xx = varlink_peek_fd(link, 0),
+ yy = varlink_peek_fd(link, 1),
+ zz = varlink_peek_fd(link, 2);
+
+ log_info("%i %i %i", xx, yy, zz);
+
+ assert_se(xx >= 0);
+ assert_se(yy >= 0);
+ assert_se(zz >= 0);
+
+ test_fd(xx, "foo", 3);
+ test_fd(yy, "bar", 3);
+ test_fd(zz, "quux", 4);
+
+ _cleanup_close_ int vv = acquire_data_fd("miau", 4, 0);
+ _cleanup_close_ int ww = acquire_data_fd("wuff", 4, 0);
+
+ assert_se(vv >= 0);
+ assert_se(ww >= 0);
+
+ r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("yo", JSON_BUILD_INTEGER(88))));
+ if (r < 0)
+ return r;
+
+ assert_se(varlink_push_fd(link, vv) == 0);
+ assert_se(varlink_push_fd(link, ww) == 1);
+
+ TAKE_FD(vv);
+ TAKE_FD(ww);
+
+ return varlink_reply(link, ret);
+}
+
static int method_done(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
if (++n_done == 2)
assert_se(varlink_get_peer_uid(link, &uid) >= 0);
assert_se(getuid() == uid);
+ assert_se(varlink_set_allow_fd_passing_input(link, true) >= 0);
+ assert_se(varlink_set_allow_fd_passing_output(link, true) >= 0);
return 0;
}
assert_se(varlink_connect_address(&c, arg) >= 0);
assert_se(varlink_set_description(c, "thread-client") >= 0);
+ assert_se(varlink_set_allow_fd_passing_input(c, true) >= 0);
+ assert_se(varlink_set_allow_fd_passing_output(c, true) >= 0);
assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0);
assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99);
assert_se(!e);
+ int fd1 = acquire_data_fd("foo", 3, 0);
+ int fd2 = acquire_data_fd("bar", 3, 0);
+ int fd3 = acquire_data_fd("quux", 4, 0);
+
+ assert_se(fd1 >= 0);
+ assert_se(fd2 >= 0);
+ assert_se(fd3 >= 0);
+
+ assert_se(varlink_push_fd(c, fd1) == 0);
+ assert_se(varlink_push_fd(c, fd2) == 1);
+ assert_se(varlink_push_fd(c, fd3) == 2);
+
+ assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0);
+
+ int fd4 = varlink_peek_fd(c, 0);
+ int fd5 = varlink_peek_fd(c, 1);
+
+ assert_se(fd4 >= 0);
+ assert_se(fd5 >= 0);
+
+ test_fd(fd4, "miau", 4);
+ test_fd(fd5, "wuff", 4);
+
assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist"));
assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND));
assert_se(varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID) >= 0);
assert_se(varlink_server_set_description(s, "our-server") >= 0);
+ assert_se(varlink_server_bind_method(s, "io.test.PassFD", method_passfd) >= 0);
assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0);
assert_se(varlink_server_bind_method(s, "io.test.Done", method_done) >= 0);
assert_se(varlink_server_bind_connect(s, on_connect) >= 0);
.msg_name = &server_addr,
.msg_namelen = sizeof(server_addr),
};
- struct timespec *recv_time = NULL;
+ struct timespec *recv_time;
triple_timestamp dts;
ssize_t len;
double origin, receive, trans, dest, delay, offset, root_distance;
return 0;
}
- recv_time = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_TIMESTAMPNS, struct timespec);
+ recv_time = CMSG_FIND_AND_COPY_DATA(&msghdr, SOL_SOCKET, SCM_TIMESTAMPNS, struct timespec);
if (!recv_time)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Packet timestamp missing.");
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "offline-passwd.h"
#include "path-util.h"
_cleanup_close_ int fd = -EBADF;
_cleanup_fclose_ FILE *f = NULL;
- fd = chase_symlinks_and_open(fname, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, &p);
+ fd = chase_and_open(fname, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, &p);
if (fd < 0)
return fd;
#include "btrfs-util.h"
#include "build.h"
#include "capability-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "chattr-util.h"
#include "conf-files.h"
#include "constants.h"
static char *arg_root = NULL;
static char *arg_image = NULL;
static char *arg_replace = NULL;
+static ImagePolicy *arg_image_policy = NULL;
#define MAX_DEPTH 256
STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
[CREATION_NORMAL] = "Created",
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)
path,
allow_failure ? ", ignoring" : "");
- r = chase_symlinks(dn, arg_root, allow_failure ? CHASE_SAFE : CHASE_SAFE|CHASE_WARN, NULL, &fd);
+ r = chase(dn, arg_root, allow_failure ? CHASE_SAFE : CHASE_SAFE|CHASE_WARN, NULL, &fd);
if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
return r;
if (r < 0)
if (!path_is_normalized(path))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to open invalid path '%s'.", path);
- r = chase_symlinks(path, arg_root, CHASE_SAFE|CHASE_WARN|CHASE_NOFOLLOW, NULL, &fd);
+ r = chase(path, arg_root, CHASE_SAFE|CHASE_WARN|CHASE_NOFOLLOW, NULL, &fd);
if (r == -ENOLINK)
return r; /* Unsafe symlink: already covered by CHASE_WARN */
if (r < 0)
assert(i);
assert(i->type == EMPTY_DIRECTORY);
- r = chase_symlinks(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
+ r = chase(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
return r;
if (r == -ENOENT) {
path = _path;
}
- r = chase_symlinks(path, arg_root, CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_WARN, NULL, NULL);
+ r = chase(path, arg_root, CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_WARN, NULL, NULL);
if (r == -EREMOTE) {
log_notice_errno(r, "Skipping %s", i->path); /* We log the configured path, to not confuse the user. */
return 0;
if (!GREEDY_REALLOC(existing->items, existing->n_items + 1))
return log_oom();
- existing->items[existing->n_items++] = i;
- i = (struct Item) {};
+ existing->items[existing->n_items++] = TAKE_STRUCT(i);
/* Sort item array, to enforce stable ordering of application */
typesafe_qsort(existing->items, existing->n_items, item_compare);
" -E Ignore rules prefixed with /dev, /proc, /run, /sys\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY Specify disk image dissection policy\n"
" --replace=PATH Treat arguments as replacement for PATH\n"
" --no-pager Do not pipe output into a pager\n"
"\nSee the %s for details.\n",
ARG_EXCLUDE_PREFIX,
ARG_ROOT,
ARG_IMAGE,
+ ARG_IMAGE_POLICY,
ARG_REPLACE,
ARG_NO_PAGER,
};
{ "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "replace", required_argument, NULL, ARG_REPLACE },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{}
break;
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_REPLACE:
if (!path_is_absolute(optarg) ||
!endswith(optarg, ".conf"))
r = mount_image_privately_interactively(
arg_image,
+ arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |
#include <sys/types.h>
#include <unistd.h>
+#include "build.h"
#include "device-nodes.h"
#include "fd-util.h"
#include "log.h"
+#include "main-func.h"
#include "memory-util.h"
#include "udev-util.h"
+#include "unaligned.h"
#define COMMAND_TIMEOUT_MSEC (30 * 1000)
+static bool arg_export = false;
+static const char *arg_device = NULL;
+
static int disk_scsi_inquiry_command(
int fd,
void *buf,
.din_xferp = (uintptr_t) buf,
.timeout = COMMAND_TIMEOUT_MSEC,
};
- int ret;
- ret = ioctl(fd, SG_IO, &io_v4);
- if (ret != 0) {
+ if (ioctl(fd, SG_IO, &io_v4) != 0) {
+ if (errno != EINVAL)
+ return log_debug_errno(errno, "ioctl v4 failed: %m");
+
/* could be that the driver doesn't do version 4, try version 3 */
- if (errno == EINVAL) {
- struct sg_io_hdr io_hdr = {
- .interface_id = 'S',
- .cmdp = (unsigned char*) cdb,
- .cmd_len = sizeof (cdb),
- .dxferp = buf,
- .dxfer_len = buf_len,
- .sbp = sense,
- .mx_sb_len = sizeof(sense),
- .dxfer_direction = SG_DXFER_FROM_DEV,
- .timeout = COMMAND_TIMEOUT_MSEC,
- };
-
- ret = ioctl(fd, SG_IO, &io_hdr);
- if (ret != 0)
- return ret;
-
- /* even if the ioctl succeeds, we need to check the return value */
- if (!(io_hdr.status == 0 &&
- io_hdr.host_status == 0 &&
- io_hdr.driver_status == 0)) {
- errno = EIO;
- return -1;
- }
- } else
- return ret;
- }
+ struct sg_io_hdr io_hdr = {
+ .interface_id = 'S',
+ .cmdp = (unsigned char*) cdb,
+ .cmd_len = sizeof (cdb),
+ .dxferp = buf,
+ .dxfer_len = buf_len,
+ .sbp = sense,
+ .mx_sb_len = sizeof(sense),
+ .dxfer_direction = SG_DXFER_FROM_DEV,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+
+ if (ioctl(fd, SG_IO, &io_hdr) != 0)
+ return log_debug_errno(errno, "ioctl v3 failed: %m");
+
+ /* even if the ioctl succeeds, we need to check the return value */
+ if (io_hdr.status != 0 ||
+ io_hdr.host_status != 0 ||
+ io_hdr.driver_status != 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v3 failed");
- /* even if the ioctl succeeds, we need to check the return value */
- if (!(io_v4.device_status == 0 &&
- io_v4.transport_status == 0 &&
- io_v4.driver_status == 0)) {
- errno = EIO;
- return -1;
+ } else {
+ /* even if the ioctl succeeds, we need to check the return value */
+ if (io_v4.device_status != 0 ||
+ io_v4.transport_status != 0 ||
+ io_v4.driver_status != 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed");
}
return 0;
.din_xferp = (uintptr_t) buf,
.timeout = COMMAND_TIMEOUT_MSEC,
};
- int ret;
- ret = ioctl(fd, SG_IO, &io_v4);
- if (ret != 0) {
- /* could be that the driver doesn't do version 4, try version 3 */
- if (errno == EINVAL) {
- struct sg_io_hdr io_hdr = {
- .interface_id = 'S',
- .cmdp = (unsigned char*) cdb,
- .cmd_len = sizeof (cdb),
- .dxferp = buf,
- .dxfer_len = buf_len,
- .sbp = sense,
- .mx_sb_len = sizeof (sense),
- .dxfer_direction = SG_DXFER_FROM_DEV,
- .timeout = COMMAND_TIMEOUT_MSEC,
- };
-
- ret = ioctl(fd, SG_IO, &io_hdr);
- if (ret != 0)
- return ret;
- } else
- return ret;
- }
+ if (ioctl(fd, SG_IO, &io_v4) != 0) {
+ if (errno != EINVAL)
+ return log_debug_errno(errno, "ioctl v4 failed: %m");
- if (!((sense[0] & 0x7f) == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c) &&
- !((sense[0] & 0x7f) == 0x70 && sense[12] == 0x00 && sense[13] == 0x1d)) {
- errno = EIO;
- return -1;
+ /* could be that the driver doesn't do version 4, try version 3 */
+ struct sg_io_hdr io_hdr = {
+ .interface_id = 'S',
+ .cmdp = (unsigned char*) cdb,
+ .cmd_len = sizeof (cdb),
+ .dxferp = buf,
+ .dxfer_len = buf_len,
+ .sbp = sense,
+ .mx_sb_len = sizeof (sense),
+ .dxfer_direction = SG_DXFER_FROM_DEV,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+
+ if (ioctl(fd, SG_IO, &io_hdr) != 0)
+ return log_debug_errno(errno, "ioctl v3 failed: %m");
+ } else {
+ if (!((sense[0] & 0x7f) == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c) &&
+ !((sense[0] & 0x7f) == 0x70 && sense[12] == 0x00 && sense[13] == 0x1d))
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed: %m");
}
return 0;
.din_xferp = (uintptr_t) buf,
.timeout = COMMAND_TIMEOUT_MSEC,
};
- int ret;
- ret = ioctl(fd, SG_IO, &io_v4);
- if (ret != 0) {
- /* could be that the driver doesn't do version 4, try version 3 */
- if (errno == EINVAL) {
- struct sg_io_hdr io_hdr = {
- .interface_id = 'S',
- .cmdp = (unsigned char*) cdb,
- .cmd_len = sizeof (cdb),
- .dxferp = buf,
- .dxfer_len = buf_len,
- .sbp = sense,
- .mx_sb_len = sizeof (sense),
- .dxfer_direction = SG_DXFER_FROM_DEV,
- .timeout = COMMAND_TIMEOUT_MSEC,
- };
-
- ret = ioctl(fd, SG_IO, &io_hdr);
- if (ret != 0)
- return ret;
- } else
- return ret;
- }
+ if (ioctl(fd, SG_IO, &io_v4) != 0) {
+ if (errno != EINVAL)
+ return log_debug_errno(errno, "ioctl v4 failed: %m");
- if (!((sense[0] & 0x7f) == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) {
- errno = EIO;
- return -1;
+ /* could be that the driver doesn't do version 4, try version 3 */
+ struct sg_io_hdr io_hdr = {
+ .interface_id = 'S',
+ .cmdp = (unsigned char*) cdb,
+ .cmd_len = sizeof (cdb),
+ .dxferp = buf,
+ .dxfer_len = buf_len,
+ .sbp = sense,
+ .mx_sb_len = sizeof (sense),
+ .dxfer_direction = SG_DXFER_FROM_DEV,
+ .timeout = COMMAND_TIMEOUT_MSEC,
+ };
+
+ if (ioctl(fd, SG_IO, &io_hdr) != 0)
+ return log_debug_errno(errno, "ioctl v3 failed: %m");
+ } else {
+ if ((sense[0] & 0x7f) != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed: %m");
}
return 0;
uint8_t identify[512],
unsigned offset_words,
size_t len) {
+ assert(offset_words < 512/2);
disk_identify_get_string(identify, offset_words,
(char *) identify + offset_words * 2, len);
}
-static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned offset_words) {
- uint16_t *p;
-
- p = (uint16_t *) identify;
- p[offset_words] = le16toh (p[offset_words]);
+static void disk_identify_fixup_uint16(uint8_t identify[512], unsigned offset_words) {
+ assert(offset_words < 512/2);
+ unaligned_write_ne16(identify + offset_words * 2,
+ unaligned_read_le16(identify + offset_words * 2));
}
/**
* disk_identify:
* @fd: File descriptor for the block device.
* @out_identify: Return location for IDENTIFY data.
- * @out_is_packet_device: Return location for whether returned data is from an IDENTIFY PACKET DEVICE.
*
* Sends the IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command to the
* device represented by @fd. If successful, then the result will be
- * copied into @out_identify and @out_is_packet_device.
+ * copied into @out_identify.
*
* This routine is based on code from libatasmart, LGPL v2.1.
*
* non-zero with errno set.
*/
static int disk_identify(int fd,
- uint8_t out_identify[512],
- int *out_is_packet_device) {
- int ret;
+ uint8_t out_identify[512]) {
uint8_t inquiry_buf[36];
- int peripheral_device_type;
- int all_nul_bytes;
- int n;
- int is_packet_device = 0;
+ int peripheral_device_type, r;
/* init results */
memzero(out_identify, 512);
* the original bug-fix and see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=556635
* for the original bug-report.)
*/
- ret = disk_scsi_inquiry_command (fd, inquiry_buf, sizeof (inquiry_buf));
- if (ret != 0)
- goto out;
+ r = disk_scsi_inquiry_command(fd, inquiry_buf, sizeof inquiry_buf);
+ if (r < 0)
+ return r;
/* SPC-4, section 6.4.2: Standard INQUIRY data */
peripheral_device_type = inquiry_buf[0] & 0x1f;
- if (peripheral_device_type == 0x05)
- {
- is_packet_device = 1;
- ret = disk_identify_packet_device_command(fd, out_identify, 512);
- goto check_nul_bytes;
- }
- if (!IN_SET(peripheral_device_type, 0x00, 0x14)) {
- ret = -1;
- errno = EIO;
- goto out;
- }
+ if (peripheral_device_type == 0x05) {
+ r = disk_identify_packet_device_command(fd, out_identify, 512);
+ if (r < 0)
+ return r;
- /* OK, now issue the IDENTIFY DEVICE command */
- ret = disk_identify_command(fd, out_identify, 512);
- if (ret != 0)
- goto out;
+ } else {
+ if (!IN_SET(peripheral_device_type, 0x00, 0x14))
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Unsupported device type.");
+
+ /* OK, now issue the IDENTIFY DEVICE command */
+ r = disk_identify_command(fd, out_identify, 512);
+ if (r < 0)
+ return r;
+ }
- check_nul_bytes:
/* Check if IDENTIFY data is all NUL bytes - if so, bail */
- all_nul_bytes = 1;
- for (n = 0; n < 512; n++) {
+ bool all_nul_bytes = true;
+ for (size_t n = 0; n < 512; n++)
if (out_identify[n] != '\0') {
- all_nul_bytes = 0;
+ all_nul_bytes = false;
break;
}
- }
- if (all_nul_bytes) {
- ret = -1;
- errno = EIO;
- goto out;
- }
+ if (all_nul_bytes)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "IDENTIFY data is all zeroes.");
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ static const struct option options[] = {
+ { "export", no_argument, NULL, 'x' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ {}
+ };
+ int c;
+
+ while ((c = getopt_long(argc, argv, "xh", options, NULL)) >= 0)
+ switch (c) {
+ case 'x':
+ arg_export = true;
+ break;
+ case 'h':
+ printf("%s [OPTIONS...] DEVICE\n\n"
+ " -x --export Print values as environment keys\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n",
+ program_invocation_short_name);
+ return 0;
+ case 'v':
+ return version();
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached();
+ }
-out:
- if (out_is_packet_device)
- *out_is_packet_device = is_packet_device;
- return ret;
+ if (!argv[optind])
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "DEVICE argument missing.");
+
+ arg_device = argv[optind];
+ return 1;
}
-int main(int argc, char *argv[]) {
+static int run(int argc, char *argv[]) {
struct hd_driveid id;
union {
uint8_t byte[512];
uint16_t wyde[256];
} identify;
- char model[41];
- char model_enc[256];
- char serial[21];
- char revision[9];
- const char *node = NULL;
- int export = 0;
+ char model[41], model_enc[256], serial[21], revision[9];
_cleanup_close_ int fd = -EBADF;
uint16_t word;
- int is_packet_device = 0;
- static const struct option options[] = {
- { "export", no_argument, NULL, 'x' },
- { "help", no_argument, NULL, 'h' },
- {}
- };
+ int r;
log_set_target(LOG_TARGET_AUTO);
udev_parse_config();
log_parse_environment();
log_open();
- for (;;) {
- int option;
-
- option = getopt_long(argc, argv, "xh", options, NULL);
- if (option == -1)
- break;
-
- switch (option) {
- case 'x':
- export = 1;
- break;
- case 'h':
- printf("Usage: %s [--export] [--help] <device>\n"
- " -x,--export print values as environment keys\n"
- " -h,--help print this help text\n\n",
- program_invocation_short_name);
- return 0;
- }
- }
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
- node = argv[optind];
- if (!node) {
- log_error("no node specified");
- return 1;
- }
+ fd = open(ASSERT_PTR(arg_device), O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return log_error_errno(errno, "Cannot open %s: %m", arg_device);
- fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
- if (fd < 0) {
- log_error("unable to open '%s'", node);
- return 1;
- }
-
- if (disk_identify(fd, identify.byte, &is_packet_device) == 0) {
+ if (disk_identify(fd, identify.byte) >= 0) {
/*
* fix up only the fields from the IDENTIFY data that we are going to
* use and copy it into the hd_driveid struct for convenience
memcpy(&id, identify.byte, sizeof id);
} else {
/* If this fails, then try HDIO_GET_IDENTITY */
- if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) {
- log_debug_errno(errno, "HDIO_GET_IDENTITY failed for '%s': %m", node);
- return 2;
- }
+ if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0)
+ return log_debug_errno(errno, "%s: HDIO_GET_IDENTITY failed: %m", arg_device);
}
memcpy(model, id.model, 40);
udev_replace_whitespace((char *) id.fw_rev, revision, 8);
udev_replace_chars(revision, NULL);
- if (export) {
+ if (arg_export) {
/* Set this to convey the disk speaks the ATA protocol */
printf("ID_ATA=1\n");
return 0;
}
+
+DEFINE_MAIN_FUNCTION(run);
#include <sys/ioctl.h>
#include <unistd.h>
+#include "build.h"
#include "fd-util.h"
#include "main-func.h"
#include "memory-util.h"
}
static int help(void) {
- printf("Usage: %s [options] <device>\n"
- " -l --lock-media lock the media (to enable eject request events)\n"
- " -u --unlock-media unlock the media\n"
- " -e --eject-media eject the media\n"
- " -d --debug print debug messages to stderr\n"
- " -h --help print this help text\n"
- "\n",
+ printf("%s [OPTIONS...] DEVICE\n\n"
+ " -l --lock-media Lock the media (to enable eject request events)\n"
+ " -u --unlock-media Unlock the media\n"
+ " -e --eject-media Eject the media\n"
+ " -d --debug Print debug messages to stderr\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n",
program_invocation_short_name);
return 0;
{ "eject-media", no_argument, NULL, 'e' },
{ "debug", no_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
{}
};
int c;
break;
case 'h':
return help();
+ case 'v':
+ return version();
+ case '?':
+ return -EINVAL;
default:
assert_not_reached();
}
#include <getopt.h>
#include "alloc-util.h"
+#include "build.h"
#include "fileio.h"
#include "main-func.h"
#include "string-util.h"
#include "udev-util.h"
#include "unaligned.h"
-#include "version.h"
#define SUPPORTED_SMBIOS_VER 0x030300
}
static int help(void) {
- printf("Usage: %s [options]\n"
- " -F,--from-dump FILE read DMI information from a binary file\n"
- " -h,--help print this help text\n\n",
+ printf("%s [OPTIONS...]\n\n"
+ " -F --from-dump FILE Read DMI information from a binary file\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n",
program_invocation_short_name);
return 0;
}
{ "from-dump", required_argument, NULL, 'F' },
{ "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
{}
};
int c;
arg_source_file = optarg;
break;
case 'V':
- printf("%s\n", GIT_VERSION);
- return 0;
+ return version();
case 'h':
return help();
case '?':
return -EINVAL;
+ case 'v':
+ return version();
default:
assert_not_reached();
}
#include <errno.h>
#include <fcntl.h>
+#include <getopt.h>
#include <linux/hid.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
+#include "build.h"
#include "device-private.h"
#include "device-util.h"
#include "fd-util.h"
#include "string-util.h"
#include "udev-util.h"
+static const char *arg_device = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ {}
+ };
+ int c;
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+ case 'h':
+ printf("%s [OPTIONS...] SYSFS_PATH\n\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n",
+ program_invocation_short_name);
+ return 0;
+ case 'v':
+ return version();
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached();
+ }
+
+ if (argc > 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument.");
+
+ arg_device = argv[optind];
+ return 1;
+}
+
static int run(int argc, char **argv) {
_cleanup_(sd_device_unrefp) struct sd_device *device = NULL;
_cleanup_free_ char *desc_path = NULL;
_cleanup_close_ int fd = -EBADF;
-
struct sd_device *hid_device;
const char *sys_path;
uint8_t desc[HID_MAX_DESCRIPTOR_SIZE];
ssize_t desc_len;
-
int r;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
- if (argc > 2)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name);
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
- if (argc == 1) {
- r = device_new_from_strv(&device, environ);
+ if (arg_device) {
+ r = sd_device_new_from_syspath(&device, arg_device);
if (r < 0)
- return log_error_errno(r, "Failed to get current device from environment: %m");
+ return log_error_errno(r, "Failed to get device from syspath %s: %m", arg_device);
} else {
- r = sd_device_new_from_syspath(&device, argv[1]);
+ r = device_new_from_strv(&device, environ);
if (r < 0)
- return log_error_errno(r, "Failed to get device from syspath: %m");
+ return log_error_errno(r, "Failed to get current device from environment: %m");
}
r = sd_device_get_parent(device, &hid_device);
fflush(f);
assert_se(rules = udev_rules_new(RESOLVE_NAME_EARLY));
- r = udev_rules_parse_file(rules, filename, NULL);
+ r = udev_rules_parse_file(rules, filename, /* extra_checks = */ false, NULL);
log_info_errno(r, "Parsing %s: %m", filename);
assert_se(r >= 0 || /* OK */
r == -ENOBUFS); /* line length exceeded */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "build.h"
+#include "cgroup-util.h"
+#include "conf-parser.h"
+#include "devnum-util.h"
+#include "device-util.h"
+#include "main-func.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "verbs.h"
+
+static char *arg_target_solution = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_target_solution, freep);
+
+static int parse_config(void) {
+ static const ConfigTableItem items[] = {
+ { "IOCost", "TargetSolution", config_parse_string, 0, &arg_target_solution },
+ };
+ return config_parse(
+ NULL,
+ "/etc/udev/iocost.conf",
+ NULL,
+ "IOCost\0",
+ config_item_table_lookup,
+ items,
+ CONFIG_PARSE_WARN,
+ NULL,
+ NULL);
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...]\n\n"
+ "Set up iocost model and qos solutions for block devices\n"
+ "\nCommands:\n"
+ " apply <path> [SOLUTION] Apply solution for the device if\n"
+ " found, do nothing otherwise\n"
+ " query <path> Query the known solution for\n"
+ " the device\n"
+ "\nOptions:\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n",
+ program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 1);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
+static int get_known_solutions(sd_device *device, char ***ret_solutions) {
+ _cleanup_free_ char **s = NULL;
+ const char *value;
+ int r;
+
+ assert(ret_solutions);
+
+ r = sd_device_get_property_value(device, "IOCOST_SOLUTIONS", &value);
+ if (r < 0)
+ return r;
+
+ s = strv_split(value, WHITESPACE);
+ if (!s)
+ return -ENOMEM;
+
+ *ret_solutions = TAKE_PTR(s);
+
+ return 0;
+}
+
+static int choose_solution(char **solutions, const char **ret_name) {
+ assert(ret_name);
+
+ if (strv_isempty(solutions))
+ return log_error_errno(
+ SYNTHETIC_ERRNO(EINVAL), "IOCOST_SOLUTIONS exists in hwdb but is empty.");
+
+ if (arg_target_solution && strv_find(solutions, arg_target_solution)) {
+ *ret_name = arg_target_solution;
+ log_debug("Selected solution based on target solution: %s", *ret_name);
+ } else {
+ *ret_name = solutions[0];
+ log_debug("Selected first available solution: %s", *ret_name);
+ }
+
+ return 0;
+}
+
+static int query_named_solution(
+ sd_device *device,
+ const char *name,
+ const char **ret_model,
+ const char **ret_qos) {
+
+ _cleanup_strv_free_ char **solutions = NULL;
+ _cleanup_free_ char *upper_name = NULL, *qos_key = NULL, *model_key = NULL;
+ const char *qos = NULL, *model = NULL;
+ int r;
+
+ assert(ret_qos);
+ assert(ret_model);
+
+ /* If NULL is passed we query the default solution, which is the first one listed
+ * in the IOCOST_SOLUTIONS key or the one specified by the TargetSolution setting.
+ */
+ if (!name) {
+ r = get_known_solutions(device, &solutions);
+ if (r == -ENOENT)
+ return log_device_debug_errno(device, r, "No entry found for device, skipping iocost logic.");
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to query solutions from device: %m");
+
+ r = choose_solution(solutions, &name);
+ if (r < 0)
+ return r;
+ }
+
+ upper_name = strdup(name);
+ if (!upper_name)
+ return log_oom();
+
+ ascii_strupper(upper_name);
+ string_replace_char(upper_name, '-', '_');
+
+ qos_key = strjoin("IOCOST_QOS_", upper_name);
+ if (!qos_key)
+ return log_oom();
+
+ model_key = strjoin("IOCOST_MODEL_", upper_name);
+ if (!model_key)
+ return log_oom();
+
+ r = sd_device_get_property_value(device, qos_key, &qos);
+ if (r == -ENOENT)
+ return log_device_debug_errno(device, r, "No value found for key %s, skipping iocost logic.", qos_key);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to obtain model for iocost solution from device: %m");
+
+ r = sd_device_get_property_value(device, model_key, &model);
+ if (r == -ENOENT)
+ return log_device_debug_errno(device, r, "No value found for key %s, skipping iocost logic.", model_key);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to obtain model for iocost solution from device: %m");
+
+ *ret_qos = qos;
+ *ret_model = model;
+
+ return 0;
+}
+
+static int apply_solution_for_path(const char *path, const char *name) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ _cleanup_free_ char *qos = NULL, *model = NULL;
+ const char *qos_params = NULL, *model_params = NULL;
+ dev_t devnum;
+ int r;
+
+ r = sd_device_new_from_path(&device, path);
+ if (r < 0)
+ return log_error_errno(r, "Error looking up device: %m");
+
+ r = query_named_solution(device, name, &model_params, &qos_params);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = sd_device_get_devnum(device, &devnum);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Error getting devnum: %m");
+
+ if (asprintf(&qos, DEVNUM_FORMAT_STR " enable=1 ctrl=user %s", DEVNUM_FORMAT_VAL(devnum), qos_params) < 0)
+ return log_oom();
+
+ if (asprintf(&model, DEVNUM_FORMAT_STR " model=linear ctrl=user %s", DEVNUM_FORMAT_VAL(devnum), model_params) < 0)
+ return log_oom();
+
+ log_debug("Applying iocost parameters to %s using solution '%s'\n"
+ "\tio.cost.qos: %s\n"
+ "\tio.cost.model: %s\n", path, name ?: "default", qos, model);
+
+ r = cg_set_attribute("io", NULL, "io.cost.qos", qos);
+ if (r < 0) {
+ log_device_full_errno(device, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to set io.cost.qos: %m");
+ return r == -ENOENT ? 0 : r;
+ }
+
+ r = cg_set_attribute("io", NULL, "io.cost.model", model);
+ if (r < 0) {
+ log_device_full_errno(device, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to set io.cost.model: %m");
+ return r == -ENOENT ? 0 : r;
+ }
+
+ return 0;
+}
+
+static int query_solutions_for_path(const char *path) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ _cleanup_strv_free_ char **solutions = NULL;
+ const char *default_solution = NULL;
+ const char *model_name = NULL;
+ int r;
+
+ r = sd_device_new_from_path(&device, path);
+ if (r < 0)
+ return log_error_errno(r, "Error looking up device: %m");
+
+ r = sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &model_name);
+ if (r == -ENOENT) {
+ log_device_debug(device, "Missing ID_MODEL_FROM_DATABASE property, trying ID_MODEL");
+ r = sd_device_get_property_value(device, "ID_MODEL", &model_name);
+ if (r == -ENOENT) {
+ log_device_info(device, "Device model not found");
+ return 0;
+ }
+ }
+ if (r < 0)
+ return log_device_error_errno(device, r, "Model name for device %s is unknown", path);
+
+ r = get_known_solutions(device, &solutions);
+ if (r == -ENOENT) {
+ log_device_info(device, "Attribute IOCOST_SOLUTIONS missing, model not found in hwdb.");
+ return 0;
+ }
+ if (r < 0)
+ return log_device_error_errno(device, r, "Couldn't access IOCOST_SOLUTIONS for device %s, model name %s on hwdb: %m\n", path, model_name);
+
+ r = choose_solution(solutions, &default_solution);
+ if (r < 0)
+ return r;
+
+ log_info("Known solutions for %s model name: \"%s\"\n"
+ "Preferred solution: %s\n"
+ "Solution that would be applied: %s",
+ path, model_name,
+ arg_target_solution, default_solution);
+
+ STRV_FOREACH(s, solutions) {
+ const char *model = NULL, *qos = NULL;
+
+ r = query_named_solution(device, *s, &model, &qos);
+ if (r < 0 || !model || !qos)
+ continue;
+
+ log_info("%s: io.cost.qos: %s\n"
+ "%s: io.cost.model: %s", *s, qos, *s, model);
+ }
+
+ return 0;
+}
+
+static int verb_query(int argc, char *argv[], void *userdata) {
+ return query_solutions_for_path(ASSERT_PTR(argv[1]));
+}
+
+static int verb_apply(int argc, char *argv[], void *userdata) {
+ return apply_solution_for_path(
+ ASSERT_PTR(argv[1]),
+ argc > 2 ? ASSERT_PTR(argv[2]) : NULL);
+}
+
+static int iocost_main(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "query", 2, 2, 0, verb_query },
+ { "apply", 2, 3, 0, verb_apply },
+ {},
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ (void) parse_config();
+
+ if (!arg_target_solution) {
+ arg_target_solution = strdup("naive");
+ if (!arg_target_solution)
+ return log_oom();
+ }
+
+ log_debug("Target solution: %s.", arg_target_solution);
+
+ return iocost_main(argc, argv);
+}
+
+DEFINE_MAIN_FUNCTION(run);
--- /dev/null
+# 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.
+#
+# Entries in this file show the compile time defaults. Local configuration
+# should be created by either modifying this file. Defaults can be restored by
+# simply deleting this file.
+#
+# Use 'systemd-analyze cat-config udev/iocost.conf' to display the full config.
+#
+# See iocost.conf(5) for details.
+
+[IOCost]
+#TargetSolution=naive
['cdrom_id/cdrom_id.c'],
['fido_id/fido_id.c',
'fido_id/fido_id_desc.c'],
+ ['iocost/iocost.c'],
['scsi_id/scsi_id.c',
'scsi_id/scsi_serial.c'],
['v4l_id/v4l_id.c'],
install_dir : udevlibexecdir)
udev_prog_paths += {name : exe}
+ public_programs += exe
endforeach
if install_sysconfdir_samples
install_data('udev.conf',
install_dir : sysconfdir / 'udev')
+ install_data('iocost/iocost.conf',
+ install_dir : sysconfdir / 'udev')
endif
custom_target(
#include <errno.h>
#include <fcntl.h>
+#include <getopt.h>
#include <mtd/mtd-user.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "alloc-util.h"
+#include "build.h"
#include "fd-util.h"
+#include "main-func.h"
#include "mtd_probe.h"
-int main(int argc, char** argv) {
+static const char *arg_device = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ {}
+ };
+ int c;
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+ case 'h':
+ printf("%s /dev/mtd[n]\n\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n",
+ program_invocation_short_name);
+ return 0;
+ case 'v':
+ return version();
+ case '?':
+ return -EINVAL;
+ default:
+ assert_not_reached();
+ }
+
+ if (argc > 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument.");
+
+ arg_device = argv[optind];
+ return 1;
+}
+
+static int run(int argc, char** argv) {
_cleanup_close_ int mtd_fd = -EBADF;
mtd_info_t mtd_info;
+ int r;
- if (argc != 2) {
- printf("usage: mtd_probe /dev/mtd[n]\n");
- return EXIT_FAILURE;
- }
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
mtd_fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (mtd_fd < 0) {
- log_error_errno(errno, "Failed to open: %m");
- return EXIT_FAILURE;
- }
+ if (mtd_fd < 0)
+ return log_error_errno(errno, "Failed to open: %m");
- if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) < 0) {
- log_error_errno(errno, "Failed to issue MEMGETINFO ioctl: %m");
- return EXIT_FAILURE;
- }
+ if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) < 0)
+ return log_error_errno(errno, "MEMGETINFO ioctl failed: %m");
- if (probe_smart_media(mtd_fd, &mtd_info) < 0)
- return EXIT_FAILURE;
-
- return EXIT_SUCCESS;
+ return probe_smart_media(mtd_fd, &mtd_info);
}
+
+DEFINE_MAIN_FUNCTION(run);
#include <unistd.h>
#include "alloc-util.h"
+#include "build.h"
#include "device-nodes.h"
#include "extract-word.h"
#include "fd-util.h"
#include "strv.h"
#include "strxcpyx.h"
#include "udev-util.h"
-#include "version.h"
static const struct option options[] = {
{ "device", required_argument, NULL, 'd' },
{ "config", required_argument, NULL, 'f' },
{ "page", required_argument, NULL, 'p' },
- { "blacklisted", no_argument, NULL, 'b' },
- { "whitelisted", no_argument, NULL, 'g' },
+ { "denylisted", no_argument, NULL, 'b' },
+ { "allowlisted", no_argument, NULL, 'g' },
+ { "blacklisted", no_argument, NULL, 'b' }, /* backward compat */
+ { "whitelisted", no_argument, NULL, 'g' }, /* backward compat */
{ "replace-whitespace", no_argument, NULL, 'u' },
{ "sg-version", required_argument, NULL, 's' },
{ "verbose", no_argument, NULL, 'v' },
" -f --config= Location of config file\n"
" -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n"
" -s --sg-version=3|4 Use SGv3 or SGv4\n"
- " -b --blacklisted Treat device as blacklisted\n"
- " -g --whitelisted Treat device as whitelisted\n"
+ " -b --denylisted Treat device as denylisted\n"
+ " -g --allowlisted Treat device as allowlisted\n"
" -u --replace-whitespace Replace all whitespace by underscores\n"
" -v --verbose Verbose logging\n"
" -x --export Print values as environment keys\n",
break;
case 'V':
- printf("%s\n", GIT_VERSION);
+ version();
exit(EXIT_SUCCESS);
case 'x':
* Figure out and print the sense key, asc and ascq.
*
* If you want to suppress these for a particular drive model, add
- * a black list entry in the scsi_id config file.
+ * a deny list entry in the scsi_id config file.
*
* XXX We probably need to: lookup the sense/asc/ascq in a retry
* table, and if found return 1 (after dumping the sense, asc, and
#include <linux/pci_regs.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "device-private.h"
#include "device-util.h"
#include "dirent-util.h"
.msg_control = &control,
.msg_controllen = sizeof(control),
};
- struct cmsghdr *cmsg;
struct ucred *cred;
ssize_t size;
cmsg_close_all(&smsg);
- cmsg = CMSG_FIRSTHDR(&smsg);
-
- if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
+ cred = CMSG_FIND_DATA(&smsg, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
+ if (!cred) {
log_error("No sender credentials received, ignoring message");
return 0;
}
- cred = (struct ucred *) CMSG_DATA(cmsg);
-
if (cred->uid != 0) {
log_error("Invalid sender uid "UID_FMT", ignoring message", cred->uid);
return 0;
#include "user-util.h"
#include "virt.h"
-#define RULES_DIRS (const char* const*) CONF_PATHS_STRV("udev/rules.d")
+#define RULES_DIRS ((const char* const*) CONF_PATHS_STRV("udev/rules.d"))
typedef enum {
OP_MATCH, /* == */
return _OP_TYPE_INVALID;
}
+static void check_token_delimiters(UdevRuleLine *rule_line, const char *line) {
+ assert(rule_line);
+
+ size_t n_comma = 0;
+ bool ws_before_comma = false, ws_after_comma = false;
+ const char *p;
+
+ for (p = line; !isempty(p); ++p) {
+ if (*p == ',')
+ ++n_comma;
+ else if (strchr(WHITESPACE, *p)) {
+ if (n_comma > 0)
+ ws_after_comma = true;
+ else
+ ws_before_comma = true;
+ } else
+ break;
+ }
+
+ if (line == rule_line->line) {
+ /* this is the first token of the rule */
+ if (n_comma > 0)
+ log_line_warning(rule_line, "Stray leading comma.");
+ } else if (isempty(p)) {
+ /* there are no more tokens in the rule */
+ if (n_comma > 0)
+ log_line_warning(rule_line, "Stray trailing comma.");
+ } else {
+ /* single comma is expected */
+ if (n_comma == 0)
+ log_line_warning(rule_line, "A comma between tokens is expected.");
+ else if (n_comma > 1)
+ log_line_warning(rule_line, "More than one comma between tokens.");
+
+ /* whitespace after comma is expected */
+ if (n_comma > 0) {
+ if (ws_before_comma)
+ log_line_warning(rule_line, "Stray whitespace before comma.");
+ if (!ws_after_comma)
+ log_line_warning(rule_line, "Whitespace after comma is expected.");
+ } else if (!ws_before_comma && !ws_after_comma)
+ log_line_warning(rule_line, "Whitespace between tokens is expected.");
+ }
+}
+
static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) {
char *key_begin, *key_end, *attr, *tmp;
UdevRuleOperatorType op;
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);
}
}
-static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr) {
+static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr, bool extra_checks) {
_cleanup_(udev_rule_line_freep) UdevRuleLine *rule_line = NULL;
_cleanup_free_ char *line = NULL;
char *p;
char *key, *attr, *value;
UdevRuleOperatorType op;
+ if (extra_checks)
+ check_token_delimiters(rule_line, p);
+
r = parse_line(&p, &key, &attr, &op, &value);
if (r < 0)
return log_line_error_errno(rule_line, r, "Invalid key/value pair, ignoring.");
}
if (rule_line->type == 0) {
- log_line_warning(rule_line, "The line takes no effect, ignoring.");
+ log_line_warning(rule_line, "The line has no effect, ignoring.");
return 0;
}
+ if (extra_checks)
+ check_tokens_order(rule_line);
+
sort_tokens(rule_line);
TAKE_PTR(rule_line);
return 0;
line->goto_label = NULL;
if ((line->type & ~(LINE_HAS_LABEL|LINE_IS_REFERENCED)) == 0) {
- log_line_notice(line, "The line takes no effect any more, dropping");
+ log_line_notice(line, "The line has no effect any more, dropping.");
/* LINE_IS_REFERENCED implies LINE_HAS_LABEL */
if (line->type & LINE_HAS_LABEL)
udev_rule_line_clear_tokens(line);
}
}
-int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile **ret) {
- _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL;
- _cleanup_free_ char *continuation = NULL, *name = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- bool ignore_line = false;
- unsigned line_nr = 0;
- struct stat st;
- int r;
-
- assert(rules);
- assert(filename);
-
- f = fopen(filename, "re");
- if (!f)
- 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 (null_or_empty(&st)) {
- log_debug("Skipping empty file: %s", filename);
- if (ret)
- *ret = NULL;
- return 0;
- }
-
- r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
- if (r < 0)
- return log_warning_errno(errno, "Failed to save stat for %s, ignoring: %m", filename);
-
- (void) fd_warn_permissions(filename, fileno(f));
-
- log_debug("Reading rules file: %s", filename);
-
- name = strdup(filename);
- if (!name)
- return log_oom();
-
- rule_file = new(UdevRuleFile, 1);
- if (!rule_file)
- return log_oom();
-
- *rule_file = (UdevRuleFile) {
- .filename = TAKE_PTR(name),
- .rules = rules,
- };
-
- LIST_APPEND(rule_files, rules->rule_files, rule_file);
-
- for (;;) {
- _cleanup_free_ char *buf = NULL;
- size_t len;
- char *line;
-
- r = read_line(f, UDEV_LINE_SIZE, &buf);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
- line_nr++;
- line = skip_leading_chars(buf, NULL);
-
- /* Lines beginning with '#' are ignored regardless of line continuation. */
- if (line[0] == '#')
- continue;
-
- len = strlen(line);
-
- if (continuation && !ignore_line) {
- if (strlen(continuation) + len >= UDEV_LINE_SIZE)
- ignore_line = true;
-
- if (!strextend(&continuation, line))
- return log_oom();
-
- if (!ignore_line) {
- line = continuation;
- len = strlen(line);
- }
- }
-
- if (len > 0 && line[len - 1] == '\\') {
- if (ignore_line)
- continue;
-
- line[len - 1] = '\0';
- if (!continuation) {
- continuation = strdup(line);
- if (!continuation)
- return log_oom();
- }
-
- continue;
- }
-
- if (ignore_line)
- log_file_error(rule_file, line_nr, "Line is too long, ignored");
- else if (len > 0)
- (void) rule_add_line(rule_file, line, line_nr);
-
- continuation = mfree(continuation);
- ignore_line = false;
- }
-
- if (continuation)
- log_file_error(rule_file, line_nr,
- "Unexpected EOF after line continuation, line ignored");
-
- rule_resolve_goto(rule_file);
-
- if (ret)
- *ret = rule_file;
-
- TAKE_PTR(rule_file);
- return 1;
-}
-
static bool token_data_is_string(UdevRuleTokenType type) {
return IN_SET(type, TK_M_ENV,
TK_M_CONST,
a->op == b->op &&
a->op == OP_MATCH &&
a->match_type == b->match_type &&
- a->match_type == MATCH_TYPE_PLAIN &&
a->attr_subst_type == b->attr_subst_type &&
a->attr_match_remove_trailing_whitespace == b->attr_match_remove_trailing_whitespace &&
token_type_and_data_eq(a, b)))
return false;
- NULSTR_FOREACH(i, a->value)
- if (nulstr_contains(b->value, i))
- return false;
+ if (a->match_type == MATCH_TYPE_PLAIN) {
+ NULSTR_FOREACH(i, a->value)
+ if (nulstr_contains(b->value, i))
+ return false;
+ return true;
+ }
- return true;
+ if (a->match_type == MATCH_TYPE_GLOB) {
+ NULSTR_FOREACH(i, a->value) {
+ size_t i_n = strcspn(i, GLOB_CHARS);
+ if (i_n == 0)
+ return false;
+ NULSTR_FOREACH(j, b->value) {
+ size_t j_n = strcspn(j, GLOB_CHARS);
+ if (j_n == 0 || strneq(i, j, MIN(i_n, j_n)))
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+ return false;
}
static void udev_check_unused_labels(UdevRuleLine *line) {
}
if (new_conflicts) {
conflicts = new_conflicts;
- log_line_error(line, "conflicting match expressions, the line takes no effect");
+ log_line_error(line, "conflicting match expressions, the line has no effect");
}
if (conflicts && duplicates)
return;
udev_check_conflicts_duplicates(line);
}
+int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) {
+ _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL;
+ _cleanup_free_ char *continuation = NULL, *name = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ bool ignore_line = false;
+ unsigned line_nr = 0;
+ struct stat st;
+ int r;
+
+ assert(rules);
+ assert(filename);
+
+ f = fopen(filename, "re");
+ 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 (null_or_empty(&st)) {
+ log_debug("Skipping empty file: %s", filename);
+ if (ret)
+ *ret = NULL;
+ return 0;
+ }
+
+ r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to save stat for %s, ignoring: %m", filename);
+
+ (void) fd_warn_permissions(filename, fileno(f));
+
+ log_debug("Reading rules file: %s", filename);
+
+ name = strdup(filename);
+ if (!name)
+ return log_oom();
+
+ rule_file = new(UdevRuleFile, 1);
+ if (!rule_file)
+ return log_oom();
+
+ *rule_file = (UdevRuleFile) {
+ .filename = TAKE_PTR(name),
+ .rules = rules,
+ };
+
+ LIST_APPEND(rule_files, rules->rule_files, rule_file);
+
+ for (;;) {
+ _cleanup_free_ char *buf = NULL;
+ size_t len;
+ char *line;
+
+ r = read_line(f, UDEV_LINE_SIZE, &buf);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ line_nr++;
+ line = skip_leading_chars(buf, NULL);
+
+ /* Lines beginning with '#' are ignored regardless of line continuation. */
+ if (line[0] == '#')
+ continue;
+
+ len = strlen(line);
+
+ if (continuation && !ignore_line) {
+ if (strlen(continuation) + len >= UDEV_LINE_SIZE)
+ ignore_line = true;
+
+ if (!strextend(&continuation, line))
+ return log_oom();
+
+ if (!ignore_line) {
+ line = continuation;
+ len = strlen(line);
+ }
+ }
+
+ if (len > 0 && line[len - 1] == '\\') {
+ if (ignore_line)
+ continue;
+
+ line[len - 1] = '\0';
+ if (!continuation) {
+ continuation = strdup(line);
+ if (!continuation)
+ return log_oom();
+ }
+
+ continue;
+ }
+
+ if (ignore_line)
+ log_file_error(rule_file, line_nr, "Line is too long, ignored");
+ else if (len > 0)
+ (void) rule_add_line(rule_file, line, line_nr, extra_checks);
+
+ continuation = mfree(continuation);
+ ignore_line = false;
+ }
+
+ if (continuation)
+ log_file_error(rule_file, line_nr,
+ "Unexpected EOF after line continuation, line ignored");
+
+ rule_resolve_goto(rule_file);
+
+ if (extra_checks)
+ LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
+ udev_check_rule_line(line);
+
+ if (ret)
+ *ret = rule_file;
+
+ TAKE_PTR(rule_file);
+ return 1;
+}
+
unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file) {
assert(rule_file);
- LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
- udev_check_rule_line(line);
-
return rule_file->issues;
}
return log_debug_errno(r, "Failed to enumerate rules files: %m");
STRV_FOREACH(f, files) {
- r = udev_rules_parse_file(rules, *f, NULL);
+ r = udev_rules_parse_file(rules, *f, /* extra_checks = */ false, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read rules file %s, ignoring: %m", *f);
}
if (token->op == OP_ASSIGN)
device_cleanup_tags(dev);
- if (buf[strspn(buf, ALPHANUMERICAL "-_")] != '\0') {
- log_event_error(dev, token, "Invalid tag name '%s', ignoring", buf);
- break;
- }
if (token->op == OP_REMOVE)
device_remove_tag(dev, buf);
else {
r = device_add_tag(dev, buf, true);
+ if (r == -ENOMEM)
+ return log_oom();
if (r < 0)
- return log_event_error_errno(dev, token, r, "Failed to add tag '%s': %m", buf);
+ log_event_warning_errno(dev, token, r, "Failed to add tag '%s', ignoring: %m", buf);
}
break;
}
break;
}
case TK_A_DEVLINK: {
- char buf[UDEV_PATH_SIZE], *p;
+ char buf[UDEV_PATH_SIZE];
bool truncated;
size_t count;
if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL))
device_cleanup_devlinks(dev);
- /* allow multiple symlinks separated by spaces */
- (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE, &truncated);
+ (void) udev_event_apply_format(event, token->value, buf, sizeof(buf),
+ /* replace_whitespace = */ event->esc != ESCAPE_NONE, &truncated);
if (truncated) {
log_event_truncated(dev, token, "symbolic link path", token->value, "SYMLINK", /* is_match = */ false);
break;
}
+ /* By default or string_escape=none, allow multiple symlinks separated by spaces. */
if (event->esc == ESCAPE_UNSET)
- count = udev_replace_chars(buf, "/ ");
+ count = udev_replace_chars(buf, /* allow = */ "/ ");
else if (event->esc == ESCAPE_REPLACE)
- count = udev_replace_chars(buf, "/");
+ count = udev_replace_chars(buf, /* allow = */ "/");
else
count = 0;
if (count > 0)
"Replaced %zu character(s) from result of SYMLINK=\"%s\"",
count, token->value);
- p = skip_leading_chars(buf, NULL);
- while (!isempty(p)) {
- char filename[UDEV_PATH_SIZE], *next;
+ for (const char *p = buf;;) {
+ _cleanup_free_ char *path = NULL;
- next = strchr(p, ' ');
- if (next) {
- *next++ = '\0';
- next = skip_leading_chars(next, NULL);
+ r = extract_first_word(&p, &path, NULL, EXTRACT_RETAIN_ESCAPE);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_warning_errno(r, "Failed to extract first path in SYMLINK=, ignoring: %m");
+ break;
}
-
- strscpyl_full(filename, sizeof(filename), &truncated, "/dev/", p, NULL);
- if (truncated)
- continue;
+ if (r == 0)
+ break;
if (token->op == OP_REMOVE) {
- device_remove_devlink(dev, filename);
- log_event_debug(dev, token, "Dropped SYMLINK '%s'", p);
+ r = device_remove_devlink(dev, path);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ log_event_warning_errno(dev, token, r, "Failed to remove devlink '%s', ignoring: %m", path);
+ else if (r > 0)
+ log_event_debug(dev, token, "Dropped SYMLINK '%s'", path);
} else {
- r = device_add_devlink(dev, filename);
+ r = device_add_devlink(dev, path);
+ if (r == -ENOMEM)
+ return log_oom();
if (r < 0)
- return log_event_error_errno(dev, token, r, "Failed to add devlink '%s': %m", filename);
-
- log_event_debug(dev, token, "Added SYMLINK '%s'", p);
+ log_event_warning_errno(dev, token, r, "Failed to add devlink '%s', ignoring: %m", path);
+ else if (r > 0)
+ log_event_debug(dev, token, "Added SYMLINK '%s'", path);
}
-
- p = next;
}
break;
}
_ESCAPE_TYPE_INVALID = -EINVAL,
} UdevRuleEscapeType;
-int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile **ret);
+int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret);
unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file);
UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing);
int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing);
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0)
switch (c) {
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);
}
}
UdevRuleFile *file;
int r;
- r = udev_rules_parse_file(rules, fname, &file);
+ r = udev_rules_parse_file(rules, fname, /* extra_checks = */ true, &file);
if (r < 0)
return log_error_errno(r, "Failed to parse rules file %s: %m", fname);
if (r == 0) /* empty file. */
#include "sd-event.h"
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "device-monitor-private.h"
#include "device-util.h"
#include "errno-util.h"
assert(argc >= 0);
assert(argv);
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0)
switch (c) {
assert(monitor);
assert(dev);
- assert_se(unsetenv("NOTIFY_SOCKET") == 0);
-
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, -1) >= 0);
/* Reset OOM score, we only protect the main daemon. */
#include <unistd.h>
#include <linux/videodev2.h>
+#include "build.h"
#include "fd-util.h"
+#include "main-func.h"
-int main(int argc, char *argv[]) {
+static const char *arg_device = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
{}
};
- _cleanup_close_ int fd = -EBADF;
- char *device;
- struct v4l2_capability v2cap;
int c;
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
-
switch (c) {
case 'h':
- printf("%s [-h,--help] <device file>\n\n"
+ printf("%s [OPTIONS...] DEVICE\n\n"
"Video4Linux device identification.\n\n"
- " -h Print this message\n",
+ " -h --help Show this help text\n"
+ " --version Show package version\n",
program_invocation_short_name);
return 0;
+ case 'v':
+ return version();
case '?':
return -EINVAL;
-
default:
assert_not_reached();
}
- device = argv[optind];
- if (!device)
- return 2;
+ if (!argv[optind])
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "DEVICE argument missing.");
+
+ arg_device = argv[optind];
+ return 1;
+}
- fd = open(device, O_RDONLY);
+static int run(int argc, char *argv[]) {
+ _cleanup_close_ int fd = -EBADF;
+ struct v4l2_capability v2cap;
+ int r;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ fd = open(arg_device, O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
- return 3;
+ return log_error_errno(errno, "Failed to open %s: %m", arg_device);
if (ioctl(fd, VIDIOC_QUERYCAP, &v2cap) == 0) {
int capabilities;
+
printf("ID_V4L_VERSION=2\n");
printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
printf("ID_V4L_CAPABILITIES=:");
+
if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS)
capabilities = v2cap.device_caps;
else
capabilities = v2cap.capabilities;
+
if ((capabilities & V4L2_CAP_VIDEO_CAPTURE) > 0 ||
(capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) > 0)
printf("capture:");
return 0;
}
+
+DEFINE_MAIN_FUNCTION(run);
import tempfile
import typing
+import pefile
__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})'
'i[3456]86' : ['ia32'],
'aarch64' : ['aa64'],
'arm[45678]*l' : ['arm'],
+ 'loongarch32' : ['loongarch32'],
+ 'loongarch64' : ['loongarch64'],
+ 'riscv32' : ['riscv32'],
'riscv64' : ['riscv64'],
}
EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), [])
return p
-def pe_next_section_offset(filename):
- import pefile
-
- pe = pefile.PE(filename, fast_load=True)
- section = pe.sections[-1]
- return pe.OPTIONAL_HEADER.ImageBase + section.VirtualAddress + section.Misc_VirtualSize
-
-
def round_up(x, blocksize=4096):
return (x + blocksize - 1) // blocksize * blocksize
# not compressed
return f.read()
+ if start.startswith(b'MZ'):
+ # not compressed aarch64 and riscv64
+ return f.read()
+
if start.startswith(b'\x1f\x8b'):
gzip = try_import('gzip')
return gzip.open(f).read()
name: str
content: pathlib.Path
tmpfile: typing.Optional[typing.IO] = None
- flags: list[str] = dataclasses.field(default_factory=lambda: ['data', 'readonly'])
- offset: typing.Optional[int] = None
measure: bool = False
@classmethod
class UKI:
executable: list[typing.Union[pathlib.Path, str]]
sections: list[Section] = dataclasses.field(default_factory=list, init=False)
- offset: typing.Optional[int] = dataclasses.field(default=None, init=False)
-
- def __post_init__(self):
- self.offset = round_up(pe_next_section_offset(self.executable))
def add_section(self, section):
- assert self.offset
- assert section.offset is None
-
if section.name in [s.name for s in self.sections]:
raise ValueError(f'Duplicate section {section.name}')
- section.offset = self.offset
- self.offset += round_up(section.size())
self.sections += [section]
return zip(a, b)
-def pe_validate(filename):
- import pefile
+class PeError(Exception):
+ pass
+
+
+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
+ oldsz = section.SizeOfRawData
+ section.PointerToRawData = round_up(oldp, pe.OPTIONAL_HEADER.FileAlignment)
+ section.SizeOfRawData = round_up(oldsz, pe.OPTIONAL_HEADER.FileAlignment)
+ padp = section.PointerToRawData - oldp
+ padsz = section.SizeOfRawData - oldsz
+
+ for later_section in pe.sections[i+1:]:
+ later_section.PointerToRawData += padp + padsz
+
+ pe.__data__ = pe.__data__[:oldp] + bytes(padp) + pe.__data__[oldp:oldp+oldsz] + bytes(padsz) + pe.__data__[oldp+oldsz:]
+
+ # We might not have any space to add new sections. Let's try our best to make some space by padding the
+ # SizeOfHeaders to a multiple of the file alignment. This is safe because the first section's data starts
+ # at a multiple of the file alignment, so all space before that is unused.
+ pe.OPTIONAL_HEADER.SizeOfHeaders = round_up(pe.OPTIONAL_HEADER.SizeOfHeaders, pe.OPTIONAL_HEADER.FileAlignment)
+ pe = pefile.PE(data=pe.write(), fast_load=True)
+
+ warnings = pe.get_warnings()
+ if warnings:
+ raise PeError(f'pefile warnings treated as errors: {warnings}')
+
+ security = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']]
+ if security.VirtualAddress != 0:
+ # We could strip the signatures, but why would anyone sign the stub?
+ raise PeError(f'Stub image is signed, refusing.')
+
+ for section in uki.sections:
+ new_section = pefile.SectionStructure(pe.__IMAGE_SECTION_HEADER_format__, pe=pe)
+ new_section.__unpack__(b'\0' * new_section.sizeof())
+
+ offset = pe.sections[-1].get_file_offset() + new_section.sizeof()
+ if offset + new_section.sizeof() > pe.OPTIONAL_HEADER.SizeOfHeaders:
+ raise PeError(f'Not enough header space to add section {section.name}.')
+
+ data = section.content.read_bytes()
+
+ new_section.set_file_offset(offset)
+ new_section.Name = section.name.encode()
+ new_section.Misc_VirtualSize = len(data)
+ # Non-stripped stubs might still have an unaligned symbol table at the end, making their size
+ # unaligned, so we make sure to explicitly pad the pointer to new sections to an aligned offset.
+ new_section.PointerToRawData = round_up(len(pe.__data__), pe.OPTIONAL_HEADER.FileAlignment)
+ new_section.SizeOfRawData = round_up(len(data), pe.OPTIONAL_HEADER.FileAlignment)
+ new_section.VirtualAddress = round_up(
+ pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize,
+ pe.OPTIONAL_HEADER.SectionAlignment,
+ )
+
+ new_section.IMAGE_SCN_MEM_READ = True
+ if section.name == '.linux':
+ # Old kernels that use EFI handover protocol will be executed inline.
+ new_section.IMAGE_SCN_CNT_CODE = True
+ else:
+ new_section.IMAGE_SCN_CNT_INITIALIZED_DATA = True
+
+ pe.__data__ = pe.__data__[:] + bytes(new_section.PointerToRawData - len(pe.__data__)) + data + bytes(new_section.SizeOfRawData - len(data))
- pe = pefile.PE(filename, fast_load=True)
+ pe.FILE_HEADER.NumberOfSections += 1
+ pe.OPTIONAL_HEADER.SizeOfInitializedData += new_section.Misc_VirtualSize
+ pe.__structures__.append(new_section)
+ pe.sections.append(new_section)
- sections = sorted(pe.sections, key=lambda s: (s.VirtualAddress, s.Misc_VirtualSize))
+ pe.OPTIONAL_HEADER.CheckSum = 0
+ pe.OPTIONAL_HEADER.SizeOfImage = round_up(
+ pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize,
+ pe.OPTIONAL_HEADER.SectionAlignment,
+ )
- for l, r in pairwise(sections):
- if l.VirtualAddress + l.Misc_VirtualSize > r.VirtualAddress + r.Misc_VirtualSize:
- raise ValueError(f'Section "{l.Name.decode()}" ({l.VirtualAddress}, {l.Misc_VirtualSize}) overlaps with section "{r.Name.decode()}" ({r.VirtualAddress}, {r.Misc_VirtualSize})')
+ pe.write(output)
def make_uki(opts):
# UKI creation
- uki.add_section(
- Section.create('.linux', linux, measure=True,
- flags=['code', 'readonly']))
+ uki.add_section(Section.create('.linux', linux, measure=True))
if opts.sb_key:
unsigned = tempfile.NamedTemporaryFile(prefix='uki')
else:
output = opts.output
- objcopy_tool = find_tool('llvm-objcopy', 'objcopy', opts=opts)
-
- cmd = [
- objcopy_tool,
- opts.stub,
- *itertools.chain.from_iterable(
- ('--add-section', f'{s.name}={s.content}',
- '--set-section-flags', f"{s.name}={','.join(s.flags)}")
- for s in uki.sections),
- output,
- ]
-
- if pathlib.Path(objcopy_tool).name != 'llvm-objcopy':
- cmd += itertools.chain.from_iterable(
- ('--change-section-vma', f'{s.name}=0x{s.offset:x}') for s in uki.sections)
-
- print('+', shell_join(cmd))
- subprocess.check_call(cmd)
-
- pe_validate(output)
+ pe_add_sections(uki, output)
# UKI signing
p.add_argument('--tools',
type=pathlib.Path,
action='append',
- help='Directories to search for tools (systemd-measure, llvm-objcopy, ...)')
+ help='Directories to search for tools (systemd-measure, ...)')
p.add_argument('--output', '-o',
type=pathlib.Path,
#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]);
return table_log_print_error(r);
}
- if (arg_legend) {
+ if (arg_legend && arg_output != OUTPUT_JSON) {
if (table_get_rows(t) > 1)
printf("\n%zu services listed.\n", table_get_rows(t) - 1);
else
log_debug("Chain invoking: %s", s);
}
+ fflush(stdout);
execv(chain_invocation[0], chain_invocation);
if (errno == ENOENT) /* Let's handle ENOENT gracefully */
log_warning_errno(errno, "Chain executable '%s' does not exist, ignoring chain invocation.", chain_invocation[0]);
arg_services = l;
}
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+ optind = 0;
+
for (;;) {
int c;
#include "terminal-util.h"
#include "virt.h"
+typedef enum VCMeta {
+ VC_KEYMAP,
+ VC_KEYMAP_TOGGLE,
+ VC_FONT,
+ VC_FONT_MAP,
+ VC_FONT_UNIMAP,
+ _VC_META_MAX,
+ _VC_META_INVALID = -EINVAL,
+} VCMeta;
+
+typedef struct Context {
+ char *config[_VC_META_MAX];
+} Context;
+
+static const char * const vc_meta_names[_VC_META_MAX] = {
+ [VC_KEYMAP] = "vconsole.keymap",
+ [VC_KEYMAP_TOGGLE] = "vconsole.keymap_toggle",
+ [VC_FONT] = "vconsole.font",
+ [VC_FONT_MAP] = "vconsole.font_map",
+ [VC_FONT_UNIMAP] = "vconsole.font_unimap",
+};
+
+/* compatibility with obsolete multiple-dot scheme */
+static const char * const vc_meta_compat_names[_VC_META_MAX] = {
+ [VC_KEYMAP_TOGGLE] = "vconsole.keymap.toggle",
+ [VC_FONT_MAP] = "vconsole.font.map",
+ [VC_FONT_UNIMAP] = "vconsole.font.unimap",
+};
+
+static const char * const vc_env_names[_VC_META_MAX] = {
+ [VC_KEYMAP] = "KEYMAP",
+ [VC_KEYMAP_TOGGLE] = "KEYMAP_TOGGLE",
+ [VC_FONT] = "FONT",
+ [VC_FONT_MAP] = "FONT_MAP",
+ [VC_FONT_UNIMAP] = "FONT_UNIMAP",
+};
+
+static void context_done(Context *c) {
+ assert(c);
+
+ for (VCMeta i = 0; i < _VC_META_MAX; i++)
+ free(c->config[i]);
+}
+
+static void context_merge_config(
+ Context *dst,
+ Context *src,
+ Context *src_compat) {
+
+ assert(dst);
+ assert(src);
+
+ for (VCMeta i = 0; i < _VC_META_MAX; i++)
+ if (src->config[i])
+ free_and_replace(dst->config[i], src->config[i]);
+ else if (src_compat && src_compat->config[i])
+ free_and_replace(dst->config[i], src_compat->config[i]);
+}
+
+static const char* context_get_config(Context *c, VCMeta meta) {
+ assert(c);
+ assert(meta >= 0 && meta < _VC_META_MAX);
+
+ if (meta == VC_KEYMAP)
+ return isempty(c->config[VC_KEYMAP]) ? SYSTEMD_DEFAULT_KEYMAP : c->config[VC_KEYMAP];
+
+ return empty_to_null(c->config[meta]);
+}
+
+static int context_read_creds(Context *c) {
+ _cleanup_(context_done) Context v = {};
+ int r;
+
+ assert(c);
+
+ r = read_credential_strings_many(
+ vc_meta_names[VC_KEYMAP], &v.config[VC_KEYMAP],
+ vc_meta_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
+ vc_meta_names[VC_FONT], &v.config[VC_FONT],
+ vc_meta_names[VC_FONT_MAP], &v.config[VC_FONT_MAP],
+ vc_meta_names[VC_FONT_UNIMAP], &v.config[VC_FONT_UNIMAP]);
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_warning_errno(r, "Failed to import credentials, ignoring: %m");
+ return r;
+ }
+
+ context_merge_config(c, &v, NULL);
+ return 0;
+}
+
+static int context_read_env(Context *c) {
+ _cleanup_(context_done) Context v = {};
+ int r;
+
+ assert(c);
+
+ r = parse_env_file(
+ NULL, "/etc/vconsole.conf",
+ vc_env_names[VC_KEYMAP], &v.config[VC_KEYMAP],
+ vc_env_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
+ vc_env_names[VC_FONT], &v.config[VC_FONT],
+ vc_env_names[VC_FONT_MAP], &v.config[VC_FONT_MAP],
+ vc_env_names[VC_FONT_UNIMAP], &v.config[VC_FONT_UNIMAP]);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /etc/vconsole.conf, ignoring: %m");
+ return r;
+ }
+
+ context_merge_config(c, &v, NULL);
+ return 0;
+}
+
+static int context_read_proc_cmdline(Context *c) {
+ _cleanup_(context_done) Context v = {}, w = {};
+ int r;
+
+ assert(c);
+
+ r = proc_cmdline_get_key_many(
+ PROC_CMDLINE_STRIP_RD_PREFIX,
+ vc_meta_names[VC_KEYMAP], &v.config[VC_KEYMAP],
+ vc_meta_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
+ vc_meta_names[VC_FONT], &v.config[VC_FONT],
+ vc_meta_names[VC_FONT_MAP], &v.config[VC_FONT_MAP],
+ vc_meta_names[VC_FONT_UNIMAP], &v.config[VC_FONT_UNIMAP],
+ vc_meta_compat_names[VC_KEYMAP_TOGGLE], &w.config[VC_KEYMAP_TOGGLE],
+ vc_meta_compat_names[VC_FONT_MAP], &w.config[VC_FONT_MAP],
+ vc_meta_compat_names[VC_FONT_UNIMAP], &w.config[VC_FONT_UNIMAP]);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
+ return r;
+ }
+
+ context_merge_config(c, &v, &w);
+ return 0;
+}
+
+static void context_load_config(Context *c) {
+ assert(c);
+
+ /* Load data from credentials (lowest priority) */
+ (void) context_read_creds(c);
+
+ /* Load data from configuration file (middle priority) */
+ (void) context_read_env(c);
+
+ /* Let the kernel command line override /etc/vconsole.conf (highest priority) */
+ (void) context_read_proc_cmdline(c);
+}
+
static int verify_vc_device(int fd) {
unsigned char data[] = {
TIOCL_GETFGCONSOLE,
return 0;
}
-static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
- const char *args[8];
+static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) {
+ const char *map, *map_toggle, *args[8];
unsigned i = 0;
pid_t pid;
int r;
+ assert(vc);
+ assert(c);
+
+ map = context_get_config(c, VC_KEYMAP);
+ map_toggle = context_get_config(c, VC_KEYMAP_TOGGLE);
+
/* An empty map means kernel map */
- if (isempty(map))
+ if (!map)
return 0;
args[i++] = KBD_LOADKEYS;
return wait_for_terminate_and_check(KBD_LOADKEYS, pid, WAIT_LOG);
}
-static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
- const char *args[9];
+static int font_load_and_wait(const char *vc, Context *c) {
+ const char *font, *map, *unimap, *args[9];
unsigned i = 0;
pid_t pid;
int r;
+ assert(vc);
+ assert(c);
+
+ font = context_get_config(c, VC_FONT);
+ map = context_get_config(c, VC_FONT_MAP);
+ unimap = context_get_config(c, VC_FONT_UNIMAP);
+
/* Any part can be set independently */
- if (isempty(font) && isempty(map) && isempty(unimap))
+ if (!font && !map && !unimap)
return 0;
args[i++] = KBD_SETFONT;
args[i++] = "-C";
args[i++] = vc;
- if (!isempty(map)) {
+ if (map) {
args[i++] = "-m";
args[i++] = map;
}
- if (!isempty(unimap)) {
+ if (unimap) {
args[i++] = "-u";
args[i++] = unimap;
}
- if (!isempty(font))
+ if (font)
args[i++] = font;
args[i++] = NULL;
}
int main(int argc, char **argv) {
- _cleanup_free_ char
- *vc = NULL,
- *vc_keymap_alloc = NULL, *vc_keymap_toggle = NULL,
- *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
+ _cleanup_(context_done) Context c = {};
+ _cleanup_free_ char *vc = NULL;
_cleanup_close_ int fd = -EBADF;
- const char *vc_keymap;
bool utf8, keyboard_ok;
unsigned idx = 0;
int r;
utf8 = is_locale_utf8();
- /* Load data from credentials (lowest priority) */
- r = read_credential_strings_many(
- "vconsole.keymap", &vc_keymap_alloc,
- "vconsole.keymap_toggle", &vc_keymap_toggle,
- "vconsole.font", &vc_font,
- "vconsole.font_map", &vc_font_map,
- "vconsole.font_unimap", &vc_font_unimap);
- if (r < 0 && r != -ENXIO)
- log_warning_errno(r, "Failed to import credentials, ignoring: %m");
-
- /* Load data from configuration file (middle priority) */
- r = parse_env_file(NULL, "/etc/vconsole.conf",
- "KEYMAP", &vc_keymap_alloc,
- "KEYMAP_TOGGLE", &vc_keymap_toggle,
- "FONT", &vc_font,
- "FONT_MAP", &vc_font_map,
- "FONT_UNIMAP", &vc_font_unimap);
- if (r < 0 && r != -ENOENT)
- log_warning_errno(r, "Failed to read /etc/vconsole.conf, ignoring: %m");
-
- /* Let the kernel command line override /etc/vconsole.conf (highest priority) */
- r = proc_cmdline_get_key_many(
- PROC_CMDLINE_STRIP_RD_PREFIX,
- "vconsole.keymap", &vc_keymap_alloc,
- "vconsole.keymap_toggle", &vc_keymap_toggle,
- "vconsole.font", &vc_font,
- "vconsole.font_map", &vc_font_map,
- "vconsole.font_unimap", &vc_font_unimap,
- /* compatibility with obsolete multiple-dot scheme */
- "vconsole.keymap.toggle", &vc_keymap_toggle,
- "vconsole.font.map", &vc_font_map,
- "vconsole.font.unimap", &vc_font_unimap);
- if (r < 0 && r != -ENOENT)
- log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
-
- vc_keymap = isempty(vc_keymap_alloc) ? SYSTEMD_DEFAULT_KEYMAP : vc_keymap_alloc;
+ context_load_config(&c);
(void) toggle_utf8_sysfs(utf8);
(void) toggle_utf8_vc(vc, fd, utf8);
- r = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap);
- keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
+ r = font_load_and_wait(vc, &c);
+ keyboard_ok = keyboard_load_and_wait(vc, &c, utf8) == 0;
if (idx > 0) {
if (r == 0)
#include "alloc-util.h"
#include "cryptsetup-util.h"
#include "fileio.h"
+#include "fstab-util.h"
#include "hexdecoct.h"
#include "log.h"
#include "main-func.h"
+#include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "string-util.h"
#include "terminal-util.h"
+static char *arg_hash = NULL;
+static bool arg_superblock = true;
+static int arg_format = 1;
+static uint64_t arg_data_block_size = 4096;
+static uint64_t arg_hash_block_size = 4096;
+static uint64_t arg_data_blocks = 0;
+static uint64_t arg_hash_offset = 0;
+static void *arg_salt = NULL;
+static uint64_t arg_salt_size = 32;
+static char *arg_uuid = NULL;
static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY;
+static char *arg_fec_what = NULL;
+static uint64_t arg_fec_offset = 0;
+static uint64_t arg_fec_roots = 2;
static char *arg_root_hash_signature = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_salt, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep);
static int help(void) {
"base64 string encoding signature prefixed by base64:.");
}
+static int parse_block_size(const char *t, uint64_t *size) {
+ uint64_t u;
+ int r;
+
+ r = parse_size(t, 1024, &u);
+ if (r < 0)
+ return r;
+
+ if (u < 512 || u > (512 * 1024))
+ return -ERANGE;
+
+ if ((u % 512) != 0 || !ISPOWEROF2(u))
+ return -EINVAL;
+
+ *size = u;
+
+ return 0;
+}
+
static int parse_options(const char *options) {
int r;
else if (streq(word, "panic-on-corruption"))
arg_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION;
#endif
- else if ((val = startswith(word, "root-hash-signature="))) {
+ else if ((val = startswith(word, "superblock="))) {
+
+ r = parse_boolean(val);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse boolean '%s': %m", word);
+
+ arg_superblock = r;
+ } else if ((val = startswith(word, "format="))) {
+
+ if (!STR_IN_SET(val, "0", "1"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "format= expects either 0 (original Chrome OS version) or "
+ "1 (modern version).");
+
+ arg_format = val[0] - '0';
+ } else if ((val = startswith(word, "data-block-size="))) {
+ uint64_t sz;
+
+ r = parse_block_size(val, &sz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse size '%s': %m", word);
+
+ arg_data_block_size = sz;
+ } else if ((val = startswith(word, "hash-block-size="))) {
+ uint64_t sz;
+
+ r = parse_block_size(val, &sz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse size '%s': %m", word);
+
+ arg_hash_block_size = sz;
+ } else if ((val = startswith(word, "data-blocks="))) {
+ uint64_t u;
+
+ r = safe_atou64(val, &u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse number '%s': %m", word);
+
+ arg_data_blocks = u;
+ } else if ((val = startswith(word, "hash-offset="))) {
+ uint64_t off;
+
+ r = parse_size(val, 1024, &off);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse offset '%s': %m", word);
+ if (off % 512 != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "hash-offset= expects a 512-byte aligned value.");
+
+ arg_hash_offset = off;
+ } else if ((val = startswith(word, "salt="))) {
+
+ if (!string_is_safe(val))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid.");
+
+ if (isempty(val)) {
+ arg_salt = mfree(arg_salt);
+ arg_salt_size = 32;
+ } else if (streq(val, "-")) {
+ arg_salt = mfree(arg_salt);
+ arg_salt_size = 0;
+ } else {
+ size_t l;
+ void *m;
+
+ r = unhexmem(val, strlen(val), &m, &l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse salt '%s': %m", word);
+
+ free_and_replace(arg_salt, m);
+ arg_salt_size = l;
+ }
+ } else if ((val = startswith(word, "uuid="))) {
+
+ r = sd_id128_from_string(val, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse UUID '%s': %m", word);
+
+ r = free_and_strdup(&arg_uuid, val);
+ if (r < 0)
+ return log_oom();
+ } else if ((val = startswith(word, "hash="))) {
+
+ r = free_and_strdup(&arg_hash, val);
+ if (r < 0)
+ return log_oom();
+ } else if ((val = startswith(word, "fec-device="))) {
+ _cleanup_free_ char *what = NULL;
+
+ what = fstab_node_to_udev_node(val);
+ if (!what)
+ return log_oom();
+
+ if (!path_is_absolute(what))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an absolute path.");
+
+ if (!path_is_normalized(what))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an normalized path.");
+
+ r = free_and_strdup(&arg_fec_what, what);
+ if (r < 0)
+ return log_oom();
+ } else if ((val = startswith(word, "fec-offset="))) {
+ uint64_t off;
+
+ r = parse_size(val, 1024, &off);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse offset '%s': %m", word);
+ if (off % 512 != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-offset= expects a 512-byte aligned value.");
+
+ arg_fec_offset = off;
+ } else if ((val = startswith(word, "fec-roots="))) {
+ uint64_t u;
+
+ r = safe_atou64(val, &u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse number '%s', ignoring: %m", word);
+ if (u < 2 || u > 24)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-rootfs= expects a value between 2 and 24 (including).");
+
+ arg_fec_roots = u;
+ } else if ((val = startswith(word, "root-hash-signature="))) {
r = save_roothashsig_option(val, /* strict= */ true);
if (r < 0)
return r;
if (streq(verb, "attach")) {
const char *volume, *data_device, *verity_device, *root_hash, *options;
_cleanup_free_ void *m = NULL;
+ struct crypt_params_verity p = {};
crypt_status_info status;
size_t l;
return log_error_errno(r, "Failed to parse options: %m");
}
- r = crypt_load(cd, CRYPT_VERITY, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to load verity superblock: %m");
+ if (arg_superblock) {
+ p = (struct crypt_params_verity) {
+ .fec_device = arg_fec_what,
+ .hash_area_offset = arg_hash_offset,
+ .fec_area_offset = arg_fec_offset,
+ .fec_roots = arg_fec_roots,
+ };
+
+ r = crypt_load(cd, CRYPT_VERITY, &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load verity superblock: %m");
+ } else {
+ p = (struct crypt_params_verity) {
+ .hash_name = arg_hash,
+ .data_device = data_device,
+ .fec_device = arg_fec_what,
+ .salt = arg_salt,
+ .salt_size = arg_salt_size,
+ .hash_type = arg_format,
+ .data_block_size = arg_data_block_size,
+ .hash_block_size = arg_hash_block_size,
+ .data_size = arg_data_blocks,
+ .hash_area_offset = arg_hash_offset,
+ .fec_area_offset = arg_fec_offset,
+ .fec_roots = arg_fec_roots,
+ .flags = CRYPT_VERITY_NO_HEADER,
+ };
+
+ r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format verity superblock: %m");
+ }
r = crypt_set_data_device(cd, data_device);
if (r < 0)
#include "alloc-util.h"
#include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "devnum-util.h"
#include "escape.h"
#include "main-func.h"
assert(path);
- r = chase_symlinks("/usr", path, CHASE_PREFIX_ROOT, &old_usr, NULL);
+ r = chase("/usr", path, CHASE_PREFIX_ROOT, &old_usr, NULL);
if (r < 0)
return log_error_errno(r, "/usr not available in old root: %m");
r = config_parse(NULL, service->path, NULL,
"Desktop Entry\0",
xdg_config_item_table_lookup, items,
- CONFIG_PARSE_WARN, service,
+ CONFIG_PARSE_RELAXED | CONFIG_PARSE_WARN,
+ service,
NULL);
/* If parsing failed, only hide the file so it will still mask others. */
if (r < 0) {
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
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
+test_append_files() {
+ if get_bool "$LOOKS_LIKE_SUSE"; then
+ dinfo "Install the unit test binaries needed by the TEST-02-UNITTESTS at runtime"
+ inst_recursive "${SOURCE_DIR}/unit-tests"
+ fi
+}
+
check_result_nspawn() {
check_result_nspawn_unittests "${1}"
}
# Clean up certain "problematic" files which may be left over by failing tests
: >"${initdir:?}/etc/fstab"
: >"${initdir:?}/etc/crypttab"
+ # Clear previous assignment
+ QEMU_OPTIONS_ARRAY=()
}
test_run_one() {
local i
local qemu_opts=()
- for i in {0..27}; do
+ for (( i = 0; i < 5; i++ )); do
qemu_opts+=(
- "-device nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
- "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ "-device" "nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ )
+ done
+ for (( i = 5; i < 10; i++ )); do
+ qemu_opts+=(
+ "-device" "nvme,drive=nvme$i,serial= deadbeef $i ,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ )
+ done
+ for (( i = 10; i < 15; i++ )); do
+ qemu_opts+=(
+ "-device" "nvme,drive=nvme$i,serial= dead/beef/$i ,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+ )
+ done
+ for (( i = 15; i < 20; i++ )); do
+ qemu_opts+=(
+ "-device" "nvme,drive=nvme$i,serial=dead/../../beef/$i,num_queues=8"
+ "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
)
done
KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
- QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
+ QEMU_OPTIONS="${USER_QEMU_OPTIONS}"
+ QEMU_OPTIONS_ARRAY=("${qemu_opts[@]}")
test_run_one "${1:?}"
}
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"
--- /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 NotifyAccess through sd-notify"
+
+# shellcheck source=test/test-functions
+. "${TEST_BASE_DIR:?}/test-functions"
+
+do_test "$@"
--- /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 "$@"
--- /dev/null
+/*.* -whitespace
+/*.* binary
+/*.* generated
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
# type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --allowlisted -d $devnode", \
SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}"
# iSCSI devices from the same host have all the same ID_SERIAL,
KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
-KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --allowlisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="st*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
# SCSI devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="cciss"
KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
# SPDX-License-Identifier: LGPL-2.1-or-later
if install_tests
- testdata_dir = testsdir + '/testdata/'
-
- install_subdir('journal-data',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('test-execute',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('test-fstab-generator',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('test-path',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('test-path-util',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('test-umount',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('test-network-generator-conversion',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-03.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-04.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-06.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-10.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-11.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-16.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-28.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-30.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-52.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
- install_subdir('testsuite-63.units',
- exclude_files : '.gitattributes',
- install_dir : testdata_dir)
+ foreach subdir : [
+ 'auxv',
+ 'journal-data',
+ 'units',
+ 'test-execute',
+ 'test-fstab-generator',
+ 'test-path',
+ 'test-path-util',
+ 'test-umount',
+ 'test-network',
+ 'test-network-generator-conversion',
+ 'testsuite-03.units',
+ 'testsuite-04.units',
+ 'testsuite-06.units',
+ 'testsuite-10.units',
+ 'testsuite-11.units',
+ 'testsuite-16.units',
+ 'testsuite-28.units',
+ 'testsuite-30.units',
+ 'testsuite-52.units',
+ 'testsuite-63.units',
+ 'testsuite-80.units',
+ ]
+ install_subdir(subdir,
+ exclude_files : '.gitattributes',
+ install_dir : testdata_dir)
+ endforeach
install_data(kbd_model_map,
install_dir : testdata_dir + '/test-keymap-util')
install_data('create-busybox-container',
install_mode : 'rwxr-xr-x',
install_dir : testdata_dir)
+
+ # The unit tests implemented as shell scripts expect to find testdata/
+ # in the directory where they are stored.
+ meson.add_install_script(meson_make_symlink,
+ testdata_dir,
+ unittestsdir / 'testdata')
endif
test_bootctl_json_sh = find_program('test-bootctl-json.sh')
configuration : conf)
if install_tests and conf.get('ENABLE_SYSUSERS') == 1
install_data(test_sysusers_sh,
- install_dir : testsdir)
+ install_dir : unittestsdir)
install_subdir('test-sysusers',
exclude_files : '.gitattributes',
install_dir : testdata_dir)
test_compare_versions_sh = files('test-compare-versions.sh')
if install_tests
install_data(test_compare_versions_sh,
- install_dir : testsdir)
+ install_dir : unittestsdir)
endif
############################################################
install_data('test-fstab-generator.sh',
install_mode : 'rwxr-xr-x',
- install_dir : testsdir)
+ install_dir : unittestsdir)
install_data('test-network-generator-conversion.sh',
install_mode : 'rwxr-xr-x',
- install_dir : testsdir)
+ install_dir : unittestsdir)
endif
############################################################
+++ /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
opts = argument_parser().parse_args()
-unittestdir = pathlib.Path(__file__).parent.absolute()
+unittestdir = pathlib.Path(__file__).parent.absolute() / 'unit-tests'
tests = list(unittestdir.glob('test-*'))
if opts.unsafe:
"$bootctl" -R || test "$?" -eq 80
"$bootctl" -RR || test "$?" -eq 80
+# regression tests for
+# https://github.com/systemd/systemd/pull/27199#issuecomment-1511387731
+if ret=$("$bootctl" --print-esp-path); then
+ test "$ret" = "/efi" -o "$ret" = "/boot" -o "$ret" = "/boot/efi"
+fi
+if ret=$("bootctl" --print-boot-path); then
+ test "$ret" = "/efi" -o "$ret" = "/boot" -o "$ret" = "/boot/efi"
+fi
+
if "$bootctl" -R > /dev/null ; then
P=$("$bootctl" -R)
PP=$("$bootctl" -RR)
# To force creating a new image from scratch (eg: to encrypt it), also define
# TEST_FORCE_NEWIMAGE=1 in the test setup script.
IMAGE_NAME=${IMAGE_NAME:-default}
-STRIP_BINARIES="${STRIP_BINARIES:-yes}"
TEST_REQUIRE_INSTALL_TESTS="${TEST_REQUIRE_INSTALL_TESTS:-1}"
TEST_PARALLELIZE="${TEST_PARALLELIZE:-0}"
TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED="${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED:-1}"
TEST_BASE_DIR=${TEST_BASE_DIR:-$(realpath "$(dirname "${BASH_SOURCE[0]}")")}
TEST_UNITS_DIR="$(realpath "$TEST_BASE_DIR/units")"
SOURCE_DIR=$(realpath "$TEST_BASE_DIR/..")
-TOOLS_DIR="$SOURCE_DIR/tools"
# These variables are used by test scripts
-export TEST_BASE_DIR TEST_UNITS_DIR SOURCE_DIR TOOLS_DIR
+export TEST_BASE_DIR TEST_UNITS_DIR SOURCE_DIR
+TOOLS_DIR="$SOURCE_DIR/tools"
# note that find-build-dir.sh will return $BUILD_DIR if provided, else it will try to find it
if get_bool "${NO_BUILD:=}"; then
BUILD_DIR="$SOURCE_DIR"
exit 1
fi
TESTNAME="$(basename "$(dirname "$(realpath "$TESTFILE")")")"
-STATEDIR="$BUILD_DIR/test/$TESTNAME"
+
+WORKDIR="/var/tmp/systemd-tests"
+if get_bool "${NO_BUILD:=}"; then
+ STATEDIR="$WORKDIR/$TESTNAME"
+else
+ STATEDIR="$BUILD_DIR/test/$TESTNAME"
+fi
+
STATEFILE="$STATEDIR/.testdir"
IMAGESTATEDIR="$STATEDIR/.."
TESTLOG="$STATEDIR/test.log"
losetup
lz4cat
mkfifo
+ mknod
mktemp
modprobe
mount
seq
setfattr
setfont
+ setpriv
setsid
sfdisk
sh
sleep
stat
+ stty
su
sulogin
sysctl
route
sort
strace
- stty
tty
vi
/usr/libexec/vi
IS_BUILT_WITH_COVERAGE=$(is_built_with_coverage && echo yes || echo no)
if get_bool "$IS_BUILT_WITH_ASAN"; then
- STRIP_BINARIES=no
SKIP_INITRD="${SKIP_INITRD:-yes}"
PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
QEMU_MEM="${QEMU_MEM:-2G}"
read -ra user_qemu_options <<< "$QEMU_OPTIONS"
qemu_options+=("${user_qemu_options[@]}")
fi
+ qemu_options+=(${QEMU_OPTIONS_ARRAY:+"${QEMU_OPTIONS_ARRAY[@]}"})
if [[ -n "${KERNEL_APPEND:=}" ]]; then
local user_kernel_append
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app0"
echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
+ ( echo "${version_id}"
+ echo "SYSEXT_IMAGE_ID=app" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
cat >"$initdir/usr/lib/systemd/system/app0.service" <<EOF
[Service]
Type=oneshot
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
( echo "${version_id}"
echo "SYSEXT_SCOPE=portable"
+ echo "SYSEXT_IMAGE_ID=app"
+ echo "SYSEXT_IMAGE_VERSION=1"
echo "PORTABLE_PREFIXES=app1" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
install_testuser
has_user_dbus_socket && install_user_dbus
setup_selinux
- strip_binaries
instmods veth
install_depmod_files
generate_module_dependencies
ddebug "Install debian files from package $deb"
for file in $files; do
[ -e "$file" ] || continue
- [ -d "$file" ] && continue
+ [ ! -L "$file" ] && [ -d "$file" ] && continue
inst "$file"
done
done < <(grep -E '^Package:' "${SOURCE_DIR}/debian/control" | cut -d ':' -f 2)
ddebug "Install files from package $p"
while read -r f; do
[ -e "$f" ] || continue
- [ -d "$f" ] && continue
+ [ ! -L "$f" ] && [ -d "$f" ] && continue
inst "$f"
done < <(rpm -ql "$p")
done
- # Embed the files needed by the extended testsuite at runtime. Also include
- # the unit tests needed by TEST-02-UNITTESTS. This is mostly equivalent to
- # what `ninja install` does for the tests when '-Dinstall-tests=true'.
- #
- # Why? openSUSE ships a package named 'systemd-testsuite' which contains
- # the minimal set of files that allows to run the testsuite on the host (as
- # long as it runs an equivalent version of systemd) getting rid of the
- # hassles of fetching, configuring, building the source code.
- dinfo "Install the files needed by the tests at runtime"
- image_install "${SOURCE_DIR}"/test-*
+ dinfo "Install the data needed by the tests at runtime"
inst_recursive "${SOURCE_DIR}/testdata"
- inst_recursive "${SOURCE_DIR}/manual"
# On openSUSE, this directory is not created at package install, at least
# for now.
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
install_missing_libraries() {
dinfo "Install missing libraries"
# install possible missing libraries
- for i in "${initdir:?}"{,/usr}/{sbin,bin}/* "$initdir"{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
+ for i in "${initdir:?}"{,/usr}/{sbin,bin}/* "$initdir"{,/usr}/lib/systemd/{,tests/unit-tests/{,manual/,unsafe/}}*; do
LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath "$i")" inst_libs "$i"
done
fi
# Partition sizes are in MiBs
- local root_size=1000
- local data_size=50
+ local root_size=768
+ local data_size=100
if ! get_bool "$NO_BUILD"; then
if meson configure "${BUILD_DIR:?}" | grep 'static-lib\|standalone-binaries' | awk '{ print $2 }' | grep -q 'true'; then
root_size=$((root_size+=200))
if get_bool "$IS_BUILT_WITH_COVERAGE"; then
root_size=$((root_size+=250))
fi
+ if get_bool "$IS_BUILT_WITH_ASAN"; then
+ root_size=$((root_size * 2))
+ fi
fi
- if ! get_bool "$STRIP_BINARIES"; then
- root_size=$((4 * root_size))
- data_size=$((2 * data_size))
- fi
+
if [ "$IMAGE_NAME" = "repart" ]; then
root_size=$((root_size+=1000))
fi
dest="${TESTDIR:?}/coverage-info"
fi
+ if [[ ! -e "${TESTDIR:?}/coverage-base" ]]; then
+ # This shouldn't happen, as the report is generated during the setup
+ # phase (test_setup()).
+ derror "Missing base coverage report"
+ return 1
+ fi
+
# Create a coverage report that will later be uploaded. Remove info about
# system libraries/headers, as we don't really care about them.
+ lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}.new"
if [[ -f "$dest" ]]; then
# If the destination report file already exists, don't overwrite it, but
- # dump the new report in a temporary file and then merge it with the already
- # present one - this usually happens when running both "parts" of a test
- # in one run (the qemu and the nspawn part).
- lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}.new"
- lcov --remove "${dest}.new" -o "${dest}.new" '/usr/include/*' '/usr/lib/*'
+ # merge it with the already present one - this usually happens when
+ # running both "parts" of a test in one run (the qemu and the nspawn part).
lcov --add-tracefile "${dest}" --add-tracefile "${dest}.new" -o "${dest}"
- rm -f "${dest}.new"
else
- lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}"
- lcov --remove "${dest}" -o "${dest}" '/usr/include/*' '/usr/lib/*'
+ # If there's no prior coverage report, merge the new one with the base
+ # report we did during the setup phase (see test_setup()).
+ lcov --add-tracefile "${TESTDIR:?}/coverage-base" --add-tracefile "${dest}.new" -o "${dest}"
fi
+ lcov --remove "$dest" -o "$dest" '/usr/include/*' '/usr/lib/*'
+ rm -f "${dest}.new"
# If the test logs contain lines like:
#
# usually due to the sandbox being too restrictive (e.g. ProtectSystem=yes,
# ProtectHome=yes) or the $BUILD_DIR being inaccessible to non-root users - see
# `setfacl` stuff in install_compiled_systemd().
+ #
+ # Also, a note: some tests, like TEST-46, overmount /home with tmpfs, which
+ # means if your build dir is under /home/your-user (which is usually the
+ # case) you might get bogus errors and missing coverage.
if ! get_bool "${IGNORE_MISSING_COVERAGE:=}" && \
"${JOURNALCTL:?}" -q --no-pager -D "${root:?}/var/log/journal" --grep "profiling:.+?gcda:[Cc]annot open"; then
derror "Detected possibly missing coverage, check the journal"
return $ret
}
-strip_binaries() {
- dinfo "Strip binaries"
- if ! get_bool "$STRIP_BINARIES"; then
- dinfo "STRIP_BINARIES == no, keeping binaries unstripped"
- return 0
- fi
- while read -r bin; do
- strip --strip-unneeded "$bin" |& grep -vi 'file format not recognized' | ddebug || :
- done < <(find "${initdir:?}" -executable -not -path '*/lib/modules/*.ko' -type f)
-}
-
create_rc_local() {
dinfo "Create rc.local"
mkdir -p "${initdir:?}/etc/rc.d"
LOOPDEV="$_LOOPDEV"
if [[ ! -d "$TESTDIR" ]]; then
if [[ -z "$TESTDIR" ]]; then
- TESTDIR="$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)"
+ TESTDIR="$(mktemp --tmpdir=$WORKDIR -d -t systemd-test.XXXXXX)"
else
mkdir -p "$TESTDIR"
fi
# 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
printf '[Service]\nStandardOutput=journal+console\nStandardError=journal+console' >"$dropin_dir/99-stdout.conf"
fi
+ if get_bool "$IS_BUILT_WITH_COVERAGE"; then
+ # Do an initial coverage capture, to make sure the final report includes
+ # files that the tests didn't touch at all
+ lcov --initial --capture --directory "${initdir}/${BUILD_DIR:?}" --output-file "${TESTDIR:?}/coverage-base"
+ fi
+
if get_bool "$hook_defined"; then
test_append_files "${initdir:?}"
fi
[[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
done
+ mkdir -p "$WORKDIR"
mkdir -p "$STATEDIR"
import_testdir
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
ID='the-id2'
EOF
-SYSTEMD_OS_RELEASE="$root/etc/os-release2" check_alias o 'the-id2'
+SYSTEMD_OS_RELEASE="/etc/os-release2" check_alias o 'the-id2'
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
--- /dev/null
+[Service]
+Type=notify
+NotifyAccess=all
+FileDescriptorStoreMax=10
+FileDescriptorStorePreserve=restart
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/fdstore-pin.sh 0
+StandardOutput=journal+console
+StandardError=journal+console
--- /dev/null
+[Service]
+Type=notify
+NotifyAccess=all
+FileDescriptorStoreMax=10
+FileDescriptorStorePreserve=yes
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/fdstore-pin.sh 1
+StandardOutput=journal+console
+StandardError=journal+console
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+PINNED="$1"
+COUNTER="/tmp/fdstore-invoked.$PINNED"
+FILE="/tmp/fdstore-data.$PINNED"
+
+# This script is called six times: thrice from a service unit where the fdstore
+# is pinned, and thrice where it isn't. The second iteration of each series is
+# a restart, the third a stop followed by a start
+
+if [ -e "$COUNTER" ] ; then
+ read -r N < "$COUNTER"
+else
+ N=0
+fi
+
+echo "Invocation #$N with PINNED=$PINNED."
+
+if [ "$N" -eq 0 ] ; then
+ # First iteration
+ test "${LISTEN_FDS:-0}" -eq 0
+ test ! -e "$FILE"
+ echo waldi > "$FILE"
+ systemd-notify --fd=3 --fdname="fd-$N-$PINNED" 3< "$FILE"
+elif [ "$N" -eq 1 ] || { [ "$N" -eq 2 ] && [ "$PINNED" -eq 1 ]; } ; then
+ # Second iteration, or iteration with pinning on
+ test "${LISTEN_FDS:-0}" -eq 1
+ # We reopen fd #3 here, so that the read offset is at zero each time (hence no <&3 here…)
+ read -r word < /proc/self/fd/3
+ test "$word" = "waldi"
+else
+ test "${LISTEN_FDS:-0}" -eq 0
+ test -e "$FILE"
+fi
+
+if [ "$N" -ge 2 ] ; then
+ rm "$COUNTER" "$FILE"
+else
+ echo $((N + 1)) > "$COUNTER"
+fi
+
+systemd-notify --ready --status="Ready"
+
+exec sleep infinity
--- /dev/null
+[Unit]
+After=fdstore-pin.service fdstore-nopin.service
+Wants=fdstore-pin.service fdstore-nopin.service
--- /dev/null
+[Service]
+Type=notify
+NotifyAccess=all
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/test.sh
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+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
+(
+ echo "subshell PID: $BASHPID"
+
+ # Make us main process
+ systemd-notify --pid="$BASHPID"
+
+ # Lock down access to just us
+ systemd-notify "NOTIFYACCESS=main"
+
+ # 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"
+
+ 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"
+systemd-notify --status="BOGUS3"
+
+sync_out e
+
+exec sleep infinity
{
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
exp_links => ["boot_disk1", "boot_diskXY1"],
- not_exp_links => ["boot_diskXX1", "hoge"],
+ not_exp_links => ["boot_diskXX1"],
}],
rules => <<EOF
SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n", SYMLINK+="hoge"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK-="hoge"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+EOF
+ },
+ {
+ desc => "SYMLINK tests",
+ devices => [
+ {
+ devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+ exp_links => ["link1", "link2/foo", "link3/aaa/bbb",
+ "abs1", "abs2/foo", "abs3/aaa/bbb",
+ "default___replace_test/foo_aaa",
+ "string_escape___replace/foo_bbb",
+ "env_with_space",
+ "default/replace/mode_foo__hoge",
+ "replace_env_harder_foo__hoge"],
+ not_exp_links => ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"],
+ }],
+ rules => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="././removed3"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="/dev//./removed3/./"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="unsafe/../../path"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/nondev/path/will/be/refused"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="link1 .///link2/././/foo//./ .///link3/aaa/bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/dev/abs1 /dev//./abs2///foo/./ ////dev/abs3/aaa/bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="default?;;replace%%test/foo'aaa"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", SYMLINK+="string_escape replace/foo%%bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="env with space", SYMLINK+="%E{.HOGE}"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="default/replace/mode?foo;;hoge", SYMLINK+="%E{.HOGE}"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}"
EOF
},
{
not_exp_name => "bad",
}],
rules => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="|foo", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="|foo", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
EOF
},
{
not_exp_name => "bad",
}],
rules => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="foo||bar", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="foo||bar", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
EOF
},
{
not_exp_name => "bad",
}],
rules => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="foo|", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="foo|", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
EOF
},
{
KERNEL=="sda", TAG="c"
TAGS=="foo||bar||c", SYMLINK+="found"
TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
+EOF
+ },
+ {
+ desc => "TAG refuses invalid string",
+ devices => [
+ {
+ devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+ exp_links => ["valid", "found"],
+ not_exp_links => ["empty", "invalid_char", "path", "bad", "bad2"],
+ }],
+ rules => <<EOF
+KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid"
+TAGS=="", SYMLINK+="empty"
+TAGS=="invalid.char", SYMLINK+="invalid_char"
+TAGS=="path/is/also/invalid", SYMLINK+="path"
+TAGS=="valid", SYMLINK+="valid"
+TAGS=="valid|", SYMLINK+="found"
+TAGS=="aaa|", SYMLINK+="bad"
+TAGS=="aaa|bbb", SYMLINK+="bad2"
EOF
},
{
EOF
},
{
- desc => "continuations with white only line",
+ desc => "continuations with space only line",
devices => [
{
devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
--- /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:?}"
+ local environ
+
+ # If $PID1_ENVIRON is set temporarily overmount /proc/1/environ with
+ # a temporary file that contains contents of $PID1_ENVIRON. This is
+ # necessary in cases where the generator reads the environment through
+ # getenv_for_pid(1, ...) or similar like getty-generator does.
+ #
+ # Note: $PID1_ENVIRON should be a NUL separated list of env assignments
+ if [[ -n "${PID1_ENVIRON:-}" ]]; then
+ environ="$(mktemp)"
+ echo -ne "${PID1_ENVIRON}\0" >"${environ:?}"
+ mount -v --bind "$environ" /proc/1/environ
+ fi
+
+ rm -fr "${out_dir:?}"/*
+ mkdir -p "$out_dir"/{normal,early,late}
+ SYSTEMD_LOG_LEVEL="${SYSTEMD_LOG_LEVEL:-debug}" "$generator" "$out_dir/normal" "$out_dir/early" "$out_dir/late"
+ ls -lR "$out_dir"
+
+ if [[ -n "${environ:-}" ]]; then
+ umount /proc/1/environ
+ rm -f "$environ"
+ fi
+}
# 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
+
+# Check if the colored --version output behaves correctly
+SYSTEMD_COLORS=256 systemctl --version
+
+# Check if we properly differentiate between a full systemd setup and a "light"
+# version of it that's done during daemon-reexec
+#
+# See: https://github.com/systemd/systemd/issues/27106
+if systemd-detect-virt -q --container; then
+ # We initialize /run/systemd/container only during a full setup
+ test -e /run/systemd/container
+ cp -afv /run/systemd/container /tmp/container
+ rm -fv /run/systemd/container
+ systemctl daemon-reexec
+ test ! -e /run/systemd/container
+ cp -afv /tmp/container /run/systemd/container
+else
+ # We bring the loopback netdev up only during a full setup, so it should
+ # not get brought back up during reexec if we disable it beforehand
+ [[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]]
+ ip link set lo down
+ [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+ systemctl daemon-reexec
+ [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+ ip link set lo up
+
+ # We also disable coredumps only during a full setup
+ 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
+
+# Check that the early setup is actually skipped on reexec.
+# If the early setup is done more than once, then several timestamps,
+# e.g. SecurityStartTimestamp, are re-initialized, and causes an ABRT
+# of systemd-analyze blame. See issue #27187.
+systemd-analyze blame
+
+echo OK >/testok
NPROC=$(nproc)
MAX_QUEUE_SIZE=${NPROC:-2}
TESTS_GLOB=${TESTS_GLOB:-test-*}
-mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}")
+mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/unit-tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}")
# reset state
rm -fv /failed-tests /skipped-tests /skipped
systemctl stop sleep.service hello-after-sleep.target
# Some basic testing that --show-transaction does something useful
-systemctl is-active systemd-importd && { echo 'unexpected success'; exit 1; }
+(! systemctl is-active systemd-importd)
systemctl -T start systemd-importd
systemctl is-active systemd-importd
systemctl --show-transaction stop systemd-importd
-systemctl is-active systemd-importd && { echo 'unexpected success'; exit 1; }
+(! systemctl is-active systemd-importd)
# Test for a crash when enqueuing a JOB_NOP when other job already exists
systemctl start --no-block hello-after-sleep.target
# wait5fail fails, so systemctl should fail
START_SEC=$(date -u '+%s')
-systemctl start --wait wait2.service wait5fail.service && { echo 'unexpected success'; exit 1; }
+(! systemctl start --wait wait2.service wait5fail.service)
END_SEC=$(date -u '+%s')
ELAPSED=$((END_SEC-START_SEC))
[[ "$ELAPSED" -ge 5 ]] && [[ "$ELAPSED" -le 7 ]] || exit 1
grep -q '^__CURSOR=' /output
grep -q '^MESSAGE=foo$' /output
grep -q '^PRIORITY=6$' /output
-grep '^FOO=' /output && { echo 'unexpected success'; exit 1; }
-grep '^SYSLOG_FACILITY=' /output && { echo 'unexpected success'; exit 1; }
+(! grep '^FOO=' /output)
+(! grep '^SYSLOG_FACILITY=' /output)
-# `-b all` negates earlier use of -b (-b and -m are otherwise exclusive)
+# '-b all' negates earlier use of -b (-b and -m are otherwise exclusive)
journalctl -b -1 -b all -m >/dev/null
# -b always behaves like -b0
systemctl kill --signal=SIGKILL systemd-journald
sleep 3
[[ ! -f "/i-lose-my-logs" ]]
+systemctl stop forever-print-hola
# https://github.com/systemd/systemd/issues/15528
journalctl --follow --file=/var/log/journal/*/* | head -n1 || [[ $? -eq 1 ]]
END=$(date '+%Y-%m-%d %T.%6N')
systemctl stop text_xattr
- if journalctl -q -u "text_xattr" -S "$START" -U "$END" --grep "Failed to set 'user.journald_log_filter_patterns' xattr.*not supported$"; then
- return 1
- fi
-
- return 0
+ ! journalctl -q -u "text_xattr" -S "$START" -U "$END" --grep "Failed to set 'user.journald_log_filter_patterns' xattr.*not supported$"
}
if is_xattr_supported; then
JTMP="/var/tmp/jtmp-$RANDOM"
mkdir "$JTMP"
-( cd /test-journals/1 && for f in *.zst ; do unzstd < "$f" > "$JTMP/${f%.zst}" ; done )
+( cd /test-journals/1 && for f in *.zst; do unzstd "$f" -o "$JTMP/${f%.zst}"; done )
-journalctl --directory="$JTMP" --list-boots --output=json > /tmp/lb1
+journalctl --directory="$JTMP" --list-boots --output=json >/tmp/lb1
diff -u /tmp/lb1 - <<'EOF'
[{"index":-3,"boot_id":"5ea5fc4f82a14186b5332a788ef9435e","first_entry":1666569600994371,"last_entry":1666584266223608},{"index":-2,"boot_id":"bea6864f21ad4c9594c04a99d89948b0","first_entry":1666584266731785,"last_entry":1666584347230411},{"index":-1,"boot_id":"4c708e1fd0744336be16f3931aa861fb","first_entry":1666584348378271,"last_entry":1666584354649355},{"index":0,"boot_id":"35e8501129134edd9df5267c49f744a4","first_entry":1666584356661527,"last_entry":1666584438086856}]
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
fi
}
+function wait_for_timeout()
+{
+ local unit="$1"
+ local time="$2"
+
+ while [[ $time -gt 0 ]]; do
+ if [[ "$(systemctl show --property=Result "$unit")" == "Result=timeout" ]]; then
+ return 0
+ fi
+
+ sleep 1
+ time=$((time - 1))
+ done
+
+ journalctl -u "$unit" >>"$TESTLOG"
+
+ return 1
+}
+
# This checks all stages, start, runtime and stop, can be extended by
# EXTEND_TIMEOUT_USEC
wait_for fail_stop stopfail
wait_for fail_runtime runtimefail
+# These ensure that RuntimeMaxSec is honored for scope and service units
+# when they are created.
+runtime_max_sec=5
+
+systemd-run \
+ --property=RuntimeMaxSec=${runtime_max_sec}s \
+ -u runtime-max-sec-test-1.service \
+ /usr/bin/sh -c "while true; do sleep 1; done"
+wait_for_timeout runtime-max-sec-test-1.service $((runtime_max_sec + 2))
+
+systemd-run \
+ --property=RuntimeMaxSec=${runtime_max_sec}s \
+ --scope \
+ -u runtime-max-sec-test-2.scope \
+ /usr/bin/sh -c "while true; do sleep 1; done" &
+wait_for_timeout runtime-max-sec-test-2.scope $((runtime_max_sec + 2))
+
+# These ensure that RuntimeMaxSec is honored for scope and service
+# units if the value is changed and then the manager is reloaded.
+systemd-run \
+ -u runtime-max-sec-test-3.service \
+ /usr/bin/sh -c "while true; do sleep 1; done"
+mkdir -p /etc/systemd/system/runtime-max-sec-test-3.service.d/
+cat > /etc/systemd/system/runtime-max-sec-test-3.service.d/override.conf << EOF
+[Service]
+RuntimeMaxSec=${runtime_max_sec}s
+EOF
+systemctl daemon-reload
+wait_for_timeout runtime-max-sec-test-3.service $((runtime_max_sec + 2))
+
+systemd-run \
+ --scope \
+ -u runtime-max-sec-test-4.scope \
+ /usr/bin/sh -c "while true; do sleep 1; done" &
+
+# Wait until the unit is running to avoid race with creating the override.
+until systemctl is-active runtime-max-sec-test-4.scope; do
+ sleep 1
+done
+mkdir -p /etc/systemd/system/runtime-max-sec-test-4.scope.d/
+cat > /etc/systemd/system/runtime-max-sec-test-4.scope.d/override.conf << EOF
+[Scope]
+RuntimeMaxSec=${runtime_max_sec}s
+EOF
+systemctl daemon-reload
+wait_for_timeout runtime-max-sec-test-4.scope $((runtime_max_sec + 2))
+
if [[ -f "$TESTLOG" ]]; then
# no mv
cp "$TESTLOG" /test.log
}
function check() {
- local i j
-
- for ((i = 0; i < 2; i++)); do
+ for _ in {1..2}; do
systemctl restart systemd-udevd.service
udevadm control --ping
udevadm settle
check_validity
- for ((j = 0; j < 2; j++)); do
+ for _ in {1..2}; do
udevadm trigger -w --action add --subsystem-match=block
check_validity
done
- for ((j = 0; j < 2; j++)); do
+ for _ in {1..2}; do
udevadm trigger -w --action change --subsystem-match=block
check_validity
done
wait_service_active() {(
set +ex
- for (( i = 0; i < 20; i++ )); do
- if (( i != 0 )); then sleep 0.5; fi
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
if systemctl --quiet is-active "${1?}"; then
return 0
fi
wait_service_inactive() {(
set +ex
- for (( i = 0; i < 20; i++ )); do
- if (( i != 0 )); then sleep 0.5; fi
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
systemctl --quiet is-active "${1?}"
if [[ "$?" == "3" ]]; then
return 0
udevadm control --reload
udevadm trigger --settle --action add /dev/null
-for ((i = 0; i < 20; i++)); do
- ((i == 0)) || sleep .5
+for i in {1..20}; do
+ ((i > 1)) && sleep .5
(
systemctl -q is-active /dev/test/symlink-to-null-on-add
assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-change
udevadm trigger --settle --action change /dev/null
-for ((i = 0; i < 20; i++)); do
- ((i == 0)) || sleep .5
+for i in {1..20}; do
+ ((i > 1)) && sleep .5
(
! systemctl -q is-active /dev/test/symlink-to-null-on-add
assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-change
udevadm trigger --settle --action add /dev/null
-for ((i = 0; i < 20; i++)); do
- ((i == 0)) || sleep .5
+for i in {1..20}; do
+ ((i > 1)) && sleep .5
(
systemctl -q is-active /dev/test/symlink-to-null-on-add
ACTION=="remove", GOTO="test-end"
# add 100 * 100byte of properties
-$(for ((i = 0; i < 100; i++)); do printf 'ENV{XXX%03i}="0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"\n' "$i"; done)
+$(for i in {1..100}; do printf 'ENV{XXX%03i}="0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"\n' "$i"; done)
LABEL="test-end"
EOF
fi
FOUND=1
- for ((i = 0; i < 100; i++)); do
+ for i in {1..100}; do
if ! grep -F "$(printf 'XXX%03i=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' "$i")" "$TMPDIR"/monitor.txt; then
FOUND=
break
# 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
assert_1 "${rules}"
{
- printf 'RUN+="/bin/true"%8175s\\\n' ' '
+ printf 'RUN+="/bin/true",%8174s\\\n' ' '
printf 'RUN+="/bin/false"%8174s\\\n' ' '
echo
} >"${rules}"
test_syntax_error '=' 'Invalid key/value pair, ignoring.'
test_syntax_error 'ACTION{a}=="b"' 'Invalid attribute for ACTION.'
test_syntax_error 'ACTION:="b"' 'Invalid operator for ACTION.'
-test_syntax_error 'ACTION=="b"' 'The line takes no effect, ignoring.'
+test_syntax_error 'ACTION=="b"' 'The line has no effect, ignoring.'
test_syntax_error 'DEVPATH{a}=="b"' 'Invalid attribute for DEVPATH.'
test_syntax_error 'DEVPATH:="b"' 'Invalid operator for DEVPATH.'
test_syntax_error 'KERNEL{a}=="b"' 'Invalid attribute for KERNEL.'
test_syntax_error 'TAGS:="a"' 'Invalid operator for TAGS.'
test_syntax_error 'SUBSYSTEM{a}=="b"' 'Invalid attribute for SUBSYSTEM.'
test_syntax_error 'SUBSYSTEM:="b"' 'Invalid operator for SUBSYSTEM.'
-test_syntax_error 'SUBSYSTEM=="bus" NAME="b"' '"bus" must be specified as "subsystem".'
+test_syntax_error 'SUBSYSTEM=="bus", NAME="b"' '"bus" must be specified as "subsystem".'
test_syntax_error 'SUBSYSTEMS{a}=="b"' 'Invalid attribute for SUBSYSTEMS.'
test_syntax_error 'SUBSYSTEMS:="b"' 'Invalid operator for SUBSYSTEMS.'
test_syntax_error 'DRIVER{a}=="b"' 'Invalid attribute for DRIVER.'
test_syntax_error 'SYSCTL{a}+="b"' "SYSCTL key takes '==', '!=', or '=' operator, assuming '='."
test_syntax_error 'SYSCTL{a}="%?"' 'Invalid value "%?" for SYSCTL (char 1: invalid substitution type), ignoring.'
test_syntax_error 'ATTRS=""' 'Invalid attribute for ATTRS.'
-test_syntax_error 'ATTRS{%}=="b" NAME="b"' 'Invalid attribute "%" for ATTRS (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'ATTRS{%}=="b", NAME="b"' 'Invalid attribute "%" for ATTRS (char 1: invalid substitution type), ignoring.'
test_syntax_error 'ATTRS{a}-="b"' 'Invalid operator for ATTRS.'
-test_syntax_error 'ATTRS{device/}!="a" NAME="b"' "'device' link may not be available in future kernels."
-test_syntax_error 'ATTRS{../}!="a" NAME="b"' 'Direct reference to parent sysfs directory, may break in future kernels.'
+test_syntax_error 'ATTRS{device/}!="a", NAME="b"' "'device' link may not be available in future kernels."
+test_syntax_error 'ATTRS{../}!="a", NAME="b"' 'Direct reference to parent sysfs directory, may break in future kernels.'
test_syntax_error 'TEST{a}=="b"' "Failed to parse mode 'a': Invalid argument"
-test_syntax_error 'TEST{0}=="%" NAME="b"' 'Invalid value "%" for TEST (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'TEST{0}=="%", NAME="b"' 'Invalid value "%" for TEST (char 1: invalid substitution type), ignoring.'
test_syntax_error 'TEST{0644}="b"' 'Invalid operator for TEST.'
test_syntax_error 'PROGRAM{a}=="b"' 'Invalid attribute for PROGRAM.'
test_syntax_error 'PROGRAM-="b"' 'Invalid operator for PROGRAM.'
-test_syntax_error 'PROGRAM=="%" NAME="b"' 'Invalid value "%" for PROGRAM (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'PROGRAM=="%", NAME="b"' 'Invalid value "%" for PROGRAM (char 1: invalid substitution type), ignoring.'
test_syntax_error 'IMPORT="b"' 'Invalid attribute for IMPORT.'
test_syntax_error 'IMPORT{a}="b"' 'Invalid attribute for IMPORT.'
test_syntax_error 'IMPORT{a}-="b"' 'Invalid operator for IMPORT.'
-test_syntax_error 'IMPORT{file}=="%" NAME="b"' 'Invalid value "%" for IMPORT (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'IMPORT{file}=="%", NAME="b"' 'Invalid value "%" for IMPORT (char 1: invalid substitution type), ignoring.'
test_syntax_error 'IMPORT{builtin}!="foo"' 'Unknown builtin command: foo'
test_syntax_error 'RESULT{a}=="b"' 'Invalid attribute for RESULT.'
test_syntax_error 'RESULT:="b"' 'Invalid operator for RESULT.'
test_syntax_error 'OPTIONS!="b"' 'Invalid operator for OPTIONS.'
test_syntax_error 'OPTIONS+="link_priority=a"' "Failed to parse link priority 'a': Invalid argument"
test_syntax_error 'OPTIONS:="log_level=a"' "Failed to parse log level 'a': Invalid argument"
-test_syntax_error 'OPTIONS="a" NAME="b"' "Invalid value for OPTIONS key, ignoring: 'a'"
+test_syntax_error 'OPTIONS="a", NAME="b"' "Invalid value for OPTIONS key, ignoring: 'a'"
test_syntax_error 'OWNER{a}="b"' 'Invalid attribute for OWNER.'
test_syntax_error 'OWNER-="b"' 'Invalid operator for OWNER.'
test_syntax_error 'OWNER!="b"' 'Invalid operator for OWNER.'
test_syntax_error 'RUN{builtin}+="foo"' "Unknown builtin command 'foo', ignoring"
test_syntax_error 'GOTO{a}="b"' 'Invalid attribute for GOTO.'
test_syntax_error 'GOTO=="b"' 'Invalid operator for GOTO.'
-test_syntax_error 'NAME="a" GOTO="b"' 'GOTO="b" has no matching label, ignoring'
-test_syntax_error 'GOTO="a" GOTO="b"
+test_syntax_error 'NAME="a", GOTO="b"' 'GOTO="b" has no matching label, ignoring'
+test_syntax_error 'GOTO="a", GOTO="b"
LABEL="a"' 'Contains multiple GOTO keys, ignoring GOTO="b".'
test_syntax_error 'LABEL{a}="b"' 'Invalid attribute for LABEL.'
test_syntax_error 'LABEL=="b"' 'Invalid operator for LABEL.'
test_syntax_error 'LABEL="b"' 'LABEL="b" is unused.'
test_syntax_error 'a="b"' "Invalid key 'a'"
-test_syntax_error 'KERNEL=="", KERNEL=="?*", NAME="a"' 'conflicting match expressions, the line takes no effect'
-test_syntax_error 'KERNEL=="abc", KERNEL!="abc", NAME="b"' 'conflicting match expressions, the line takes no effect'
-test_syntax_error 'KERNEL=="|a|b", KERNEL!="b|a|", NAME="c"' 'conflicting match expressions, the line takes no effect'
-test_syntax_error 'KERNEL=="a|b", KERNEL=="c|d|e", NAME="f"' 'conflicting match expressions, the line takes no effect'
+test_syntax_error 'KERNEL=="", KERNEL=="?*", NAME="a"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'KERNEL=="abc", KERNEL!="abc", NAME="b"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'KERNEL=="|a|b", KERNEL!="b|a|", NAME="c"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'KERNEL=="a|b", KERNEL=="c|d|e", NAME="f"' 'conflicting match expressions, the line has no effect'
# shellcheck disable=SC2016
-test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}!="?*" ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
- 'conflicting match expressions, the line takes no effect'
+test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}!="?*", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
+ 'conflicting match expressions, the line has no effect'
+test_syntax_error 'ACTION=="a*", ACTION=="bc*", NAME="d"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'ACTION=="a*|bc*", ACTION=="d*|ef*", NAME="g"' 'conflicting match expressions, the line has no effect'
test_syntax_error 'KERNEL!="", KERNEL=="?*", NAME="a"' 'duplicate expressions'
test_syntax_error 'KERNEL=="|a|b", KERNEL=="b|a|", NAME="c"' 'duplicate expressions'
# shellcheck disable=SC2016
-test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}=="?*" ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
+test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}=="?*", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
'duplicate expressions'
+test_syntax_error ',ACTION=="a", NAME="b"' 'Stray leading comma.'
+test_syntax_error ' ,ACTION=="a", NAME="b"' 'Stray leading comma.'
+test_syntax_error ', ACTION=="a", NAME="b"' 'Stray leading comma.'
+test_syntax_error 'ACTION=="a", NAME="b",' 'Stray trailing comma.'
+test_syntax_error 'ACTION=="a", NAME="b", ' 'Stray trailing comma.'
+test_syntax_error 'ACTION=="a" NAME="b"' 'A comma between tokens is expected.'
+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|b", KERNEL!="a|c", NAME="d"
KERNEL!="a", KERNEL!="b", NAME="c"
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}"
echo 'GOTO="a"' >"${rules}"
cat >"${exp}" <<EOF
${rules}:1 GOTO="a" has no matching label, ignoring
-${rules}:1 The line takes no effect any more, dropping
+${rules}:1 The line has no effect any more, dropping.
${rules}: udev rules check failed
EOF
cp "${workdir}/default_output_1_fail" "${exo}"
cat >"${exp}" <<EOF
${rules}:2 Contains multiple LABEL keys, ignoring LABEL="a".
${rules}:1 GOTO="a" has no matching label, ignoring
-${rules}:1 The line takes no effect any more, dropping
+${rules}:1 The line has no effect any more, dropping.
${rules}:2 LABEL="b" is unused.
${rules}: udev rules check failed
EOF
EOF
cat >"${exp}" <<EOF
${rules}:1 duplicate expressions
-${rules}:1 conflicting match expressions, the line takes no effect
+${rules}:1 conflicting match expressions, the line has no effect
+${rules}: udev rules check failed
+EOF
+cp "${workdir}/default_output_1_fail" "${exo}"
+assert_1 "${rules}"
+
+cat >"${rules}" <<'EOF'
+ACTION=="a"NAME="b"
+EOF
+cat >"${exp}" <<EOF
+${rules}:1 A comma between tokens is expected.
+${rules}:1 Whitespace between tokens is expected.
+${rules}: udev rules check failed
+EOF
+cp "${workdir}/default_output_1_fail" "${exo}"
+assert_1 "${rules}"
+
+cat >"${rules}" <<'EOF'
+ACTION=="a" ,NAME="b"
+EOF
+cat >"${exp}" <<EOF
+${rules}:1 Stray whitespace before comma.
+${rules}:1 Whitespace after comma is expected.
${rules}: udev rules check failed
EOF
cp "${workdir}/default_output_1_fail" "${exo}"
set -o pipefail
systemd-run --wait -p FailureAction=poweroff true
-systemd-run --wait -p SuccessAction=poweroff false && { echo 'unexpected success'; exit 1; }
+(! systemd-run --wait -p SuccessAction=poweroff false)
if ! test -f /firstphase ; then
echo OK >/firstphase
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 \
- -p StandardOutput=tty \
- -p StandardError=tty \
- -p Type=forking \
- -p RuntimeDirectory=mainpidsh3 \
- -p PIDFile=/run/mainpidsh3/pid \
- -p DynamicUser=1 \
- -p TimeoutStartSec=2s \
- /dev/shm/test20-mainpid3.sh \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run \
+ --unit=test-mainpidsh3.service \
+ -p StandardOutput=tty \
+ -p StandardError=tty \
+ -p Type=forking \
+ -p RuntimeDirectory=mainpidsh3 \
+ -p PIDFile=/run/mainpidsh3/pid \
+ -p DynamicUser=1 \
+ -p TimeoutStartSec=2s \
+ /dev/shm/test-mainpid3.sh)
# 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
# on the fly by one of the fuzzers
systemctl list-jobs | grep -F 'end.service' && SHUTDOWN_AT_EXIT=1 || SHUTDOWN_AT_EXIT=0
+# shellcheck disable=SC2317
at_exit() {
set +e
# We have to call the end.service/poweroff explicitly even if it's specified on
systemctl log-level info
+# FIXME: systemd-run doesn't play well with daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27204
+sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:Reexecute FIXME' /etc/dfuzzer.conf
+
# TODO
# * check for possibly newly introduced buses?
BUS_LIST=(
# Let's reload the systemd daemon to test (de)serialization as well
systemctl daemon-reload
+ # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved
+ systemctl daemon-reexec
done
umount /var/lib/machines
# Let's reload the systemd user daemon to test (de)serialization as well
systemctl --machine 'testuser@.host' --user daemon-reload
+ # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved
+ systemctl --machine 'testuser@.host' --user daemon-reexec
done
echo OK >/testok
mkfifo /tmp/f/fifo
chmod 644 /tmp/f/fifo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/f/fifo 0666 daemon daemon - This string should not be written
EOF
ln -s missing /tmp/f/dangling
ln -s /tmp/file-owned-by-root /tmp/f/symlink
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/f/dangling 0644 daemon daemon - -
f /tmp/f/symlink 0644 daemon daemon - -
EOF
EOF
test -f /tmp/f/ro-fs/foo; test ! -s /tmp/f/ro-fs/foo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/f/ro-fs/foo 0666 - - - -
EOF
test "$(stat -c %U:%G:%a /tmp/f/fifo)" = "root:root:644"
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/f/ro-fs/bar 0644 - - - -
EOF
test ! -e /tmp/f/ro-fs/bar
ln -s /root /tmp/f/daemon/unsafe-symlink
chown -R --no-dereference daemon:daemon /tmp/f/daemon
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/f/daemon/unsafe-symlink/exploit 0644 daemon daemon - -
EOF
test ! -e /tmp/f/daemon/unsafe-symlink/exploit
### unspecified in the other cases.
mkfifo /tmp/F/fifo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
F /tmp/F/fifo 0644 - - - -
EOF
ln -s missing /tmp/F/dangling
ln -s /tmp/file-owned-by-root /tmp/F/symlink
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/F/dangling 0644 daemon daemon - -
f /tmp/F/symlink 0644 daemon daemon - -
EOF
test -f /tmp/F/ro-fs/foo; test ! -s /tmp/F/ro-fs/foo
echo "truncating is not allowed anymore" >/tmp/F/rw-fs/foo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
F /tmp/F/ro-fs/foo 0644 - - - -
EOF
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
F /tmp/F/ro-fs/foo 0644 - - - - This string should not be written
EOF
test -f /tmp/F/ro-fs/foo
# Trying to change the perms should fail.
: >/tmp/F/rw-fs/foo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
F /tmp/F/ro-fs/foo 0666 - - - -
EOF
test "$(stat -c %U:%G:%a /tmp/F/ro-fs/foo)" = "root:root:644"
### Try to create a new file.
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
F /tmp/F/ro-fs/bar 0644 - - - -
EOF
test ! -e /tmp/F/ro-fs/bar
ln -s /root /tmp/F/daemon/unsafe-symlink
chown -R --no-dereference daemon:daemon /tmp/F/daemon
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
F /tmp/F/daemon/unsafe-symlink/exploit 0644 daemon daemon - -
EOF
test ! -e /tmp/F/daemon/unsafe-symlink/exploit
test ! -e /tmp/w/unexistent
### no argument given -> fails.
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
w /tmp/w/unexistent 0644 - - - -
EOF
ln -s /root /tmp/w/daemon/unsafe-symlink
chown -R --no-dereference daemon:daemon /tmp/w/daemon
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
f /tmp/w/daemon/unsafe-symlink/exploit 0644 daemon daemon - -
EOF
test ! -e /tmp/w/daemon/unsafe-symlink/exploit
# Verify the command fails to write to a root-owned subdirectory under an
# unprivileged user's directory when it's not part of the prefix, as expected
# by the unsafe_transition function.
-echo 'd /tmp/user/root/test' | systemd-tmpfiles --create - \
- && { echo 'unexpected success'; exit 1; }
+echo 'd /tmp/user/root/test' | (! systemd-tmpfiles --create -)
test ! -e /tmp/user/root/test
-echo 'd /user/root/test' | systemd-tmpfiles --root=/tmp --create - \
- && { echo 'unexpected success'; exit 1; }
+echo 'd /user/root/test' | (! systemd-tmpfiles --root=/tmp --create -)
test ! -e /tmp/user/root/test
# Verify the above works when all user-owned directories are in the prefix.
# And now, do the same with Type=exec, where the latter two should fail
systemd-run --unit=four -p Type=exec /bin/sleep infinity
-systemd-run --unit=five -p Type=exec -p User=idontexist /bin/sleep infinity && { echo 'unexpected success'; exit 1; }
-systemd-run --unit=six -p Type=exec /tmp/brokenbinary && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=five -p Type=exec -p User=idontexist /bin/sleep infinity)
+(! systemd-run --unit=six -p Type=exec /tmp/brokenbinary)
systemd-run --unit=seven -p KillSignal=SIGTERM -p RestartKillSignal=SIGINT -p Type=exec /bin/sleep infinity
# Both TERM and SIGINT happen to have the same number on all architectures
# Should work normally
busctl call \
- org.freedesktop.systemd1 /org/freedesktop/systemd1 \
- org.freedesktop.systemd1.Manager StartTransientUnit \
- "ssa(sv)a(sa(sv))" test-20933-ok.service replace 1 \
- ExecStart "a(sasb)" 1 \
- /usr/bin/sleep 2 /usr/bin/sleep 1 true \
- 0
+ org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager StartTransientUnit \
+ "ssa(sv)a(sa(sv))" test-20933-ok.service replace 1 \
+ ExecStart "a(sasb)" 1 \
+ /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+ 0
# DBus call should fail but not crash systemd
-busctl call \
- org.freedesktop.systemd1 /org/freedesktop/systemd1 \
- org.freedesktop.systemd1.Manager StartTransientUnit \
- "ssa(sv)a(sa(sv))" test-20933-bad.service replace 1 \
- ExecStart "a(sasb)" 1 \
- /usr/bin/sleep 0 true \
- 0 && { echo 'unexpected success'; exit 1; }
+(! busctl call \
+ org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager StartTransientUnit \
+ "ssa(sv)a(sa(sv))" test-20933-bad.service replace 1 \
+ ExecStart "a(sasb)" 1 \
+ /usr/bin/sleep 0 true \
+ 0)
# Same but with the empty argv in the middle
-busctl call \
- org.freedesktop.systemd1 /org/freedesktop/systemd1 \
- org.freedesktop.systemd1.Manager StartTransientUnit \
- "ssa(sv)a(sa(sv))" test-20933-bad-middle.service replace 1 \
- ExecStart "a(sasb)" 3 \
- /usr/bin/sleep 2 /usr/bin/sleep 1 true \
- /usr/bin/sleep 0 true \
- /usr/bin/sleep 2 /usr/bin/sleep 1 true \
- 0 && { echo 'unexpected success'; exit 1; }
+(! busctl call \
+ org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ org.freedesktop.systemd1.Manager StartTransientUnit \
+ "ssa(sv)a(sa(sv))" test-20933-bad-middle.service replace 1 \
+ ExecStart "a(sasb)" 3 \
+ /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+ /usr/bin/sleep 0 true \
+ /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+ 0)
systemd-analyze log-level info
# Test removal
machinectl remove testimage
test ! -f /var/lib/machines/testimage.raw
-machinectl image-status testimage && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status testimage)
# Test export of clone
machinectl export-raw testimage3 /var/tmp/testimage3.raw
test -f /var/lib/machines/testimage4.raw
machinectl image-status testimage4
test ! -f /var/lib/machines/testimage3.raw
-machinectl image-status testimage3 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status testimage3)
cmp /var/tmp/testimage.raw /var/lib/machines/testimage4.raw
# Test export of rename
# Test removal
machinectl remove testimage4
test ! -f /var/lib/machines/testimage4.raw
-machinectl image-status testimage4 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status testimage4)
# → And now, let's test directory trees ← #
# Test removal
machinectl remove scratch
test ! -f /var/lib/machines/scratch
-machinectl image-status scratchi && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch)
# Test clone
machinectl clone scratch2 scratch3
# Test removal
machinectl remove scratch2
test ! -f /var/lib/machines/scratch2
-machinectl image-status scratch2 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch2)
# Test rename
machinectl rename scratch3 scratch4
test -d /var/lib/machines/scratch4
machinectl image-status scratch4
test ! -f /var/lib/machines/scratch3
-machinectl image-status scratch3 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch3)
diff -r /var/tmp/scratch/ /var/lib/machines/scratch4
# Test removal
machinectl remove scratch4
test ! -f /var/lib/machines/scratch4
-machinectl image-status scratch4 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch4)
# Test import-tar hyphen/stdin pipe behavior
# shellcheck disable=SC2002
# Test removal
machinectl remove scratch5
test ! -f /var/lib/machines/scratch5
-machinectl image-status scratch5 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch5)
echo OK >/testok
systemctl list-sockets --show-types
systemctl list-sockets --state=listening
systemctl list-timers -a -l
-systemctl list-unit-files
-systemctl list-unit-files "*journal*"
systemctl list-jobs
systemctl list-jobs --after
systemctl list-jobs --before
systemctl list-paths
systemctl list-paths --legend=no -a "systemd*"
+test_list_unit_files() {
+ systemctl list-unit-files "$@"
+ systemctl list-unit-files "$@" "*journal*"
+}
+
+test_list_unit_files
+test_list_unit_files --root=/
+
# is-* verbs
# Should return 4 for a missing unit file
assert_rc 4 systemctl --quiet is-active not-found.service
(! systemctl is-active "$UNIT_NAME")
# enable/disable/preset
-(! systemctl is-enabled "$UNIT_NAME")
-systemctl enable "$UNIT_NAME"
-systemctl is-enabled -l "$UNIT_NAME"
-# We created a preset file for this unit above with a "disable" policy
-systemctl preset "$UNIT_NAME"
-(! systemctl is-enabled "$UNIT_NAME")
-systemctl reenable "$UNIT_NAME"
-systemctl is-enabled "$UNIT_NAME"
-systemctl preset --preset-mode=enable-only "$UNIT_NAME"
-systemctl is-enabled "$UNIT_NAME"
-systemctl preset --preset-mode=disable-only "$UNIT_NAME"
-(! systemctl is-enabled "$UNIT_NAME")
-systemctl enable --runtime "$UNIT_NAME"
-[[ -e "/run/systemd/system/multi-user.target.wants/$UNIT_NAME" ]]
-systemctl is-enabled "$UNIT_NAME"
-systemctl disable "$UNIT_NAME"
-# The unit should be still enabled, as we didn't use the --runtime switch
-systemctl is-enabled "$UNIT_NAME"
-systemctl disable --runtime "$UNIT_NAME"
-(! systemctl is-enabled "$UNIT_NAME")
+test_enable_disable_preset() {
+ (! systemctl is-enabled "$@" "$UNIT_NAME")
+ systemctl enable "$@" "$UNIT_NAME"
+ systemctl is-enabled "$@" -l "$UNIT_NAME"
+ # We created a preset file for this unit above with a "disable" policy
+ systemctl preset "$@" "$UNIT_NAME"
+ (! systemctl is-enabled "$@" "$UNIT_NAME")
+ systemctl reenable "$@" "$UNIT_NAME"
+ systemctl is-enabled "$@" "$UNIT_NAME"
+ systemctl preset "$@" --preset-mode=enable-only "$UNIT_NAME"
+ systemctl is-enabled "$@" "$UNIT_NAME"
+ systemctl preset "$@" --preset-mode=disable-only "$UNIT_NAME"
+ (! systemctl is-enabled "$@" "$UNIT_NAME")
+ systemctl enable "$@" --runtime "$UNIT_NAME"
+ [[ -e "/run/systemd/system/multi-user.target.wants/$UNIT_NAME" ]]
+ systemctl is-enabled "$@" "$UNIT_NAME"
+ systemctl disable "$@" "$UNIT_NAME"
+ # The unit should be still enabled, as we didn't use the --runtime switch
+ systemctl is-enabled "$@" "$UNIT_NAME"
+ systemctl disable "$@" --runtime "$UNIT_NAME"
+ (! systemctl is-enabled "$@" "$UNIT_NAME")
+}
+
+test_enable_disable_preset
+test_enable_disable_preset --root=/
# mask/unmask/revert
-systemctl disable "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
-systemctl mask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked ]]
-systemctl unmask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
-systemctl mask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked ]]
-systemctl revert "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
-systemctl mask --runtime "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked-runtime ]]
-# This should be a no-op without the --runtime switch
-systemctl unmask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked-runtime ]]
-systemctl unmask --runtime "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
+test_mask_unmask_revert() {
+ systemctl disable "$@" "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+ systemctl mask "$@" "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked ]]
+ systemctl unmask "$@" "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+ systemctl mask "$@" "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked ]]
+ systemctl revert "$@" "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+ systemctl mask "$@" --runtime "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked-runtime ]]
+ # This should be a no-op without the --runtime switch
+ systemctl unmask "$@" "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked-runtime ]]
+ systemctl unmask "$@" --runtime "$UNIT_NAME"
+ [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+}
+
+test_mask_unmask_revert
+test_mask_unmask_revert --root=/
# add-wants/add-requires
(! systemctl show -P Wants "$UNIT_NAME" | grep "systemd-journald.service")
done
# set-default/get-default
-target="$(systemctl get-default)"
-systemctl set-default emergency.target
-[[ "$(systemctl get-default)" == emergency.target ]]
-systemctl set-default "$target"
-[[ "$(systemctl get-default)" == "$target" ]]
+test_get_set_default() {
+ target="$(systemctl get-default "$@")"
+ systemctl set-default "$@" emergency.target
+ [[ "$(systemctl get-default "$@")" == emergency.target ]]
+ systemctl set-default "$@" "$target"
+ [[ "$(systemctl get-default "$@")" == "$target" ]]
+}
+
+test_get_set_default
+test_get_set_default --root=/
# show/status
systemctl show --property ""
STATE_DIRECTORY=/var/lib/
fi
# Bump the timeout if we're running with plain QEMU
-[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=60 || TIMEOUT=30
+[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=90 || TIMEOUT=30
systemd-dissect --no-pager /usr/share/minimal_0.raw | grep -q '✓ portable service'
systemd-dissect --no-pager /usr/share/minimal_1.raw | grep -q '✓ portable service'
status="$(portablectl is-attached --extension app0 minimal_0)"
[[ "${status}" == "running-runtime" ]]
+grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
+
timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
systemctl is-active app0.service
status="$(portablectl is-attached --extension app0 minimal_1)"
[[ "${status}" == "running-runtime" ]]
+grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_1.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
+
portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/systemd/system/app1.service
portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/systemd/system/app0.service
+grep -q -F "LogExtraFields=PORTABLE=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app0.service.d/20-portable.conf
+
+grep -q -F "LogExtraFields=PORTABLE=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app1.service.d/20-portable.conf
+
portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
# Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce.
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)
-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)
+
+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
-systemctl clean tmp-hoge.mount && { echo 'unexpected success'; exit 1; }
+(! systemctl clean tmp-hoge.mount)
test -d /etc/hoge
test -d /run/hoge
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)
-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
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
- systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
- && { echo 'unexpected success'; exit 1; }
+ (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
test -d "${path}"/zzz
test ! -L "${path}"/zzz
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}=zzz:xxx zzz:xxx2" \
-p TemporaryFileSystem="${path}" -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
- systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
- && { echo 'unexpected success'; exit 1; }
+ (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
test -L "${path}"/zzz
test -d "${path}"/private/zzz
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
- systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
- && { echo 'unexpected success'; exit 1; }
+ (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
test -d "${path}"/zzz
test ! -L "${path}"/zzz
systemctl restart getty@tty2.service
# check session
- for ((i = 0; i < 30; i++)); do
- (( i != 0 )) && sleep 1
+ for i in {1..30}; do
+ (( i > 1 )) && sleep 1
check_session && break
done
check_session
create_session
s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
- /usr/lib/systemd/tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}"
+ /usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}"
}
test_list_users() {
# 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
systemctl start "$SERVICE_NAME"
systemctl status "$SERVICE_NAME"
# The reload SHOULD fail but SHOULD NOT affect the service state
-systemctl reload "$SERVICE_NAME" && { echo 'unexpected success'; exit 1; }
+(! systemctl reload "$SERVICE_NAME")
systemctl status "$SERVICE_NAME"
systemctl stop "$SERVICE_NAME"
systemctl start "$SERVICE_NAME"
systemctl status "$SERVICE_NAME"
# The reload SHOULD fail but SHOULD NOT affect the service state
-systemctl reload "$SERVICE_NAME" && { echo 'unexpected success'; exit 1; }
+(! systemctl reload "$SERVICE_NAME")
systemctl status "$SERVICE_NAME"
systemctl stop "$SERVICE_NAME"
systemd-analyze log-level debug
# test one: Restart=on-failure should restart the service
-systemd-run --unit=one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1" \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1")
for ((secs = 0; secs < MAX_SECS; secs++)); do
- [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]] || break
- sleep 1
+ [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]] || break
+ sleep 1
done
if [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]]; then
- exit 1
+ exit 1
fi
TMP_FILE="/tmp/test-41-oneshot-restart-test"
# test two: make sure StartLimitBurst correctly limits the number of restarts
# and restarts execution of the unit from the first ExecStart=
-systemd-run --unit=two \
- -p StartLimitIntervalSec=120 \
- -p StartLimitBurst=3 \
- -p Type=oneshot \
- -p Restart=on-failure \
- -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1" \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=two \
+ -p StartLimitIntervalSec=120 \
+ -p StartLimitBurst=3 \
+ -p Type=oneshot \
+ -p Restart=on-failure \
+ -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1")
# wait for at least 3 restarts
for ((secs = 0; secs < MAX_SECS; secs++)); do
- [[ $(cat $TMP_FILE) != "aaa" ]] || break
- sleep 1
+ [[ $(cat $TMP_FILE) != "aaa" ]] || break
+ sleep 1
done
if [[ $(cat $TMP_FILE) != "aaa" ]]; then
- exit 1
+ exit 1
fi
# wait for 5 more seconds to make sure there aren't excess restarts
sleep 5
if [[ $(cat $TMP_FILE) != "aaa" ]]; then
- exit 1
+ exit 1
fi
systemd-analyze log-level info
systemd-analyze log-level debug
-systemd-run --unit=simple1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple -p ExecStopPost='/bin/touch /run/simple1' true
+systemd-run --unit=simple1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple \
+ -p ExecStopPost='/bin/touch /run/simple1' true
test -f /run/simple1
-systemd-run --unit=simple2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple -p ExecStopPost='/bin/touch /run/simple2' false \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=simple2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple \
+ -p ExecStopPost='/bin/touch /run/simple2' false)
test -f /run/simple2
-systemd-run --unit=exec1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec -p ExecStopPost='/bin/touch /run/exec1' sleep 1
+systemd-run --unit=exec1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec \
+ -p ExecStopPost='/bin/touch /run/exec1' sleep 1
test -f /run/exec1
-systemd-run --unit=exec2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec -p ExecStopPost='/bin/touch /run/exec2' sh -c 'sleep 1; false' \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=exec2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec \
+ -p ExecStopPost='/bin/touch /run/exec2' sh -c 'sleep 1; false')
test -f /run/exec2
cat >/tmp/forking1.sh <<EOF
EOF
chmod +x /tmp/forking1.sh
-systemd-run --unit=forking1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec -p ExecStopPost='/bin/touch /run/forking1' /tmp/forking1.sh
+systemd-run --unit=forking1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec \
+ -p ExecStopPost='/bin/touch /run/forking1' /tmp/forking1.sh
test -f /run/forking1
cat >/tmp/forking2.sh <<EOF
set -eux
-( sleep 4; exit 1 ) &
+(sleep 4; exit 1) &
MAINPID=\$!
disown
EOF
chmod +x /tmp/forking2.sh
-systemd-run --unit=forking2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec -p ExecStopPost='/bin/touch /run/forking2' /tmp/forking2.sh \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=forking2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec \
+ -p ExecStopPost='/bin/touch /run/forking2' /tmp/forking2.sh)
test -f /run/forking2
-systemd-run --unit=oneshot1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot -p ExecStopPost='/bin/touch /run/oneshot1' true
+systemd-run --unit=oneshot1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot \
+ -p ExecStopPost='/bin/touch /run/oneshot1' true
test -f /run/oneshot1
-systemd-run --unit=oneshot2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot -p ExecStopPost='/bin/touch /run/oneshot2' false \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=oneshot2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot \
+ -p ExecStopPost='/bin/touch /run/oneshot2' false)
test -f /run/oneshot2
-systemd-run --unit=dbus1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost -p ExecStopPost='/bin/touch /run/dbus1' \
- busctl call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus RequestName su systemd.test.ExecStopPost 4 \
- || :
+systemd-run --unit=dbus1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost \
+ -p ExecStopPost='/bin/touch /run/dbus1' \
+ busctl call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus RequestName su systemd.test.ExecStopPost 4 || :
test -f /run/dbus1
-systemd-run --unit=dbus2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost -p ExecStopPost='/bin/touch /run/dbus2' true
+systemd-run --unit=dbus2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost \
+ -p ExecStopPost='/bin/touch /run/dbus2' true
test -f /run/dbus2
# https://github.com/systemd/systemd/issues/19920
-systemd-run --unit=dbus3.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p ExecStopPost='/bin/touch /run/dbus3' true \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=dbus3.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus \
+ -p ExecStopPost='/bin/touch /run/dbus3' true)
cat >/tmp/notify1.sh <<EOF
#!/usr/bin/env bash
EOF
chmod +x /tmp/notify1.sh
-systemd-run --unit=notify1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify -p ExecStopPost='/bin/touch /run/notify1' /tmp/notify1.sh
+systemd-run --unit=notify1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify \
+ -p ExecStopPost='/bin/touch /run/notify1' /tmp/notify1.sh
test -f /run/notify1
-systemd-run --unit=notify2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify -p ExecStopPost='/bin/touch /run/notify2' true \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=notify2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify \
+ -p ExecStopPost='/bin/touch /run/notify2' true)
test -f /run/notify2
systemd-run --unit=idle1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle -p ExecStopPost='/bin/touch /run/idle1' true
test -f /run/idle1
-systemd-run --unit=idle2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle -p ExecStopPost='/bin/touch /run/idle2' false \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=idle2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle \
+ -p ExecStopPost='/bin/touch /run/idle2' false)
test -f /run/idle2
systemd-analyze log-level info
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 \
runas testuser systemctl --user log-level debug
runas testuser systemd-run --wait --user --unit=test-private-tmp-innerfile \
- -p PrivateUsers=yes -p PrivateTmp=yes \
+ -p PrivateTmp=yes \
-P touch /tmp/innerfile.txt
# File should not exist outside the job's tmp directory.
test ! -e /tmp/innerfile.txt
touch /tmp/outerfile.txt
# File should not appear in unit's private tmp.
runas testuser systemd-run --wait --user --unit=test-private-tmp-outerfile \
- -p PrivateUsers=yes -p PrivateTmp=yes \
+ -p PrivateTmp=yes \
-P test ! -e /tmp/outerfile.txt
# Confirm that creating a file in home works
test -e /home/testuser/works.txt
# Confirm that creating a file in home is blocked under read-only
-runas testuser systemd-run --wait --user --unit=test-protect-home-read-only \
- -p PrivateUsers=yes -p ProtectHome=read-only \
+(! runas testuser systemd-run --wait --user --unit=test-protect-home-read-only \
+ -p ProtectHome=read-only \
-P bash -c '
test -e /home/testuser/works.txt || exit 10
touch /home/testuser/blocked.txt && exit 11
- ' \
- && { echo 'unexpected success'; exit 1; }
+ ')
test ! -e /home/testuser/blocked.txt
# Check that tmpfs hides the whole directory
runas testuser systemd-run --wait --user --unit=test-protect-home-tmpfs \
- -p PrivateUsers=yes -p ProtectHome=tmpfs \
+ -p ProtectHome=tmpfs \
-P test ! -e /home/testuser
# Confirm that home, /root, and /run/user are inaccessible under "yes"
# shellcheck disable=SC2016
runas testuser systemd-run --wait --user --unit=test-protect-home-yes \
- -p PrivateUsers=yes -p ProtectHome=yes \
+ -p ProtectHome=yes \
-P bash -c '
test "$(stat -c %a /home)" = "0"
test "$(stat -c %a /root)" = "0"
# namespace (no CAP_SETGID in the parent namespace to write the additional
# mapping of the user supplied group and thus cannot change groups to an
# unmapped group ID)
-runas testuser systemd-run --wait --user --unit=test-group-fail \
+(! runas testuser systemd-run --wait --user --unit=test-group-fail \
-p PrivateUsers=yes -p Group=daemon \
- -P true \
- && { echo 'unexpected success'; exit 1; }
+ -P true)
# Check that with a new user namespace we can bind mount
# files and use a different root directory
runas testuser systemd-run --wait --user --unit=test-bind-mount \
- -p PrivateUsers=yes -p BindPaths=/dev/null:/etc/os-release \
+ -p BindPaths=/dev/null:/etc/os-release \
test ! -s /etc/os-release
runas testuser systemd-run --wait --user --unit=test-read-write \
- -p PrivateUsers=yes -p ReadOnlyPaths=/ \
+ -p ReadOnlyPaths=/ \
-p ReadWritePaths="/var /run /tmp" \
-p NoExecPaths=/ -p ExecPaths=/usr \
test ! -w /etc/os-release
test -s /etc/os-release
runas testuser systemd-run --wait --user --unit=test-devices \
- -p PrivateUsers=yes -p PrivateDevices=yes -p PrivateIPC=yes \
+ -p PrivateDevices=yes -p PrivateIPC=yes \
sh -c "ls -1 /dev/ | wc -l | grep -q -F 18"
# Same check as test/test-execute/exec-privatenetwork-yes.service
runas testuser systemd-run --wait --user --unit=test-network \
- -p PrivateUsers=yes -p PrivateNetwork=yes \
+ -p PrivateNetwork=yes \
/bin/sh -x -c '! ip link | grep -E "^[0-9]+: " | grep -Ev ": (lo|(erspan|gre|gretap|ip_vti|ip6_vti|ip6gre|ip6tnl|sit|tunl)0@.*):"'
-runas testuser systemd-run --wait --user --unit=test-hostname \
- -p PrivateUsers=yes -p ProtectHostname=yes \
- hostnamectl hostname foo \
- && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-hostname \
+ -p ProtectHostname=yes \
+ hostnamectl hostname foo)
-runas testuser systemd-run --wait --user --unit=test-clock \
- -p PrivateUsers=yes -p ProtectClock=yes \
- timedatectl set-time "2012-10-30 18:17:16" \
- && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-clock \
+ -p ProtectClock=yes \
+ timedatectl set-time "2012-10-30 18:17:16")
-runas testuser systemd-run --wait --user --unit=test-kernel-tunable \
- -p PrivateUsers=yes -p ProtectKernelTunables=yes \
- sh -c "echo 0 >/proc/sys/user/max_user_namespaces" \
- && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-kernel-tunable \
+ -p ProtectKernelTunables=yes \
+ sh -c "echo 0 >/proc/sys/user/max_user_namespaces")
-runas testuser systemd-run --wait --user --unit=test-kernel-mod \
- -p PrivateUsers=yes -p ProtectKernelModules=yes \
- sh -c "modprobe -r overlay && modprobe overlay" \
- && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-kernel-mod \
+ -p ProtectKernelModules=yes \
+ sh -c "modprobe -r overlay && modprobe overlay")
if sysctl kernel.dmesg_restrict=0; then
- runas testuser systemd-run --wait --user --unit=test-kernel-log \
- -p PrivateUsers=yes -p ProtectKernelLogs=yes -p LogNamespace=yes \
- dmesg \
- && { echo 'unexpected success'; exit 1; }
+ (! runas testuser systemd-run --wait --user --unit=test-kernel-log \
+ -p ProtectKernelLogs=yes -p LogNamespace=yes \
+ dmesg)
fi
unsquashfs -no-xattrs -d /tmp/img /usr/share/minimal_0.raw
runas testuser systemd-run --wait --user --unit=test-root-dir \
- -p PrivateUsers=yes -p RootDirectory=/tmp/img \
+ -p RootDirectory=/tmp/img \
grep MARKER=1 /etc/os-release
mkdir /tmp/img_bind
mount --bind /tmp/img /tmp/img_bind
runas testuser systemd-run --wait --user --unit=test-root-dir-bind \
- -p PrivateUsers=yes -p RootDirectory=/tmp/img_bind -p MountFlags=private \
+ -p RootDirectory=/tmp/img_bind -p MountFlags=private \
grep MARKER=1 /etc/os-release
umount /tmp/img_bind
if unshare --mount --user --map-root-user mount -t overlay overlay /tmp/c -o lowerdir=/tmp/a:/tmp/b; then
unsquashfs -no-xattrs -d /tmp/app2 /usr/share/app1.raw
runas testuser systemd-run --wait --user --unit=test-extension-dir \
- -p PrivateUsers=yes -p ExtensionDirectories=/tmp/app2 \
+ -p ExtensionDirectories=/tmp/app2 \
-p TemporaryFileSystem=/run -p RootDirectory=/tmp/img \
-p MountAPIVFS=yes \
grep PORTABLE_PREFIXES=app1 /usr/lib/extension-release.d/extension-release.app2
journalctl -o cat >/tmp/no-hello-world
grep "^hello world$" /tmp/hello-world
-grep "^hello world$" /tmp/no-hello-world && { echo 'unexpected success'; exit 1; }
+(! grep "^hello world$" /tmp/no-hello-world)
systemd-analyze log-level info
# shellcheck source=test/units/assert.sh
. "$(dirname "$0")"/assert.sh
+test_timedatectl() {
+ timedatectl --no-pager --help
+ timedatectl --version
+
+ timedatectl
+ timedatectl --no-ask-password
+ timedatectl status --machine=testuser@.host
+ timedatectl status
+ timedatectl show
+ timedatectl show --all
+ timedatectl show -p NTP
+ timedatectl show -p NTP --value
+ timedatectl list-timezones
+
+ if ! systemd-detect-virt -qc; then
+ systemctl enable --runtime --now systemd-timesyncd
+ timedatectl timesync-status
+ timedatectl show-timesync
+ fi
+}
+
restore_timezone() {
if [[ -f /tmp/timezone.bak ]]; then
mv /tmp/timezone.bak /etc/timezone
}
wait_mon() {
- for ((i = 0; i < 10; i++)); do
- if (( i != 0 )); then sleep 1; fi
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 1
if grep -q "$1" "$mon"; then break; fi
done
assert_in "$2" "$(cat "$mon")"
echo 'disable NTP'
timedatectl set-ntp false
- for ((i = 0; i < 10; i++)); do
- if (( i != 0 )); then sleep 1; fi
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 1
if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=inactive" ]]; then
break;
fi
timedatectl set-ntp true
wait_mon "NTP" "BOOLEAN true"
assert_ntp "true"
- for ((i = 0; i < 10; i++)); do
- if (( i != 0 )); then sleep 1; fi
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 1
if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=active" ]]; then
break;
fi
: >/failed
+test_timedatectl
test_timezone
test_adjtime
test_ntp
Description=TEST-46-HOMED
Wants=getty-pre.target
Before=getty-pre.target
-Wants=systemd-homed.service
-After=systemd-homed.service
+Requires=systemd-homed.service systemd-userdbd.socket
+After=systemd-homed.service systemd-userdbd.socket
[Service]
ExecStartPre=rm -f /failed /testok
}
wait_for_state() {
- for ((i = 0; i < 10; i++)) ; do
+ for i in {1..10}; do
+ (( i > 1 )) && sleep 0.5
homectl inspect "$1" | grep -qF "State: $2" && break
- sleep .5
done
}
fi
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
-PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz \
- && { echo 'unexpected success'; exit 1; }
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
-PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz \
- && { echo 'unexpected success'; exit 1; }
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
wait_for_state test-user inactive
homectl remove test-user
homectl remove test-user2
fi
+# userdbctl tests
+export PAGER=
+
+# Create a couple of user/group records to test io.systemd.DropIn
+# See docs/USER_RECORD.md and docs/GROUP_RECORD.md
+mkdir -p /run/userdb/
+cat >"/run/userdb/dropingroup.group" <<\EOF
+{
+ "groupName" : "dropingroup",
+ "gid" : 1000000
+}
+EOF
+cat >"/run/userdb/dropinuser.user" <<\EOF
+{
+ "userName" : "dropinuser",
+ "uid" : 2000000,
+ "realName" : "🐱",
+ "memberOf" : [
+ "dropingroup"
+ ]
+}
+EOF
+cat >"/run/userdb/dropinuser.user-privileged" <<\EOF
+{
+ "privileged" : {
+ "hashedPassword" : [
+ "$6$WHBKvAFFT9jKPA4k$OPY4D4TczKN/jOnJzy54DDuOOagCcvxxybrwMbe1SVdm.Bbr.zOmBdATp.QrwZmvqyr8/SafbbQu.QZ2rRvDs/"
+ ],
+ "sshAuthorizedKeys" : [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA//dxI2xLg4MgxIKKZv1nqwTEIlE/fdakii2Fb75pG+ foo@bar.tld",
+ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMlaqG2rTMje5CQnfjXJKmoSpEVJ2gWtx4jBvsQbmee2XbU/Qdq5+SRisssR9zVuxgg5NA5fv08MgjwJQMm+csc= hello@world.tld"
+ ]
+ }
+}
+EOF
+# Set permissions and create necessary symlinks as described in nss-systemd(8)
+chmod 0600 "/run/userdb/dropinuser.user-privileged"
+ln -svrf "/run/userdb/dropingroup.group" "/run/userdb/1000000.group"
+ln -svrf "/run/userdb/dropinuser.user" "/run/userdb/2000000.user"
+ln -svrf "/run/userdb/dropinuser.user-privileged" "/run/userdb/2000000.user-privileged"
+
+userdbctl
+userdbctl --version
+userdbctl --help --no-pager
+userdbctl --no-legend
+userdbctl --output=classic
+userdbctl --output=friendly
+userdbctl --output=table
+userdbctl --output=json | jq
+userdbctl -j --json=pretty | jq
+userdbctl -j --json=short | jq
+userdbctl --with-varlink=no
+
+userdbctl user
+userdbctl user testuser
+userdbctl user root
+userdbctl user testuser root
+userdbctl user -j testuser root | jq
+# Check only UID for the nobody user, since the name is build-configurable
+userdbctl user --with-nss=no --synthesize=yes
+userdbctl user --with-nss=no --synthesize=yes 0 root 65534
+userdbctl user dropinuser
+userdbctl user 2000000
+userdbctl user --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropinuser
+userdbctl user --with-nss=no 2000000
+(! userdbctl user '')
+(! userdbctl user 🐱)
+(! userdbctl user 🐱 '' bar)
+(! userdbctl user i-do-not-exist)
+(! userdbctl user root i-do-not-exist testuser)
+(! userdbctl user --with-nss=no --synthesize=no 0 root 65534)
+(! userdbctl user -N root nobody)
+(! userdbctl user --with-dropin=no dropinuser)
+(! userdbctl user --with-dropin=no 2000000)
+
+userdbctl group
+userdbctl group testuser
+userdbctl group root
+userdbctl group testuser root
+userdbctl group -j testuser root | jq
+# Check only GID for the nobody group, since the name is build-configurable
+userdbctl group --with-nss=no --synthesize=yes
+userdbctl group --with-nss=no --synthesize=yes 0 root 65534
+userdbctl group dropingroup
+userdbctl group 1000000
+userdbctl group --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropingroup
+userdbctl group --with-nss=no 1000000
+(! userdbctl group '')
+(! userdbctl group 🐱)
+(! userdbctl group 🐱 '' bar)
+(! userdbctl group i-do-not-exist)
+(! userdbctl group root i-do-not-exist testuser)
+(! userdbctl group --with-nss=no --synthesize=no 0 root 65534)
+(! userdbctl group --with-dropin=no dropingroup)
+(! userdbctl group --with-dropin=no 1000000)
+
+userdbctl users-in-group
+userdbctl users-in-group testuser
+userdbctl users-in-group testuser root
+userdbctl users-in-group -j testuser root | jq
+userdbctl users-in-group 🐱
+(! userdbctl users-in-group '')
+(! userdbctl users-in-group foo '' bar)
+
+userdbctl groups-of-user
+userdbctl groups-of-user testuser
+userdbctl groups-of-user testuser root
+userdbctl groups-of-user -j testuser root | jq
+userdbctl groups-of-user 🐱
+(! userdbctl groups-of-user '')
+(! userdbctl groups-of-user foo '' bar)
+
+userdbctl services
+userdbctl services -j | jq
+
+userdbctl ssh-authorized-keys dropinuser | tee /tmp/authorized-keys
+grep "ssh-ed25519" /tmp/authorized-keys
+grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+echo "my-top-secret-key 🐱" >/tmp/my-top-secret-key
+userdbctl ssh-authorized-keys dropinuser --chain /bin/cat /tmp/my-top-secret-key | tee /tmp/authorized-keys
+grep "ssh-ed25519" /tmp/authorized-keys
+grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+grep "my-top-secret-key 🐱" /tmp/authorized-keys
+(! userdbctl ssh-authorized-keys 🐱)
+(! userdbctl ssh-authorized-keys dropin-user --chain)
+(! userdbctl ssh-authorized-keys dropin-user --chain '')
+(! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /bin/false)
+
+(! userdbctl '')
+for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do
+ (! userdbctl "--$opt=''")
+ (! userdbctl "--$opt='🐱'")
+ (! userdbctl "--$opt=foo")
+ (! userdbctl "--$opt=foo" "--$opt=''" "--$opt=🐱")
+done
+
systemd-analyze log-level info
echo OK >/testok
systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1"
systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release")
+# Test image policies
+systemd-dissect --validate "${image}.gpt"
+systemd-dissect --validate "${image}.gpt" --image-policy='*'
+(! systemd-dissect --validate "${image}.gpt" --image-policy='~')
+(! systemd-dissect --validate "${image}.gpt" --image-policy='-')
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=absent)
+(! systemd-dissect --validate "${image}.gpt" --image-policy=swap=unprotected+encrypted+verity)
+systemd-dissect --validate "${image}.gpt" --image-policy=root=unprotected
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity-sig=unused+absent
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent+unprotected
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity=unused+absent)
+systemd-dissect --validate "${image}.gpt" --image-policy=root=signed
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity-sig=unused+absent)
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity=unused+absent)
+
+# Test RootImagePolicy= unit file setting
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='*' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='~' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='-' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=absent' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=verity' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=signed' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=encrypted' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+
systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/mount"
grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release"
grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release"
# ExtensionDirectories will set up an overlay
mkdir -p "${image_dir}/app0" "${image_dir}/app1" "${image_dir}/app-nodistro"
-systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistent" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
-systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
+(! systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistent" --property RootImage="${image}.raw" cat /opt/script0.sh)
+(! systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh)
systemd-dissect --mount /usr/share/app0.raw "${image_dir}/app0"
systemd-dissect --mount /usr/share/app1.raw "${image_dir}/app1"
systemd-dissect --mount /usr/share/app-nodistro.raw "${image_dir}/app-nodistro"
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)
+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
# Note, sizeof_field(struct loop_info64, lo_file_name) == 64,
# and --loop-ref accepts upto 63 characters, and udev creates symlink
# based on the name when it has upto _62_ characters.
-name="$(for (( i = 0; i < 62; i++ )); do echo -n 'x'; done)"
+name="$(for _ in {1..62}; do echo -n 'x'; done)"
LOOP="$(systemd-dissect --attach --loop-ref="$name" "${image}.raw")"
udevadm trigger -w "$LOOP"
# Detach by the /dev/loop/by-ref symlink
systemd-dissect --detach "/dev/loop/by-ref/$name"
-name="$(for (( i = 0; i < 63; i++ )); do echo -n 'x'; done)"
+name="$(for _ in {1..63}; do echo -n 'x'; done)"
LOOP="$(systemd-dissect --attach --loop-ref="$name" "${image}.raw")"
udevadm trigger -w "$LOOP"
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
+cat <<EOF >/run/confexts/test/etc/testscript
+#!/bin/bash
+echo "This should not happen"
+EOF
+chmod +x /run/confexts/test/etc/testscript
+systemd-confext merge
+grep -q -F "MARKER_CONFEXT_123" /etc/testfile
+(! /etc/testscript)
+systemd-confext status
+systemd-confext unmerge
+rm -rf /run/confexts/
+
echo OK >/testok
exit 0
systemd-analyze log-level debug
+run_with_cred_compare() {
+ local cred="${1:?}"
+ local exp="${2?}"
+ shift 2
+
+ diff <(systemd-run -p SetCredential="$cred" --wait --pipe -- systemd-creds "$@") <(echo -ne "$exp")
+}
+
+# Sanity checks
+#
+# Create a dummy "full" disk (similar to /dev/full) to check out-of-space
+# scenarios
+mkdir /tmp/full
+mount -t tmpfs -o size=1,nr_inodes=1 tmpfs /tmp/full
+
+# verb: setup
+# Run this first, otherwise any encrypted credentials wouldn't be decryptable
+# as we regnerate the host key
+rm -fv /var/lib/systemd/credential.secret
+systemd-creds setup
+test -e /var/lib/systemd/credential.secret
+rm -fv /var/lib/systemd/credential.secret
+
+# Prepare a couple of dummy credentials for the cat/list verbs
+CRED_DIR="$(mktemp -d)"
+ENC_CRED_DIR="$(mktemp -d)"
+echo foo >"$CRED_DIR/secure-or-weak"
+echo foo >"$CRED_DIR/insecure"
+echo foo | systemd-creds --name="encrypted" encrypt - - | base64 -d >"$ENC_CRED_DIR/encrypted"
+echo foo | systemd-creds encrypt - - | base64 -d >"$ENC_CRED_DIR/encrypted-unnamed"
+chmod -R 0400 "$CRED_DIR" "$ENC_CRED_DIR"
+chmod -R 0444 "$CRED_DIR/insecure"
+mkdir /tmp/empty/
+
+systemd-creds --system
+systemd-creds --no-pager --help
+systemd-creds --version
+systemd-creds has-tpm2 || :
+systemd-creds has-tpm2 -q || :
+
+# verb: list
+systemd-creds list --system
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --no-legend
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=pretty | jq
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=short | jq
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=off
+ENCRYPTED_CREDENTIALS_DIRECTORY="/tmp/empty/" CREDENTIALS_DIRECTORY="/tmp/empty/" systemd-creds list
+
+# verb: cat
+for cred in secure-or-weak insecure encrypted encrypted-unnamed; do
+ ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds cat "$cred"
+done
+run_with_cred_compare "mycred:" "" cat mycred
+run_with_cred_compare "mycred:\n" "\n" cat mycred
+run_with_cred_compare "mycred:foo" "foo" cat mycred
+run_with_cred_compare "mycred:foo" "foofoofoo" cat mycred mycred mycred
+# Note: --newline= does nothing when stdout is not a tty, which is the case here
+run_with_cred_compare "mycred:foo" "foo" --newline=yes cat mycred
+run_with_cred_compare "mycred:foo" "foo" --newline=no cat mycred
+run_with_cred_compare "mycred:foo" "foo" --newline=auto cat mycred
+run_with_cred_compare "mycred:foo" "foo" --transcode=no cat mycred
+run_with_cred_compare "mycred:foo" "foo" --transcode=0 cat mycred
+run_with_cred_compare "mycred:foo" "foo" --transcode=false cat mycred
+run_with_cred_compare "mycred:foo" "Zm9v" --transcode=base64 cat mycred
+run_with_cred_compare "mycred:Zm9v" "foo" --transcode=unbase64 cat mycred
+run_with_cred_compare "mycred:Zm9v" "foofoofoo" --transcode=unbase64 cat mycred mycred mycred
+run_with_cred_compare "mycred:Zm9vCg==" "foo\n" --transcode=unbase64 cat mycred
+run_with_cred_compare "mycred:hello world" "68656c6c6f20776f726c64" --transcode=hex cat mycred
+run_with_cred_compare "mycred:68656c6c6f20776f726c64" "hello world" --transcode=unhex cat mycred
+run_with_cred_compare "mycred:68656c6c6f20776f726c64" "hello worldhello world" --transcode=unhex cat mycred mycred
+run_with_cred_compare "mycred:68656c6c6f0a776f726c64" "hello\nworld" --transcode=unhex cat mycred
+run_with_cred_compare 'mycred:{ "foo" : "bar", "baz" : [ 3, 4 ] }' '{"foo":"bar","baz":[3,4]}\n' --json=short cat mycred
+systemd-run -p SetCredential='mycred:{ "foo" : "bar", "baz" : [ 3, 4 ] }' --wait --pipe -- systemd-creds --json=pretty cat mycred | jq
+
+# verb: encrypt/decrypt
+echo "According to all known laws of aviation..." >/tmp/cred.orig
+systemd-creds --with-key=host encrypt /tmp/cred.orig /tmp/cred.enc
+systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.{enc,dec}
+# --pretty
+cred_name="fo'''o''bar"
+cred_option="$(systemd-creds --pretty --name="$cred_name" encrypt /tmp/cred.orig -)"
+mkdir -p /run/systemd/system
+cat >/run/systemd/system/test-54-pretty-cred.service <<EOF
+[Service]
+Type=oneshot
+${cred_option:?}
+ExecStart=bash -c "diff <(systemd-creds cat \"$cred_name\") /tmp/cred.orig"
+EOF
+systemctl daemon-reload
+systemctl start test-54-pretty-cred
+rm /run/systemd/system/test-54-pretty-cred.service
+# Credential validation: name
+systemd-creds --name="foo" -H encrypt /tmp/cred.orig /tmp/cred.enc
+(! systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec)
+(! systemd-creds --name="bar" decrypt /tmp/cred.enc /tmp/cred.dec)
+systemd-creds --name="" decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.dec
+systemd-creds --name="foo" decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.{enc,dec}
+# Credential validation: time
+systemd-creds --not-after="+1d" encrypt /tmp/cred.orig /tmp/cred.enc
+(! systemd-creds --timestamp="+2d" decrypt /tmp/cred.enc /tmp/cred.dec)
+systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.{enc,dec}
+
+(! unshare -m bash -exc "mount -t tmpfs tmpfs /run/credentials && systemd-creds list")
+(! unshare -m bash -exc "mount -t tmpfs tmpfs /run/credentials && systemd-creds --system list")
+(! CREDENTIALS_DIRECTORY="" systemd-creds list)
+(! systemd-creds --system --foo)
+(! systemd-creds --system -@)
+(! systemd-creds --system --json=)
+(! systemd-creds --system --json="")
+(! systemd-creds --system --json=foo)
+(! systemd-creds --system cat)
+(! systemd-creds --system cat "")
+(! systemd-creds --system cat this-should-not-exist)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --transcode= cat mycred)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --transcode="" cat mycred)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --transcode=foo cat mycred)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --newline=foo cat mycred)
+(! systemd-run -p SetCredential=mycred:notbase64 --wait --pipe -- systemd-creds --transcode=unbase64 cat mycred)
+(! systemd-run -p SetCredential=mycred:nothex --wait --pipe -- systemd-creds --transcode=unhex cat mycred)
+(! systemd-run -p SetCredential=mycred:a --wait --pipe -- systemd-creds --transcode=unhex cat mycred)
+(! systemd-run -p SetCredential=mycred:notjson --wait --pipe -- systemd-creds --json=short cat mycred)
+(! systemd-run -p SetCredential=mycred:notjson --wait --pipe -- systemd-creds --json=pretty cat mycred)
+(! systemd-creds encrypt /foo/bar/baz -)
+(! systemd-creds decrypt /foo/bar/baz -)
+(! systemd-creds decrypt / -)
+(! systemd-creds encrypt / -)
+(! echo foo | systemd-creds --with-key=foo encrypt - -)
+(! echo {0..20} | systemd-creds decrypt - -)
+(! systemd-creds --not-after= encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --not-after="" encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --not-after="-1d" encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --timestamp= encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --timestamp="" encrypt /tmp/cred.orig /tmp/cred.enc)
+(! dd if=/dev/zero count=2M | systemd-creds --with-key=tpm2-absent encrypt - /dev/null)
+(! dd if=/dev/zero count=2M | systemd-creds --with-key=tpm2-absent decrypt - /dev/null)
+(! echo foo | systemd-creds encrypt - /tmp/full/foo)
+(! echo foo | systemd-creds encrypt - - | systemd-creds decrypt - /tmp/full/foo)
+
# Verify that the creds are properly loaded and we can read them from the service's unpriv user
systemd-run -p LoadCredential=passwd:/etc/passwd \
- -p LoadCredential=shadow:/etc/shadow \
- -p SetCredential=dog:wuff \
- -p DynamicUser=1 \
- --wait \
- --pipe \
- cat '${CREDENTIALS_DIRECTORY}/passwd' '${CREDENTIALS_DIRECTORY}/shadow' '${CREDENTIALS_DIRECTORY}/dog' >/tmp/ts54-concat
-( cat /etc/passwd /etc/shadow && echo -n wuff ) | cmp /tmp/ts54-concat
+ -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
+(cat /etc/passwd /etc/shadow && echo -n wuff) | cmp /tmp/ts54-concat
rm /tmp/ts54-concat
# Test that SetCredential= acts as fallback for LoadCredential=
systemd-run -p AssertCredential="$expected_credential" -p Type=oneshot true
# And this should fail
- systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true && { echo 'unexpected success'; exit 1; }
+ (! systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true)
fi
# Verify that the creds are immutable
-systemd-run -p LoadCredential=passwd:/etc/passwd \
- -p DynamicUser=1 \
- --wait \
- touch '${CREDENTIALS_DIRECTORY}/passwd' \
- && { echo 'unexpected success'; exit 1; }
-systemd-run -p LoadCredential=passwd:/etc/passwd \
- -p DynamicUser=1 \
- --wait \
- rm '${CREDENTIALS_DIRECTORY}/passwd' \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run -p LoadCredential=passwd:/etc/passwd \
+ -p DynamicUser=1 \
+ --unit=test-54-immutable-touch.service \
+ --wait \
+ touch '${CREDENTIALS_DIRECTORY}/passwd')
+(! systemd-run -p LoadCredential=passwd:/etc/passwd \
+ -p DynamicUser=1 \
+ --unit=test-54-immutable-rm.service \
+ --wait \
+ rm '${CREDENTIALS_DIRECTORY}/passwd')
# Check directory-based loading
mkdir -p /tmp/ts54-creds/sub
echo -n c >/tmp/ts54-creds/baz
echo -n d >/tmp/ts54-creds/sub/qux
systemd-run -p LoadCredential=cred:/tmp/ts54-creds \
- -p DynamicUser=1 \
- --wait \
- --pipe \
- cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
- '${CREDENTIALS_DIRECTORY}/cred_bar' \
- '${CREDENTIALS_DIRECTORY}/cred_baz' \
- '${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat
-( echo -n abcd ) | cmp /tmp/ts54-concat
+ -p DynamicUser=1 \
+ --unit=test-54-dir.service \
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
+ '${CREDENTIALS_DIRECTORY}/cred_bar' \
+ '${CREDENTIALS_DIRECTORY}/cred_baz' \
+ '${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat
+cmp /tmp/ts54-concat <(echo -n abcd)
rm /tmp/ts54-concat
rm -rf /tmp/ts54-creds
systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
systemd-run -p LoadCredentialEncrypted=test-54:/tmp/test-54-ciphertext \
- --wait \
- --pipe \
- cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
echo -n $RANDOM >/tmp/test-54-plaintext
systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
systemd-run -p SetCredentialEncrypted=test-54:"$(cat /tmp/test-54-ciphertext)" \
- --wait \
- --pipe \
- cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
+ --wait \
+ --pipe \
+ cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
rm /tmp/test-54-plaintext /tmp/test-54-ciphertext
fi
+# https://github.com/systemd/systemd/issues/27275
+systemd-run -p DynamicUser=yes -p 'LoadCredential=os:/etc/os-release' \
+ -p 'ExecStartPre=true' \
+ -p 'ExecStartPre=systemd-creds cat os' \
+ --unit=test-54-exec-start.service \
+ --wait \
+ --pipe \
+ true | cmp /etc/os-release
+
systemd-analyze log-level info
echo OK >/testok
/tmp/test56-exit-cgroup.sh 'systemctl stop two'
# false exec condition: systemd-run should exit immediately with status code: 1
-systemd-run --wait --unit=three -p Type=notify -p ExitType=cgroup \
+(! systemd-run --wait --unit=three -p Type=notify -p ExitType=cgroup \
-p ExecCondition=false \
- /tmp/test56-exit-cgroup.sh \
- && { echo 'unexpected success'; exit 1; }
+ /tmp/test56-exit-cgroup.sh)
# service should exit uncleanly (main process exits with SIGKILL)
-systemd-run --wait --unit=four -p Type=notify -p ExitType=cgroup \
- /tmp/test56-exit-cgroup.sh 'systemctl kill --signal 9 four' \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-run --wait --unit=four -p Type=notify -p ExitType=cgroup \
+ /tmp/test56-exit-cgroup.sh 'systemctl kill --signal 9 four')
# Multiple level process tree, parent process exits quickly
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Failed Dependency Unit
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/sh -c "if [ -f /tmp/testsuite-57-retry-fail ]; then exit 0; else exit 1; fi"
+Restart=no
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upheld Unit
+Requires=testsuite-57-retry-fail.service
+After=testsuite-57-retry-fail.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/echo ok
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upholding Unit
+Upholds=testsuite-57-retry-upheld.service
+
+[Service]
+ExecStart=/bin/sleep infinity
if [ -f /tmp/testsuite-57.counter ] ; then
read -r counter < /tmp/testsuite-57.counter
- counter=$(("$counter" + 1))
+ counter=$((counter + 1))
else
counter=0
fi
systemctl stop testsuite-57-uphold.service
+# Idea is this:
+# 1. we start testsuite-57-retry-uphold.service
+# 2. which through Uphold= starts testsuite-57-retry-upheld.service
+# 3. which through Requires= starts testsuite-57-retry-fail.service
+# 4. which fails as /tmp/testsuite-57-retry-fail does not exist, so testsuite-57-retry-upheld.service
+# is no longer restarted
+# 5. we create /tmp/testsuite-57-retry-fail
+# 6. now testsuite-57-retry-upheld.service will be restarted since upheld, and its dependency will
+# be satisfied
+
+rm -f /tmp/testsuite-57-retry-fail
+systemctl start testsuite-57-retry-uphold.service
+
+while ! systemctl is-failed testsuite-57-retry-fail.service ; do
+ sleep .5
+done
+
+systemctl is-active testsuite-57-retry-upheld.service && { echo 'unexpected success'; exit 1; }
+
+touch /tmp/testsuite-57-retry-fail
+
+while ! systemctl is-active testsuite-57-retry-upheld.service ; do
+ sleep .5
+done
+
+systemctl stop testsuite-57-retry-uphold.service testsuite-57-retry-fail.service testsuite-57-retry-upheld.service
+
# Idea is this:
# 1. we start testsuite-57-prop-stop-one.service
# 2. which through Wants=/After= pulls in testsuite-57-prop-stop-two.service 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 "$@"
}
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
# Trigger the mount ratelimiting
cd "$(mktemp -d)"
mkdir foo
- for ((i = 0; i < 50; i++)); do
+ for _ in {1..50}; do
mount --bind foo foo
umount foo
done
# shellcheck disable=SC2064
trap "rm -f /run/systemd/system/tmp-hoge.mount '$mount_mytmpfs'" RETURN
- for ((i = 0; i < 10; i++)); do
+ for _ in {1..10}; do
systemctl --no-block start tmp-hoge.mount
sleep ".$RANDOM"
systemctl daemon-reexec
local i
- for (( i = 0; i < 20; i++ )); do
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
if check_device_units 0 "$@"; then
return 0
fi
- sleep .5
done
check_device_units 1 "$@"
}
testcase_nvme_basic() {
+ local expected_symlinks=()
+ local i
+
+ for (( i = 0; i < 5; i++ )); do
+ expected_symlinks+=(
+ # both replace mode provides the same devlink
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1
+ )
+ done
+ for (( i = 5; i < 10; i++ )); do
+ expected_symlinks+=(
+ # old replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i"
+ # newer replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1
+ )
+ done
+ for (( i = 10; i < 15; i++ )); do
+ expected_symlinks+=(
+ # old replace mode does not provide devlink, as serial contains "/"
+ # newer replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1
+ )
+ done
+ for (( i = 15; i < 20; i++ )); do
+ expected_symlinks+=(
+ # old replace mode does not provide devlink, as serial contains "/"
+ # newer replace mode
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"
+ # with nsid
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"_1
+ )
+ done
+
+ udevadm settle
+ ls /dev/disk/by-id
+ for i in "${expected_symlinks[@]}"; do
+ udevadm wait --settle --timeout=30 "$i"
+ done
+
lsblk --noheadings | grep "^nvme"
- [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 28 ]]
+ [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]]
}
testcase_nvme_subsystem() {
local expected_symlinks=(
# Controller(s)
/dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_16
+ /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_17
# Shared namespaces
/dev/disk/by-path/pci-*-nvme-16
/dev/disk/by-path/pci-*-nvme-17
# Sanity checks
#
-# We can't really test time, blame, critical-chain and plot verbs here, as
+# We can't really test time, critical-chain and plot verbs here, as
# the testsuite service is a part of the boot transaction, so let's assume
# they fail
systemd-analyze || :
systemd-analyze time || :
-systemd-analyze blame || :
systemd-analyze critical-chain || :
+# blame
+systemd-analyze blame
+systemd-run --wait --user --pipe -M testuser@.host systemd-analyze blame
# plot
systemd-analyze plot >/dev/null || :
systemd-analyze plot --json=pretty >/dev/null || :
systemd-analyze cat-config systemd/system.conf systemd/journald.conf >/dev/null
systemd-analyze cat-config systemd/system.conf foo/bar systemd/journald.conf >/dev/null
systemd-analyze cat-config foo/bar
+# security
+systemd-analyze security
+systemd-analyze security --json=off
+systemd-analyze security --json=pretty | jq
+systemd-analyze security --json=short | jq
if [[ ! -v ASAN_OPTIONS ]]; then
# check that systemd-analyze cat-config paths work in a chroot
set +e
# Default behaviour is to recurse through all dependencies when unit is loaded
-systemd-analyze verify --root=/tmp/img/ testfile.service \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --root=/tmp/img/ testfile.service)
# As above, recurses through all dependencies when unit is loaded
-systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service)
# Recurses through unit file and its direct dependencies when unit is loaded
-systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service)
set -e
set +e
# Non-zero exit status since all associated dependencies are recursively loaded when the unit file is loaded
-systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service)
set -e
rm /tmp/testfile.service
# Alias a unit file's name on disk (see #20061)
cp /tmp/testfile.service /tmp/testsrvc
-systemd-analyze verify /tmp/testsrvc \
- && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify /tmp/testsrvc)
systemd-analyze verify /tmp/testsrvc:alias.service
# Zero exit status since the value used for comparison determine exposure to security threats is by default 100
systemd-analyze security --offline=true /tmp/testfile.service
-set +e
#The overall exposure level assigned to the unit is greater than the set threshold
-systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service \
- && { echo 'unexpected success'; exit 1; }
-set -e
+(! systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service)
# Ensure we print the list of ACLs, see https://github.com/systemd/systemd/issues/23185
systemd-analyze security --offline=true /tmp/testfile.service | grep -q -F "/dev/sda"
--profile=strict \
--root=/tmp/img/ testfile.service
-set +e
# The trusted profile doesn't add any sanboxing options
-systemd-analyze security --threshold=25 --offline=true \
+(! systemd-analyze security --threshold=25 --offline=true \
--security-policy=/tmp/testfile.json \
--profile=/usr/lib/systemd/portable/profile/trusted/service.conf \
- --root=/tmp/img/ testfile.service \
- && { echo 'unexpected success'; exit 1; }
+ --root=/tmp/img/ testfile.service)
-systemd-analyze security --threshold=50 --offline=true \
+(! systemd-analyze security --threshold=50 --offline=true \
--security-policy=/tmp/testfile.json \
- --root=/tmp/img/ testfile.service \
- && { echo 'unexpected success'; exit 1; }
-set -e
+ --root=/tmp/img/ testfile.service)
rm /tmp/img/usr/lib/systemd/system/testfile.service
check deny yes /run/systemd/transient/"$name"
check deny no "$name"
+# Let's also test the "image-policy" verb
+
+systemd-analyze image-policy '*' 2>&1 | grep -q -F "Long form: =verity+signed+encrypted+unprotected+unused+absent"
+systemd-analyze image-policy '-' 2>&1 | grep -q -F "Long form: =unused+absent"
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -F "Long form: usr=verity:home=encrypted:=unused+absent"
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^home \+encrypted \+'
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr \+verity \+'
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^root \+ignore \+'
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr-verity \+unprotected \+'
+
+(! systemd-analyze image-policy 'doedel')
+
systemd-analyze log-level info
echo OK >/testok
systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto $img
# Enroll unlock with default PCR policy
-env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Check with wrong PCR
tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
-/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
+(! /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
# Enroll unlock with PCR+PIN policy
systemd-cryptenroll --wipe-slot=tpm2 $img
-env PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img
-env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
+PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img
+PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Check failure with wrong PIN
-env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
+(! PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
# Check LUKS2 token plugin unlock (i.e. without specifying tpm2-device=auto)
if cryptsetup --help | grep -q 'LUKS2 external token plugin support is compiled-in' && \
[ -f "$(cryptsetup --help | sed -n -r 's/.*LUKS2 external token plugin path: (.*)\./\1/p')/libcryptsetup-token-systemd-tpm2.so" ]; then
- env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1
+ PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Check failure with wrong PIN
- env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1 && { echo 'unexpected success'; exit 1; }
+ (! PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1)
else
echo 'cryptsetup has no LUKS2 token plugin support, skipping'
fi
# Check failure with wrong PCR (and correct PIN)
tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
-env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
+(! PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
# Enroll unlock with PCR 0+7
systemd-cryptenroll --wipe-slot=tpm2 $img
-env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
/usr/lib/systemd/systemd-cryptsetup detach test-volume
# Invalidate PCR, decrypting should fail now
tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
- systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" >/dev/null && { echo 'unexpected success'; exit 1; }
+ (! systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" >/dev/null)
# Sign new PCR state, decrypting should work now.
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: >"/tmp/pcrsign.sig2"
# After extending the PCR things should fail
tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
- SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
- SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
+ (! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1)
+ (! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1)
# But once we sign the current PCRs, we should be able to unlock again
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: >"/tmp/pcrsign.sig3"
# Sign one more phase, this should
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig4" >"/tmp/pcrsign.sig5"
- ( ! cmp "/tmp/pcrsign.sig4" "/tmp/pcrsign.sig5" )
+ (! cmp "/tmp/pcrsign.sig4" "/tmp/pcrsign.sig5")
# Should still be good to unlock, given the old entry still exists
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig5",headless=1
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)
+
+(! systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo)
+
+(! systemd-cryptenroll --fido2-with-client-pin=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-client-pin=false $img_2
+
+(! systemd-cryptenroll --fido2-with-user-presence=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-user-presence=false $img_2
+
+(! systemd-cryptenroll --fido2-with-user-verification=1234 $img_2)
+
+(! systemd-cryptenroll --tpm2-with-pin=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-user-verification=false $img_2
+
+#arg_enroll_type
+(! systemd-cryptenroll --recovery-key --password $img_2)
+
+(! systemd-cryptenroll --password --recovery-key $img_2)
+
+(! systemd-cryptenroll --password --fido2-device=auto $img_2)
+
+(! systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2)
+
+(! systemd-cryptenroll --password --tpm2-device=auto $img_2)
+
+#arg_unlock_type
+(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2)
+
+(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2)
+
+#fido2_cred_algorithm
+(! systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2)
+
+#tpm2_errors
+(! systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2)
+
+(! systemd-cryptenroll --tpm2-pcrs=key $img_2)
+
+(! systemd-cryptenroll --tpm2-pcrs=44+8 $img_2)
+
+systemd-cryptenroll --tpm2-pcrs=8 $img_2
+
+(! systemd-cryptenroll --tpm2-pcrs=hello $img_2)
+
+systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config $img_2
+
+#wipe_slots
+(! systemd-cryptenroll --wipe-slot $img_2)
+
+(! systemd-cryptenroll --wipe-slot=10240000 $img_2)
+
+#fido2_multiple_auto
+(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2)
+
echo OK >/testok
exit 0
wait_vconsole_setup() {
local i ss
- for ((i = 0; i < 20; i++)); do
- if (( i != 0 )); then sleep .5; fi
+ for i in {1..20}; do
+ (( i > 1 )) && sleep 0.5
ss="$(systemctl --property SubState --value show systemd-vconsole-setup.service)"
if [[ "$ss" == "exited" || "$ss" == "dead" || "$ss" == "condition" ]]; then
return 0
assert_not_in "X11 Options:" "$output"
}
+test_validate() {
+ if [[ -z "$(localectl list-keymaps)" ]]; then
+ echo "No vconsole keymap installed, skipping test."
+ return
+ fi
+
+ if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then
+ echo "No x11 keymap installed, skipping test."
+ return
+ fi
+
+ backup_keymap
+ trap restore_keymap RETURN
+
+ # clear previous settings
+ systemctl stop systemd-localed.service
+ wait_vconsole_setup
+ rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+ # create invalid configs
+ cat >/etc/vconsole.conf <<EOF
+KEYMAP=foobar
+XKBLAYOUT=hogehoge
+EOF
+
+ # confirm that the invalid settings are not shown
+ output=$(localectl)
+ assert_in "VC Keymap: .unset." "$output"
+ if [[ "$output" =~ "X11 Layout: hogehoge" ]]; then
+ # Debian/Ubuntu build systemd without xkbcommon.
+ echo "systemd built without xkbcommon, skipping test."
+ return
+ fi
+ assert_in "X11 Layout: .unset." "$output"
+
+ # only update the virtual console keymap
+ assert_rc 0 localectl --no-convert set-keymap us
+
+ output=$(localectl)
+ assert_in "VC Keymap: us" "$output"
+ assert_in "X11 Layout: .unset." "$output"
+
+ output=$(cat /etc/vconsole.conf)
+ assert_in "KEYMAP=us" "$output"
+ assert_not_in "XKBLAYOUT=" "$output"
+
+ # clear previous settings
+ systemctl stop systemd-localed.service
+ wait_vconsole_setup
+ rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+ # create invalid configs
+ cat >/etc/vconsole.conf <<EOF
+KEYMAP=foobar
+XKBLAYOUT=hogehoge
+EOF
+
+ # confirm that the invalid settings are not shown
+ output=$(localectl)
+ assert_in "VC Keymap: .unset." "$output"
+ assert_in "X11 Layout: .unset." "$output"
+
+ # only update the X11 keyboard layout
+ assert_rc 0 localectl --no-convert set-x11-keymap us
+
+ output=$(localectl)
+ assert_in "VC Keymap: .unset." "$output"
+ assert_in "X11 Layout: us" "$output"
+
+ output=$(cat /etc/vconsole.conf)
+ assert_not_in "KEYMAP=" "$output"
+ assert_in "XKBLAYOUT=us" "$output"
+
+ # clear previous settings
+ systemctl stop systemd-localed.service
+ wait_vconsole_setup
+ rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+ # create invalid configs
+ cat >/etc/vconsole.conf <<EOF
+KEYMAP=foobar
+XKBLAYOUT=hogehoge
+EOF
+
+ # update the virtual console keymap with conversion
+ assert_rc 0 localectl set-keymap us
+
+ output=$(localectl)
+ assert_in "VC Keymap: us" "$output"
+ assert_in "X11 Layout: us" "$output"
+
+ output=$(cat /etc/vconsole.conf)
+ assert_in "KEYMAP=us" "$output"
+ assert_in "XKBLAYOUT=us" "$output"
+}
+
: >/failed
+# Make sure the content of kbd-model-map is the one that the tests expect
+# regardless of the version installed 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
test_x11_keymap
test_convert
+test_validate
touch /testok
rm /failed
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Make sure the binary name fits into 15 characters
+CORE_TEST_BIN="/tmp/test-dump"
+CORE_TEST_UNPRIV_BIN="/tmp/test-usr-dump"
+MAKE_DUMP_SCRIPT="/tmp/make-dump"
+# Unset $PAGER so we don't have to use --no-pager everywhere
+export PAGER=
+
+at_exit() {
+ rm -fv -- "$CORE_TEST_BIN" "$CORE_TEST_UNPRIV_BIN" "$MAKE_DUMP_SCRIPT"
+}
+
+trap at_exit EXIT
+
+if systemd-detect-virt -cq; then
+ echo "Running in a container, skipping the systemd-coredump test..."
+ exit 0
+fi
+
+# Check that we're the ones to receive coredumps
+sysctl kernel.core_pattern | grep systemd-coredump
+
+# Prepare "fake" binaries for coredumps, so we can properly exercise
+# the matching stuff too
+cp -vf /bin/sleep "${CORE_TEST_BIN:?}"
+cp -vf /bin/sleep "${CORE_TEST_UNPRIV_BIN:?}"
+# Simple script that spawns given "fake" binary and then kills it with
+# given signal
+cat >"${MAKE_DUMP_SCRIPT:?}" <<\EOF
+#!/bin/bash -ex
+
+bin="${1:?}"
+sig="${2:?}"
+
+ulimit -c unlimited
+"$bin" infinity &
+pid=$!
+sleep 1
+kill -s "$sig" "$pid"
+# This should always fail
+! wait "$pid"
+EOF
+chmod +x "$MAKE_DUMP_SCRIPT"
+
+# Privileged stuff
+[[ "$(id -u)" -eq 0 ]]
+# Trigger a couple of coredumps
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP"
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT"
+# In the tests we store the coredumps in journals, so let's generate a couple
+# with Storage=external as well
+mkdir -p /run/systemd/coredump.conf.d/
+printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP"
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT"
+rm -fv /run/systemd/coredump.conf.d/99-external.conf
+# Wait a bit for the coredumps to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_BIN | wc -l) -lt 4 ]]; do sleep 1; done"
+
+coredumpctl
+SYSTEMD_LOG_LEVEL=debug coredumpctl
+coredumpctl --help
+coredumpctl --version
+coredumpctl --no-pager --no-legend
+coredumpctl --all
+coredumpctl -1
+coredumpctl -n 1
+coredumpctl --reverse
+coredumpctl -F COREDUMP_EXE
+coredumpctl --json=short | jq
+coredumpctl --json=pretty | jq
+coredumpctl --json=off
+coredumpctl --root=/
+coredumpctl --directory=/var/log/journal
+coredumpctl --file="/var/log/journal/$(</etc/machine-id)/system.journal"
+coredumpctl --since=@0
+coredumpctl --since=yesterday --until=tomorrow
+# We should have a couple of externally stored coredumps
+coredumpctl --field=COREDUMP_FILENAME | tee /tmp/coredumpctl.out
+grep "/var/lib/systemd/coredump/core" /tmp/coredumpctl.out
+rm -f /tmp/coredumpctl.out
+
+coredumpctl info
+coredumpctl info "$CORE_TEST_BIN"
+coredumpctl info /foo /bar/ /baz "$CORE_TEST_BIN"
+coredumpctl info "${CORE_TEST_BIN##*/}"
+coredumpctl info foo bar baz "${CORE_TEST_BIN##*/}"
+coredumpctl info COREDUMP_EXE="$CORE_TEST_BIN"
+coredumpctl info COREDUMP_EXE=aaaaa COREDUMP_EXE= COREDUMP_EXE="$CORE_TEST_BIN"
+
+coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN"
+SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$CORE_TEST_BIN"
+coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_BIN##*/}"
+
+coredumpctl dump "$CORE_TEST_BIN" >/tmp/core.redirected
+test -s /tmp/core.redirected
+coredumpctl dump -o /tmp/core.output "${CORE_TEST_BIN##*/}"
+test -s /tmp/core.output
+rm -f /tmp/core.{output,redirected}
+
+# Unprivileged stuff
+# Related issue: https://github.com/systemd/systemd/issues/26912
+UNPRIV_CMD=(systemd-run --user --wait --pipe -M "testuser@.host" --)
+# Trigger a couple of coredumps as an unprivileged user
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP"
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT"
+# In the tests we store the coredumps in journals, so let's generate a couple
+# with Storage=external as well
+mkdir -p /run/systemd/coredump.conf.d/
+printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP"
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT"
+rm -fv /run/systemd/coredump.conf.d/99-external.conf
+# Wait a bit for the coredumps to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_UNPRIV_BIN | wc -l) -lt 4 ]]; do sleep 1; done"
+
+# root should see coredumps from both binaries
+coredumpctl info "$CORE_TEST_UNPRIV_BIN"
+coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}"
+# The test user should see only their own coredumps
+"${UNPRIV_CMD[@]}" coredumpctl
+"${UNPRIV_CMD[@]}" coredumpctl info "$CORE_TEST_UNPRIV_BIN"
+"${UNPRIV_CMD[@]}" coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}"
+(! "${UNPRIV_CMD[@]}" coredumpctl info --all "$CORE_TEST_BIN")
+(! "${UNPRIV_CMD[@]}" coredumpctl info --all "${CORE_TEST_BIN##*/}")
+# We should have a couple of externally stored coredumps
+"${UNPRIV_CMD[@]}" coredumpctl --field=COREDUMP_FILENAME | tee /tmp/coredumpctl.out
+grep "/var/lib/systemd/coredump/core" /tmp/coredumpctl.out
+rm -f /tmp/coredumpctl.out
+
+"${UNPRIV_CMD[@]}" coredumpctl debug --debugger=/bin/true "$CORE_TEST_UNPRIV_BIN"
+"${UNPRIV_CMD[@]}" coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_UNPRIV_BIN##*/}"
+
+"${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_UNPRIV_BIN" >/tmp/core.redirected
+test -s /tmp/core.redirected
+"${UNPRIV_CMD[@]}" coredumpctl dump -o /tmp/core.output "${CORE_TEST_UNPRIV_BIN##*/}"
+test -s /tmp/core.output
+rm -f /tmp/core.{output,redirected}
+(! "${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_BIN" >/dev/null)
+
+# --backtrace mode
+# Pass one of the existing journal coredump records to systemd-coredump and
+# use our PID as the source to make matching the coredump later easier
+# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT HOSTNAME
+journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509994 12345 mymachine
+# Wait a bit for the coredump to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -eq 0 ]]; do sleep 1; done"
+coredumpctl info "$$"
+coredumpctl info COREDUMP_HOSTNAME="mymachine"
+
+
+(! coredumpctl --hello-world)
+(! coredumpctl -n 0)
+(! coredumpctl -n -1)
+(! coredumpctl --file=/dev/null)
+(! coredumpctl --since=0)
+(! coredumpctl --until='')
+(! coredumpctl --since=today --until=yesterday)
+(! coredumpctl --directory=/ --root=/)
+(! coredumpctl --json=foo)
+(! coredumpctl -F foo -F bar)
+(! coredumpctl list 0)
+(! coredumpctl list -- -1)
+(! coredumpctl list '')
+(! coredumpctl info /../.~=)
+(! coredumpctl info '')
+(! coredumpctl dump --output=/dev/full "$CORE_TEST_BIN")
+(! coredumpctl dump --output=/dev/null --output=/dev/null "$CORE_TEST_BIN")
+(! coredumpctl debug --debugger=/bin/false)
+(! coredumpctl debug --debugger=/bin/true --debugger-arguments='"')
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"
# shellcheck source=test/units/assert.sh
. "$(dirname "$0")"/assert.sh
-: >/failed
-
at_exit() {
if [[ -v NSPAWN_NAME && -e "/var/lib/machines/$NSPAWN_NAME" ]]; then
rm -fvr "/var/lib/machines/$NSPAWN_NAME" "/etc/systemd/nspawn/$NSPAWN_NAME" "new"
fi
-
- return 0
}
trap at_exit EXIT
EDITOR='mv new' script -ec 'machinectl edit "$NSPAWN_NAME"' /dev/null
printf '%s\n' '[Exec]' 'Boot=false' | cmp - "/etc/systemd/nspawn/$NSPAWN_NAME"
-
-touch /testok
-rm /failed
--- /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
# Test for resolvectl, resolvconf
systemctl unmask systemd-resolved.service
-systemctl start systemd-resolved.service
+systemctl enable --now systemd-resolved.service
systemctl service-log-level systemd-resolved.service debug
ip link add hoge type dummy
ip link add hoge.foo type dummy
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-80-NOTIFYACCESS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
+StandardOutput=journal+console
+StandardError=journal+console
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+: >/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
+
+sync_in a
+
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
+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 unprivileged process"
+assert_rc 3 systemctl --quiet is-active notify.service
+
+sync_out d
+sync_in e
+
+systemctl --quiet is-active notify.service
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK"
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none"
+
+systemctl stop notify.service
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
+
+rm /tmp/syncfifo1 /tmp/syncfifo2
+
+# Now test basic fdstore behaviour
+
+MYSCRIPT="/tmp/myscript$RANDOM.sh"
+cat >> "$MYSCRIPT" <<'EOF'
+#!/usr/bin/env bash
+set -eux
+set -o pipefail
+test "$FDSTORE" -eq 7
+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"
+
+systemd-analyze log-level debug
+
+# Test fdstore pinning (this will pull in fdstore-pin.service fdstore-nopin.service)
+systemctl start fdstore-pin.target
+
+assert_eq "$(systemctl show fdstore-pin.service -P FileDescriptorStorePreserve)" yes
+assert_eq "$(systemctl show fdstore-nopin.service -P FileDescriptorStorePreserve)" restart
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
+
+# The file descriptor store should survive service restarts
+systemctl restart fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
+
+# It should not survive the service stop plus a later start (unless pinned)
+systemctl stop fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
+
+systemctl start fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
+
+systemctl stop fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
+
+systemctl clean fdstore-pin.service --what=fdstore
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
+
+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/early/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/early/masked.service" /dev/null
+link_eq "$OUT_DIR/early/masked.socket" /dev/null
+link_endswith "$OUT_DIR/early/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/early/default.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/early/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/early/masked-initrd.service"
+test ! -h "$OUT_DIR/early/default.target.wants/wants-initrd.service"
+test ! -h "$OUT_DIR/early/default.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/early/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/early/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/early/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+grep -F "/dev/tty666" "$OUT_DIR/early/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/early/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/early/masked.service" /dev/null
+link_eq "$OUT_DIR/early/masked.socket" /dev/null
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+test ! -d "$OUT_DIR/early/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/early/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/early/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/early/masked-no-suffix.service"
+test ! -h "$OUT_DIR/early/masked.service"
+test ! -h "$OUT_DIR/early/masked.socket"
+test ! -h "$OUT_DIR/early/initrd.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/early/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/early/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/early/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/early/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+test ! -d "$OUT_DIR/early/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"
+
+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,x-systemd.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:?}/normal"
+ # 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")"
+ if [[ "$where" == / ]]; then
+ # This option is ignored for rootfs mounts
+ test ! -e "$out_dir/$supp"
+ (! link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp")
+ else
+ test -e "$out_dir/$supp"
+ link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp"
+ fi
+ 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
+}
+
+: "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/normal/sysroot.mount"
+test -e "$OUT_DIR/normal/systemd-fsck-root.service"
+link_eq "$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+link_eq "$OUT_DIR/normal/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")
+
+: "fstab-generator: kernel args - systemd.swap=0"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="systemd.swap=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+# No swap units should get created here
+[[ "$(find "$OUT_DIR" -name "*.swap" | wc -l)" -eq 0 ]]
+
+# Possible TODO
+# - combine the rootfs & usrfs arguments and mix them with fstab entries
+# - systemd.volatile=
+: "fstab-generator: kernel args - root= + rootfstype= + rootflags="
+# shellcheck disable=SC2034
+EXPECTED_FSTAB=(
+ "/dev/disk/by-label/rootfs / ext4 noexec,ro 0 1"
+)
+CMDLINE="root=LABEL=rootfs rootfstype=ext4 rootflags=noexec"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+# The /proc/cmdline here is a dummy value to tell the in_initrd_host() function
+# we're parsing host's fstab, but it's all on the kernel cmdline instead
+SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB=/proc/cmdline check_fstab_mount_units EXPECTED_FSTAB "$OUT_DIR"
+
+# This is a very basic sanity test that involves manual checks, since adding it
+# to the check_fstab_mount_units() function would make it way too complex
+# (yet another possible TODO)
+: "fstab-generator: kernel args - mount.usr= + mount.usrfstype= + mount.usrflags="
+CMDLINE="mount.usr=UUID=be780f43-8803-4a76-9732-02ceda6e9808 mount.usrfstype=ext4 mount.usrflags=noexec,nodev"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+cat "$OUT_DIR/normal/sysroot-usr.mount" "$OUT_DIR/normal/sysusr-usr.mount"
+# The general idea here is to mount the device to /sysusr/usr and then
+# bind-mount /sysusr/usr to /sysroot/usr
+grep -qE "^What=/dev/disk/by-uuid/be780f43-8803-4a76-9732-02ceda6e9808$" "$OUT_DIR/normal/sysusr-usr.mount"
+grep -qE "^Where=/sysusr/usr$" "$OUT_DIR/normal/sysusr-usr.mount"
+grep -qE "^Type=ext4$" "$OUT_DIR/normal/sysusr-usr.mount"
+grep -qE "^Options=noexec,nodev,ro$" "$OUT_DIR/normal/sysusr-usr.mount"
+link_eq "$OUT_DIR/normal/initrd-usr-fs.target.requires/sysusr-usr.mount" "../sysusr-usr.mount"
+grep -qE "^What=/sysusr/usr$" "$OUT_DIR/normal/sysroot-usr.mount"
+grep -qE "^Where=/sysroot/usr$" "$OUT_DIR/normal/sysroot-usr.mount"
+grep -qE "^Options=bind$" "$OUT_DIR/normal/sysroot-usr.mount"
+link_eq "$OUT_DIR/normal/initrd-fs.target.requires/sysroot-usr.mount" "../sysroot-usr.mount"
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+# Disable history expansion so we don't have to escape ! in strings below
+set +o histexpand
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-getty-generator"
+OUT_DIR="$(mktemp -d /tmp/getty-generator.XXX)"
+
+at_exit() {
+ rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+if in_container; then
+ # Do a limited test in a container, as writing to /dev is usually restrited
+ : "getty-generator: \$container_ttys env (container)"
+ # In a container we allow only /dev/pts/* ptys
+ PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+
+ # console-getty.service is always pulled in in containers
+ link_endswith "$OUT_DIR/normal/getty.target.wants/console-getty.service" "/lib/systemd/system/console-getty.service"
+ link_endswith "$OUT_DIR/normal/getty.target.wants/container-getty@0.service" "/lib/systemd/system/container-getty@.service"
+ test ! -e "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service"
+ test ! -h "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service"
+
+ exit 0
+fi
+
+DUMMY_ACTIVE_CONSOLES=(
+ "hvc99"
+ "xvc99"
+ "hvsi99"
+ "sclp_line99"
+ "ttysclp99"
+ "3270!tty99"
+ "dummy99"
+)
+DUMMY_INACTIVE_CONSOLES=(
+ "inactive99"
+ "xvc199"
+)
+DUMMY_CONSOLES=(
+ "${DUMMY_ACTIVE_CONSOLES[@]}"
+ "${DUMMY_INACTIVE_CONSOLES[@]}"
+)
+# Create a bunch of dummy consoles
+for console in "${DUMMY_CONSOLES[@]}"; do
+ mknod "/dev/$console" c 4 0
+done
+# Sneak in one "not-a-tty" console
+touch /dev/notatty99
+# Temporarily replace /sys/class/tty/console/active with our list of dummy
+# consoles so getty-generator can process them
+echo -ne "${DUMMY_ACTIVE_CONSOLES[@]}" /dev/notatty99 >/tmp/dummy-active-consoles
+mount -v --bind /tmp/dummy-active-consoles /sys/class/tty/console/active
+
+: "getty-generator: no arguments"
+# Sneak in an invalid value for $SYSTEMD_GETTY_AUTO to test things out
+PID1_ENVIRON="SYSTEMD_GETTY_AUTO=foo" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+for console in "${DUMMY_ACTIVE_CONSOLES[@]}"; do
+ unit="$(systemd-escape --template serial-getty@.service "$console")"
+ link_endswith "$OUT_DIR/normal/getty.target.wants/$unit" "/lib/systemd/system/serial-getty@.service"
+done
+for console in "${DUMMY_INACTIVE_CONSOLES[@]}" /dev/notatty99; do
+ unit="$(systemd-escape --template serial-getty@.service "$console")"
+ test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
+ test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
+done
+
+: "getty-generator: systemd.getty_auto=0 on kernel cmdline"
+SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+: "getty-generator: SYSTEMD_GETTY_AUTO=0 in PID1's environment"
+PID1_ENVIRON="SYSTEMD_GETTY_AUTO=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+# Cleanup
+umount /sys/class/tty/console/active
+rm -f "${DUMMY_CONSOLES[@]/#//dev/}" /dev/notatty99
--- /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-run-generator"
+OUT_DIR="$(mktemp -d /tmp/run-generator.XXX)"
+
+at_exit() {
+ rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+check_kernel_cmdline_target() {
+ local out_dir="${1:?}/normal"
+
+ cat "$out_dir/kernel-command-line.target"
+ grep -qE "^Requires=kernel-command-line.service$" "$out_dir/kernel-command-line.target"
+ grep -qE "^After=kernel-command-line.service$" "$out_dir/kernel-command-line.target"
+
+ link_eq "$out_dir/default.target" "kernel-command-line.target"
+}
+
+: "run-generator: empty cmdline"
+SYSTEMD_PROC_CMDLINE="" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+: "run-generator: single command"
+CMDLINE="systemd.run='echo hello world'"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_kernel_cmdline_target "$OUT_DIR"
+UNIT="$OUT_DIR/normal/kernel-command-line.service"
+cat "$UNIT"
+systemd-analyze verify --man=no --recursive-errors=no "$UNIT"
+grep -qE "^SuccessAction=exit$" "$UNIT"
+grep -qE "^FailureAction=exit$" "$UNIT"
+grep -qE "^ExecStart=echo hello world$" "$UNIT"
+
+: "run-generator: multiple commands + success/failure actions"
+ARGS=(
+ # These should be ignored
+ "systemd.run"
+ "systemd.run_success_action"
+ "systemd.run_failure_action"
+
+ # Set actions which we will overwrite later
+ "systemd.run_success_action="
+ "systemd.run_failure_action="
+
+ "systemd.run=/bin/false"
+ "systemd.run="
+ "systemd.run=/bin/true"
+ "systemd.run='echo this is a long string'"
+
+ "systemd.run_success_action=reboot"
+ "systemd.run_failure_action=poweroff-force"
+)
+CMDLINE="${ARGS[*]}"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_kernel_cmdline_target "$OUT_DIR"
+UNIT="$OUT_DIR/normal/kernel-command-line.service"
+cat "$UNIT"
+systemd-analyze verify --man=no --recursive-errors=no "$UNIT"
+grep -qE "^SuccessAction=reboot$" "$UNIT"
+grep -qE "^FailureAction=poweroff-force$" "$UNIT"
+grep -qE "^ExecStart=/bin/false$" "$UNIT"
+grep -qE "^ExecStart=$" "$UNIT"
+grep -qE "^ExecStart=/bin/true$" "$UNIT"
+grep -qE "^ExecStart=echo this is a long string$" "$UNIT"
--- /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
+
+for script in "${0%.sh}".*.sh; do
+ echo "Running $script"
+ "./$script"
+done
+
+touch /testok
+rm /failed
--- /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-system-update-generator"
+OUT_DIR="$(mktemp -d /tmp/system-update-generator-generator.XXX)"
+
+at_exit() {
+ rm -frv "${OUT_DIR:?}" /system-update
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+rm -f /system-update
+
+: "system-update-generator: no /system-update flag"
+run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+: "system-update-generator: with /system-update flag"
+touch /system-update
+run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/early/default.target" "/lib/systemd/system/system-update.target"
+
+: "system-update-generator: kernel cmdline warnings"
+# We should warn if the default target is overridden on the kernel cmdline
+# by a runlevel or systemd.unit=, but still generate the symlink
+SYSTEMD_PROC_CMDLINE="systemd.unit=foo.bar 3" run_and_list "$GENERATOR_BIN" "$OUT_DIR" |& tee /tmp/system-update-generator.log
+link_endswith "$OUT_DIR/early/default.target" "/lib/systemd/system/system-update.target"
+grep -qE "Offline system update overridden .* systemd.unit=" /tmp/system-update-generator.log
+grep -qE "Offline system update overridden .* runlevel" /tmp/system-update-generator.log
--- /dev/null
+#!/usr/bin/python
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+"""
+A program to parse auxv (e.g. /proc/self/auxv).
+
+By default, current arch is assumed, but options can be used to override the
+endianness and word size.
+"""
+
+import struct
+
+import click
+
+# From /usr/include/elf.h
+AT_AUXV = {
+ 'AT_NULL' : 0, # End of vector
+ 'AT_IGNORE' : 1, # Entry should be ignored
+ 'AT_EXECFD' : 2, # File descriptor of program
+ 'AT_PHDR' : 3, # Program headers for program
+ 'AT_PHENT' : 4, # Size of program header entry
+ 'AT_PHNUM' : 5, # Number of program headers
+ 'AT_PAGESZ' : 6, # System page size
+ 'AT_BASE' : 7, # Base address of interpreter
+ 'AT_FLAGS' : 8, # Flags
+ 'AT_ENTRY' : 9, # Entry point of program
+ 'AT_NOTELF' : 10, # Program is not ELF
+ 'AT_UID' : 11, # Real uid
+ 'AT_EUID' : 12, # Effective uid
+ 'AT_GID' : 13, # Real gid
+ 'AT_EGID' : 14, # Effective gid
+ 'AT_CLKTCK' : 17, # Frequency of times()
+
+ # Some more special a_type values describing the hardware.
+ 'AT_PLATFORM' : 15, # String identifying platform.
+ 'AT_HWCAP' : 16, # Machine-dependent hints about processor capabilities.
+
+ # This entry gives some information about the FPU initialization performed by the kernel.
+ 'AT_FPUCW' : 18, # Used FPU control word.
+
+ # Cache block sizes.
+ 'AT_DCACHEBSIZE' : 19, # Data cache block size.
+ 'AT_ICACHEBSIZE' : 20, # Instruction cache block size.
+ 'AT_UCACHEBSIZE' : 21, # Unified cache block size.
+
+ # A special ignored value for PPC, used by the kernel to control the
+ # interpretation of the AUXV. Must be > 16.
+ 'AT_IGNOREPPC' : 22, # Entry should be ignored.
+
+ 'AT_SECURE' : 23, # Boolean, was exec setuid-like?
+
+ 'AT_BASE_PLATFORM' : 24, # String identifying real platforms.
+
+ 'AT_RANDOM' : 25, # Address of 16 random bytes.
+
+ 'AT_HWCAP2' : 26, # More machine-dependent hints about processor capabilities.
+
+ 'AT_EXECFN' : 31, # Filename of executable.
+
+ # Pointer to the global system page used for system calls and other nice things.
+ 'AT_SYSINFO' : 32,
+ 'AT_SYSINFO_EHDR' : 33,
+
+ # Shapes of the caches. Bits 0-3 contains associativity; bits 4-7 contains
+ # log2 of line size; mask those to get cache size.
+ 'AT_L1I_CACHESHAPE' : 34,
+ 'AT_L1D_CACHESHAPE' : 35,
+ 'AT_L2_CACHESHAPE' : 36,
+ 'AT_L3_CACHESHAPE' : 37,
+
+ # Shapes of the caches, with more room to describe them.
+ # GEOMETRY are comprised of cache line size in bytes in the bottom 16 bits
+ # and the cache associativity in the next 16 bits.
+ 'AT_L1I_CACHESIZE' : 40,
+ 'AT_L1I_CACHEGEOMETRY' : 41,
+ 'AT_L1D_CACHESIZE' : 42,
+ 'AT_L1D_CACHEGEOMETRY' : 43,
+ 'AT_L2_CACHESIZE' : 44,
+ 'AT_L2_CACHEGEOMETRY' : 45,
+ 'AT_L3_CACHESIZE' : 46,
+ 'AT_L3_CACHEGEOMETRY' : 47,
+
+ 'AT_MINSIGSTKSZ' : 51, # Stack needed for signal delivery
+}
+AT_AUXV_NAMES = {v:k for k,v in AT_AUXV.items()}
+
+@click.command(help=__doc__)
+@click.option('-b', '--big-endian', 'endian',
+ flag_value='>',
+ help='Input is big-endian')
+@click.option('-l', '--little-endian', 'endian',
+ flag_value='<',
+ help='Input is little-endian')
+@click.option('-3', '--32', 'field_width',
+ flag_value=32,
+ help='Input is 32-bit')
+@click.option('-6', '--64', 'field_width',
+ flag_value=64,
+ help='Input is 64-bit')
+@click.argument('file',
+ type=click.File(mode='rb'))
+def dump(endian, field_width, file):
+ data = file.read()
+
+ if field_width is None:
+ field_width = struct.calcsize('P') * 8
+ if endian is None:
+ endian = '@'
+
+ width = {32:'II', 64:'QQ'}[field_width]
+
+ format = f'{endian}{width}'
+ print(f'# {format=}')
+
+ seen_null = False
+
+ for item in struct.iter_unpack(format, data):
+ key, val = item
+ name = AT_AUXV_NAMES.get(key, f'unknown ({key})')
+ if name.endswith(('UID', 'GID')):
+ pref, fmt = '', 'd'
+ else:
+ pref, fmt = '0x', 'x'
+
+ if seen_null:
+ print('# trailing garbarbage after AT_NULL')
+
+ print(f'{name:18} = {pref}{val:{fmt}}')
+
+ if name == 'AT_NULL':
+ seen_null = True
+
+ if not seen_null:
+ print('# array not terminated with AT_NULL')
+
+if __name__ == '__main__':
+ dump()
['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',
# reboot or some other action on its own.
ConditionPathExists=|/system-update
ConditionPathIsSymbolicLink=|/system-update
+ConditionPathExists=|/etc/system-update
+ConditionPathIsSymbolicLink=|/etc/system-update
[Service]
Type=oneshot
-ExecStart=rm -fv /system-update
+ExecStart=rm -fv /system-update /etc/system-update
--- /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
+ConditionDirectoryNotEmpty=|/.extra/sysext
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]