]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #26887 from yuwata/proc-cmdline-filter-arguments
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 7 Apr 2023 08:55:30 +0000 (10:55 +0200)
committerGitHub <noreply@github.com>
Fri, 7 Apr 2023 08:55:30 +0000 (10:55 +0200)
proc-cmdline: filter PID1 arguments on container

238 files changed:
.github/workflows/build_test.sh
.github/workflows/codeql.yml
.github/workflows/labeler.yml
.github/workflows/linter.yml
.github/workflows/make_release.yml [new file with mode: 0644]
.github/workflows/mkosi.yml
.github/workflows/scorecards.yml
.gitignore
TODO
docs/COREDUMP.md [new file with mode: 0644]
docs/ENVIRONMENT.md
docs/HACKING.md
docs/TEMPORARY_DIRECTORIES.md
docs/TESTING_WITH_SANITIZERS.md
hwdb.d/60-sensor.hwdb
man/html.in
man/kernel-install.xml
man/org.freedesktop.systemd1.xml
man/os-release.xml
man/print-unit-path-call-method.c [new file with mode: 0644]
man/print-unit-path.c
man/rules/meson.build
man/sd-bus-errors.xml
man/sd_bus_call_method.xml
man/sd_bus_message_open_container.xml
man/systemd-analyze.xml
man/systemd-coredump.xml
man/systemd-dissect.xml
man/systemd-firstboot.xml
man/systemd-journal-remote.service.xml
man/systemd-notify.xml
man/systemd-sysext.xml
man/systemd-tmpfiles.xml
man/systemd.netdev.xml
man/systemd.service.xml
man/systemd.unit.xml
man/tmpfiles.d.xml
mkosi.build
mkosi.conf.d/10-systemd.conf
mkosi.conf.d/20-arch.conf [moved from mkosi.conf.d/arch/10-arch.conf with 77% similarity]
mkosi.conf.d/20-centos.conf [moved from mkosi.conf.d/centos/10-centos.conf with 66% similarity]
mkosi.conf.d/20-debian.conf [moved from mkosi.conf.d/debian/10-debian.conf with 90% similarity]
mkosi.conf.d/20-fedora.conf [moved from mkosi.conf.d/fedora/10-fedora.conf with 89% similarity]
mkosi.conf.d/20-opensuse.conf [moved from mkosi.conf.d/opensuse/10-opensuse.conf with 91% similarity]
mkosi.conf.d/20-ubuntu.conf [moved from mkosi.conf.d/ubuntu/10-ubuntu.conf with 90% similarity]
mkosi.conf.d/21-centos-8/mkosi.conf [new file with mode: 0644]
mkosi.conf.d/21-centos-8/mkosi.reposdir/powertools.repo [moved from mkosi.conf.d/centos/mkosi.reposdir/powertools.repo with 84% similarity]
mkosi.conf.d/21-centos-9.conf [new file with mode: 0644]
mkosi.extra/root/.gdbinit
mkosi.kernel.config
po/ru.po
shell-completion/bash/busctl
shell-completion/bash/portablectl
shell-completion/bash/systemd-confext [new file with mode: 0644]
src/analyze/analyze-fdstore.c [new file with mode: 0644]
src/analyze/analyze-fdstore.h [new file with mode: 0644]
src/analyze/analyze.c
src/analyze/meson.build
src/ask-password/ask-password.c
src/basic/architecture.h
src/basic/chase.c
src/basic/compress.c
src/basic/compress.h
src/basic/coverage.h
src/basic/devnum-util.c
src/basic/devnum-util.h
src/basic/fd-util.c
src/basic/fd-util.h
src/basic/fs-util.c
src/basic/log.c
src/basic/log.h
src/basic/missing_fcntl.h
src/basic/mkdir.c
src/basic/os-util.c
src/basic/os-util.h
src/basic/stat-util.c
src/basic/stat-util.h
src/basic/terminal-util.c
src/basic/unit-def.c
src/basic/unit-def.h
src/basic/unit-file.c
src/basic/user-util.h
src/boot/bootctl-install.c
src/boot/bootctl-status.c
src/boot/bootctl-util.c
src/boot/bootctl.c
src/core/dbus-manager.c
src/core/dbus-service.c
src/core/dbus-service.h
src/core/dbus-unit.c
src/core/dynamic-user.c
src/core/dynamic-user.h
src/core/execute.c
src/core/execute.h
src/core/main.c
src/core/manager-serialize.c
src/core/manager.c
src/core/manager.h
src/core/mount.c
src/core/mount.h
src/core/namespace.c
src/core/service.c
src/core/service.h
src/core/socket.c
src/core/socket.h
src/core/swap.c
src/core/swap.h
src/core/unit-serialize.c
src/core/unit.c
src/core/unit.h
src/creds/creds.c
src/cryptenroll/cryptenroll-tpm2.c
src/cryptenroll/cryptenroll.c
src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
src/cryptsetup/cryptsetup-tpm2.c
src/cryptsetup/cryptsetup-tpm2.h
src/cryptsetup/cryptsetup.c
src/firstboot/firstboot.c
src/fstab-generator/fstab-generator.c
src/home/homectl.c
src/journal/journald-server.c
src/kernel-install/test-kernel-install.sh
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h
src/libsystemd/sd-bus/bus-control.c
src/libsystemd/sd-bus/bus-error.c
src/libsystemd/sd-device/device-util.c
src/libsystemd/sd-id128/id128-util.c
src/libsystemd/sd-id128/id128-util.h
src/libsystemd/sd-id128/sd-id128.c
src/libsystemd/sd-journal/journal-file.c
src/libsystemd/sd-journal/journal-file.h
src/machine-id-setup/machine-id-setup-main.c
src/network/networkctl.c
src/network/networkd-network-bus.c
src/notify/notify.c
src/nspawn/nspawn.c
src/partition/repart.c
src/portable/portable.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-scope.c
src/resolve/resolved-link.c
src/resolve/resolved-link.h
src/resolve/resolved-manager.c
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/btrfs-util.c
src/shared/btrfs-util.h
src/shared/creds-util.c
src/shared/discover-image.c
src/shared/discover-image.h
src/shared/dissect-image.c
src/shared/exec-util.c
src/shared/extension-release.h [deleted file]
src/shared/extension-util.c [moved from src/shared/extension-release.c with 51% similarity]
src/shared/extension-util.h [new file with mode: 0644]
src/shared/fdset.c
src/shared/fdset.h
src/shared/fileio-label.c
src/shared/find-esp.c
src/shared/find-esp.h
src/shared/format-table.c
src/shared/format-table.h
src/shared/machine-id-setup.c
src/shared/meson.build
src/shared/mkfs-util.c
src/shared/rm-rf.c
src/shared/specifier.c
src/shared/tpm2-util.c
src/shared/tpm2-util.h
src/sysext/meson.build
src/sysext/sysext.c
src/systemd/sd-bus-protocol.h
src/test/meson.build
src/test/test-chase-manual.c [new file with mode: 0644]
src/test/test-chase.c
src/test/test-compare-operator.c
src/test/test-env-util.c
src/test/test-fd-util.c
src/test/test-fs-util.c
src/test/test-id128.c
src/test/test-log.c
src/test/test-nss-hosts.c
src/test/test-os-util.c
src/test/test-rm-rf.c
src/test/test-specifier.c
src/test/test-tpm2.c
src/tmpfiles/tmpfiles.c
src/udev/udev-rules.c
src/udev/udevadm-lock.c
src/ukify/ukify.py
src/user-sessions/user-sessions.c
test/README.testsuite
test/TEST-78-SIGQUEUE/test.sh
test/TEST-79-MEMPRESS/test.sh
test/TEST-80-NOTIFYACCESS/test.sh
test/TEST-81-GENERATORS/Makefile [new symlink]
test/TEST-81-GENERATORS/test.sh [new file with mode: 0755]
test/meson.build
test/mkosi.build.networkd-test [deleted file]
test/mkosi.default.networkd-test [deleted file]
test/mkosi.nspawn.networkd-test [deleted file]
test/test-functions
test/test-network/systemd-networkd-tests.py
test/test-sysusers.sh.in
test/testsuite-80.units/test.sh
test/units/generator-utils.sh [new file with mode: 0644]
test/units/testsuite-01.service
test/units/testsuite-01.sh [new file with mode: 0755]
test/units/testsuite-04.sh
test/units/testsuite-17.11.sh
test/units/testsuite-19.sh
test/units/testsuite-20.sh
test/units/testsuite-33.sh
test/units/testsuite-35.sh
test/units/testsuite-43.sh
test/units/testsuite-50.sh
test/units/testsuite-54.sh
test/units/testsuite-57-short-lived.sh
test/units/testsuite-58.sh
test/units/testsuite-59.sh
test/units/testsuite-70.sh
test/units/testsuite-73.sh
test/units/testsuite-74.firstboot.sh
test/units/testsuite-74.modules-load.sh [new file with mode: 0755]
test/units/testsuite-74.mount.sh
test/units/testsuite-80.sh
test/units/testsuite-81.debug-generator.sh [new file with mode: 0755]
test/units/testsuite-81.environment-d-generator.sh [new file with mode: 0755]
test/units/testsuite-81.fstab-generator.sh [new file with mode: 0755]
test/units/testsuite-81.service [new file with mode: 0644]
test/units/testsuite-81.sh [new file with mode: 0755]
units/meson.build
units/systemd-confext.service [new file with mode: 0644]
units/systemd-coredump.socket
units/systemd-sysext.service

index 3ec229bd9b7bed551fc3fc63f66df4d4420b9b1b..40614f9084e3dc4182140e04cf6d39c2d115772c 100755 (executable)
@@ -11,7 +11,7 @@ ARGS=(
     "--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"
index 19ec1219da359a119ad8122cde05a2bdb1d72e23..d5faedf7155ea4ace76cb4ac93a5707e2f7198eb 100644 (file)
@@ -45,7 +45,7 @@ jobs:
       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
@@ -53,7 +53,7 @@ jobs:
     - 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
index 6085150699f19605e7f58535c37c84dca8ee23b9..389f896f2383c3a2393e74f332eb8ab3e09a9407 100644 (file)
@@ -22,7 +22,7 @@ jobs:
 
     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 }}"
index 83e0872d567d74719a811c67ed026aef50c4f8e9..8b4259ade632ccec8dd116e88b8094284e0f35b8 100644 (file)
@@ -29,7 +29,7 @@ jobs:
           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
diff --git a/.github/workflows/make_release.yml b/.github/workflows/make_release.yml
new file mode 100644 (file)
index 0000000..47dbbea
--- /dev/null
@@ -0,0 +1,18 @@
+name: Make a Github release
+
+on:
+  push:
+    tags:
+      - "v*"
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Release
+        uses: softprops/action-gh-release@v1
+        with:
+          prerelease: ${{ contains(github.ref_name, '-rc') }}
+          draft: ${{ github.repository == 'systemd/systemd' }}
index a4a315bc236b651c2b444476d2176595f3ea5556..e5e3646509b53b76b0c2609ac2768662caa575c2 100644 (file)
@@ -1,7 +1,7 @@
 ---
 # 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:
@@ -73,11 +73,11 @@ jobs:
 
     steps:
     - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
-    - uses: systemd/mkosi@c1f1e9ab2fe89f21ebdb4984b676f9a489081a64
+    - uses: systemd/mkosi@f219c1125893e8773efed5ec8a1226f3bd8a00cb
 
     - name: Configure
       run: |
-        tee mkosi.default <<- EOF
+        tee mkosi.conf <<- EOF
         [Distribution]
         Distribution=${{ matrix.distro }}
         Release=${{ matrix.release }}
index 26009939db6a75263d5ab3ef0cb463b7311fee21..397460a16e3244a74f9c861178e3343ccf6ec107 100644 (file)
@@ -37,7 +37,7 @@ jobs:
           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
@@ -65,6 +65,6 @@ jobs:
       # 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
index 844d67f0a1091ba81ed01161c074e49498d19cf9..1ad0675c4e0d0c1cad621135d8df14dc5bb0253e 100644 (file)
@@ -31,11 +31,10 @@ __pycache__/
 /.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/
diff --git a/TODO b/TODO
index 2c0d185683a01bd6be14cb3d952dbfe2ab5bdfcc..73edc66d782628f8e6c74e0128bfc8bdfbdecfba 100644 (file)
--- a/TODO
+++ b/TODO
@@ -129,6 +129,10 @@ Deprecations and removals:
 
 Features:
 
+* rework journalctl -M to be based on a machined method that generates a mount
+  fd of the relevant journal dirs in the container with uidmapping applied to
+  allow the host to read it, while making everything read-only.
+
 * fix our various hwdb lookup keys to end with ":" again. The original idea was
   that hwdb patters can match arbitrary fields with expressions like
   "*:foobar:*", to wildcard match both the start and the end of the string.
@@ -136,18 +140,11 @@ Features:
   always end in a colon. This requires updating our udev rules, as well as
   checking if the various hwdb files are fine with that.
 
-* Add a bus API to enumerate contents of the fdstore of a service,
-  handle/display similar to querying the process tree. Should probably just an
-  array of inode/devnum of fd, plus fd_get_name() data
-
 * mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user
   among other things such as dynamic uid ranges for containers and so on. That
   way noone can create files there with these uids and we enforce they are only
   used transiently, never persistently.
 
-* set MS_NOSYMFOLLOW for ESP and XBOOTLDR mounts both in gpt-generator and in
-  dissect.c
-
 * rework loopback support in fstab: when "loop" option is used, then
   instantiate a new systemd-loop@.service for the source path, set the
   lo_file_name field for it to something recognizable derived from the fstab
@@ -524,13 +521,13 @@ Features:
 * 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
@@ -541,17 +538,17 @@ Features:
   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.
 
@@ -600,13 +597,10 @@ Features:
   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
@@ -621,9 +615,6 @@ Features:
 * chase(): refuse resolution if trailing slash is specified on input,
   but final node is not a directory
 
-* chase(): add new flag that simply refuses all symlink use in a path,
-  then use that for accessing XBOOTLDR/ESP
-
 * document in boot loader spec that symlinks in XBOOTLDR/ESP are not OK even if
   non-VFAT fs is used.
 
@@ -634,6 +625,8 @@ Features:
 
 * 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
@@ -753,9 +746,6 @@ Features:
 
 * implement varlink introspection
 
-* we should probably drop all use of prefix_roota() and friends, and use
-  chase() instead
-
 * make persistent restarts easier by adding a new setting OpenPersistentFile=
   or so, which allows opening one or more files that is "persistent" across
   service restarts, hot reboot, cold reboots (depending on configuration): the
@@ -796,7 +786,7 @@ Features:
   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
@@ -868,10 +858,6 @@ Features:
   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
@@ -880,8 +866,8 @@ Features:
   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?
 
@@ -912,7 +898,7 @@ Features:
 
 * 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.
@@ -928,12 +914,6 @@ Features:
 
 * 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).
@@ -976,8 +956,6 @@ Features:
 
 * 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.
@@ -1228,7 +1206,7 @@ Features:
 
 * 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
@@ -1314,10 +1292,6 @@ Features:
 * 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
 
@@ -1367,9 +1341,6 @@ Features:
 
 * 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
@@ -1559,10 +1530,6 @@ Features:
 
 * 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
@@ -1962,11 +1929,6 @@ Features:
 
 * mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units.
 
-* systemd-firstboot: make sure to always use chase() before
-  reading/writing files
-
-* firstboot: make it useful to be run immediately after yum --installroot to set up a machine. (most specifically, make --copy-root-password work even if /etc/passwd already exists
-
 * EFI:
   - honor language efi variables for default language selection (if there are any?)
   - honor timezone efi variables for default timezone selection (if there are any?)
@@ -2036,7 +1998,7 @@ Features:
   - 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.
@@ -2265,11 +2227,6 @@ Features:
   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.
diff --git a/docs/COREDUMP.md b/docs/COREDUMP.md
new file mode 100644 (file)
index 0000000..7d17d30
--- /dev/null
@@ -0,0 +1,147 @@
+---
+title: systemd Coredump Handling
+category: Concepts
+layout: default
+SPDX-License-Identifier: LGPL-2.1-or-later
+---
+
+# systemd Coredump Handling
+
+## Support in the Service Manager (PID 1)
+
+The systemd service manager natively provides coredump handling functionality,
+as implemented by the Linux kernel. Specifically, PID 1 provides the following
+functionality:
+
+1. During very early boot it will raise the
+   [`LIMIT_CORE`](https://man7.org/linux/man-pages/man2/getrlimit.2.html)
+   resource limit for itself to infinity (and thus implicitly also all its
+   children). This removes any limits on the size of generated coredumps, for
+   all invoked processes, from earliest boot on. (The Linux kernel sets the
+   limit to 0 by default.)
+
+2. At the same time it will turn off coredump handling in the kernel by writing
+   `|/bin/false` into `/proc/sys/kernel/core_pattern` (also known as the
+   "`kernel.core_pattern` sysctl"; see
+   [core(5)](https://man7.org/linux/man-pages/man5/core.5.html) for
+   details). This means that coredumps are not actually processed. (The Linux
+   kernel sets the pattern to `core` by default, so that coredumps are written
+   to the current working directory of the crashing process.)
+
+Net effect: after PID1 has started and performed this setup coredumps are
+disabled, but by means of the the `kernel.core_pattern` sysctl rather than by
+size limit. This is generally preferable, since the pattern can be updated
+trivially at the right time to enable coredumping once the system is ready,
+taking comprehensive effect on all userspace. (Or to say this differently:
+disabling coredumps via the size limit is problematic, since it cannot easily
+be undone without iterating through all already running processes once the
+system is ready for coredump handling.)
+
+Processing of core dumps may be enabled at the appropriate time by updating the
+`kernel.core_pattern` sysctl. Only coredumps that happen later will be
+processed.
+
+During the final shutdown phase the `kernel.core_pattern` sysctl is updated
+again to `|/bin/false`, disabling coredump support again, should it have been
+enabled in the meantime.
+
+This means coredump handling is generally not available during earliest boot
+and latest shutdown, reflecting the fact that storage is typically not
+available in these environments, and many other facilities are missing too that
+are required to collect and process a coredump successfully.
+
+## `systemd-coredump` Handler
+
+The systemd suite provides a coredump handler
+[`systemd-coredump`](https://www.freedesktop.org/software/systemd/man/systemd-coredump.html)
+which can be enabled at build-time. It is activated during boot via the
+`/usr/lib/sysctl.d/50-coredump.conf` drop-in file for
+`systemd-sysctl.service`. It registers the `systemd-coredump` tool as
+`kernel.core_pattern` sysctl.
+
+`systemd-coredump` is implemented as socket activated service: when the kernel
+invokes the userspace coredump handler, the received coredump file descriptor
+is immediately handed off to the socket activated service
+`systemd-coredump@.service` via the `systemd-coredump.socket` socket unit. This
+means the coredump handler runs for a very short time only, and the potentially
+*heavy* and security sensitive coredump processing work is done as part of the
+specified service unit, and thus can take benefit of regular service resource
+management and sandboxing.
+
+The `systemd-coredump` handler will extract a backtrace and [ELF packaging
+metadata](https://systemd.io/ELF_PACKAGE_METADATA) from any coredumps it
+receieves and log both. The information about coredumps stored in the journal
+can be enumerated and queried with the
+[`coredumpctl`](https://www.freedesktop.org/software/systemd/man/coredumpctl.html)
+tool, for example for directly invoking a debugger such as `gdb` on a collected
+coredump.
+
+The handler writes coredump files to `/var/lib/systemd/coredump/`.  Old files
+are cleaned up periodically by
+[`systemd-tmpfiles(8)`](https://www.freedesktop.org/software/systemd/man/systemd-tmpfiles.html).
+
+## User Experience
+
+With the above, any coredumps generated on the system are by default collected
+and turned into logged events — except during very early boot and late
+shutdown. Individual services, processes or users can opt-out of coredump
+collection, by setting `LIMIT_CORE` to 0 (or alternatively invoke
+[`PR_SET_DUMPABLE`](https://man7.org/linux/man-pages/man2/prctl.2.html)). The
+resource limit can be set freely by daemons/processes/users to arbitrary
+values, which the coredump handler will respect. The `coredumpctl` tool may be
+used to further analyze/debug coredumps.
+
+## Alternative Coredump Handlers
+
+While we recommend usage of the `systemd-coredump` handler, it's fully
+supported to use alternative coredump handlers instead. A similar
+implementation pattern is recommended. Specifically:
+
+1. Use a `sysctl.d/` drop-in to register your handler with the kernel. Make
+   sure to include the `%c` specifier in the pattern (which reflects the
+   crashing process' `RLIMIT_CORE`) and act on it: limit the stored coredump
+   file to the specified limit.
+
+2. Do not do heavy processing directly in the coredump handler. Instead,
+   quickly pass off the kernel's coredump file descriptor to an
+   auxiliary service running as service under the service manager, so that it
+   can be done under supervision, sandboxing and resource management.
+
+Note that at any given time only a single handler can be enabled, i.e. the
+`kernel.core_pattern` sysctl cannot reference multiple executables.
+
+## Packaging
+
+It might make sense to split `systemd-coredump` into a separate distribution
+package. If doing so, make sure that `/usr/lib/sysctl.d/50-coredump.conf` and
+the associated service and socket units are also added to the split off package.
+
+Note that in a scenario where `systemd-coredump` is split out and not
+installed, coredumping is turned off during the entire runtime of the system —
+unless an alternative handler is installed, or behaviour is manually reverted
+to legacy style handling (see below).
+
+## Restoring Legacy Coredump Handling
+
+The default policy of the kernel to write coredumps into the current working
+directory of the crashing process is considered highly problematic by many,
+including by the systemd maintainers. Nonetheless, if users locally want to
+return to this behaviour, two changes must be made (followed by a reboot):
+
+```console
+$ mkdir -p /etc/sysctl.d
+$ cat >/etc/sysctl.d/50-coredump.conf <<EOF
+# Party like it's 1995!
+kernel.core_pattern=core
+EOF
+```
+
+and
+
+```console
+$ mkdir -p /etc/systemd/system.conf.d
+$ cat >/etc/systemd/system.conf.d/50-coredump.conf <<EOF
+[Manager]
+DefaultLimitCORE=0:infinity
+EOF
+```
index 3ec5573ff9524e0ee4059331c6fc9ae8473a1f95..292a0f1e0e176e6780315a5ec5e54294a813c45b 100644 (file)
@@ -328,7 +328,9 @@ the journal instead of only when logging in debug mode.
   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`:
 
@@ -513,6 +515,14 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \
   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
index 265f17e0d59095b64e140ee9cfd329c7bf8aace1..d5c53734300145f31536fd07987708ea203053b6 100644 (file)
@@ -340,7 +340,7 @@ To debug systemd-boot in an IDE such as VSCode we can use a launch configuration
 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)
 
index 4e815ed4d285aef530c62ee31aa747601048de58..d655b9ca77628a75a19747c105f86be0bf55f3c8 100644 (file)
@@ -111,14 +111,13 @@ strategies to avoid these issues:
    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
@@ -127,9 +126,7 @@ strategies to avoid these issues:
    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
index ef98cf0121c63cc05619dd3700d294858fb1cdc5..67b6be46c284db10fd2e011a414b0cead037176d 100644 (file)
@@ -15,7 +15,7 @@ compiler you want to use and which part of the test suite you want to run.
 
 ## 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:
 
 ```
index 2c19ab33d611f1bcf72833471c3f2eb388924352..e142dc55445b2d2ab7bd85fc4c5bd17bb7a890d5 100644 (file)
@@ -963,6 +963,10 @@ sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAENCORE2WT8-B:*
 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
 #########################################
index 5e545b741282a3d40c5676050b312f85afdbac8c..aaff9d138382f4c38ac1d367e3e2d892ba24aed4 100755 (executable)
@@ -14,7 +14,11 @@ target="man/$1.html"
 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"
 
index 3d5aebf2e0b20aa784d249cff476728eef4d39bf..4f30e9b25241c6d69be8ec2d0fd6bd6078d591e8 100644 (file)
         </varlistentry>
       </variablelist>
 
-      <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> and <varname>$KERNEL_INSTALL_UKI_GENERATOR</varname> 
-      are set for plugins to select the initrd and/or UKI generator. This may be configured as 
+      <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> and <varname>$KERNEL_INSTALL_UKI_GENERATOR</varname>
+      are set for plugins to select the initrd and/or UKI generator. This may be configured as
       <varname>initrd_generator=</varname> and <varname>uki_generator=</varname> in <filename>install.conf</filename>, see below.</para>
 
       <para><varname>$KERNEL_INSTALL_STAGING_AREA</varname> is set for plugins to a path to a directory.
             <varname>MACHINE_ID=</varname>,
             <varname>BOOT_ROOT=</varname>,
             <varname>layout=</varname>,
-            <varname>initrd_generator=</varname>.
+            <varname>initrd_generator=</varname>,
+            <varname>uki_generator=</varname>.
             See the Environment variables section above for details.</para>
           </listitem>
       </varlistentry>
index ab94a90b7cfd181a3c30a9581a3945fb2f0fe7d8..50680e6b37af8b8a8324084e087c44bfc001295c 100644 (file)
@@ -273,6 +273,8 @@ node /org/freedesktop/systemd1 {
       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);
@@ -974,6 +976,8 @@ node /org/freedesktop/systemd1 {
 
     <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"/>
@@ -1531,6 +1535,11 @@ node /org/freedesktop/systemd1 {
       <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>
@@ -2548,6 +2557,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
                  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);
@@ -2568,7 +2578,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t RestartUSecMax = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
-      readonly t RestartUSecCurrent = ...;
+      readonly t RestartUSecNext = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t TimeoutStartUSec = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -3198,7 +3208,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property RestartUSecMax is not documented!-->
 
-    <!--property RestartUSecCurrent is not documented!-->
+    <!--property RestartUSecNext is not documented!-->
 
     <!--property TimeoutStartFailureMode is not documented!-->
 
@@ -3742,6 +3752,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <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()"/>
@@ -3762,7 +3774,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecMax"/>
 
-    <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecCurrent"/>
+    <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecNext"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="TimeoutStartUSec"/>
 
@@ -4393,6 +4405,16 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       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>
index e74f27b9907e42fc2adb0d80f05be08708d9d1cc..6cc786acf9e50367fb6a343e5163071728539f67 100644 (file)
           </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
diff --git a/man/print-unit-path-call-method.c b/man/print-unit-path-call-method.c
new file mode 100644 (file)
index 0000000..f73dd07
--- /dev/null
@@ -0,0 +1,51 @@
+/* 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;
+}
index 44c8271820f79acc3dfd74e3adea3c87fe2f7340..0b89318736aeb5060235d5100be2fd062ac6f726 100644 (file)
@@ -1,20 +1,20 @@
 /* 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"
@@ -26,12 +26,16 @@ static int log_error(int error, const char *message) {
   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)
@@ -43,7 +47,7 @@ static int print_unit_path(sd_bus *bus) {
 
   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);
@@ -54,14 +58,3 @@ static int print_unit_path(sd_bus *bus) {
 
   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);
-}
index 39cc55a929f608beff07e031c52f032031d75039..b6c88db390e3b430dca14df9013384463deddf84 100644 (file)
@@ -85,6 +85,7 @@ manpages = [
    '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',
@@ -96,8 +97,11 @@ manpages = [
    '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',
@@ -1039,7 +1043,10 @@ manpages = [
    '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',
index f3b1515c78db7faa81916f10b5c66aab92d85358..dc9d9fc63b560589a8475051d3f3257993867247 100644 (file)
     <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 &lt;systemd/sd-bus.h&gt;</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>
 
index 762ea11c01067531c11e309005de21edaa930b6d..7b525558d70c26df4be3aebcde929116c8f8baa1 100644 (file)
 
   <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>
 
index 688f43227b190b99e79aed52262e24319a95e220..0b9164e9bf98ea77c76253ebdccf9921b5b0d73b 100644 (file)
@@ -91,7 +91,7 @@
     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>
 
index ad6d691a1d1fd2ddb7d0a2c55dbd8ef56b6c463c..9fd28e6f45c768e8089a61d0a8275a18166ab1b3 100644 (file)
       <arg choice="plain">malloc</arg>
       <arg choice="opt" rep="repeat"><replaceable>D-BUS SERVICE</replaceable></arg>
     </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-analyze</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">fdstore</arg>
+      <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
+    </cmdsynopsis>
   </refsynopsisdiv>
 
   <refsect1>
@@ -803,8 +809,37 @@ $ systemd-analyze verify /tmp/source:alias.service
 }
         </programlisting>
       </example>
+    </refsect2>
+
+    <refsect2>
+      <title><command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command></title>
+
+      <para>Lists the current contents of the specified service unit's file descriptor store. This shows
+      names, inode types, device numbers, inode numbers, paths and open modes of the open file
+      descriptors. The specified units must have <varname>FileDescriptorStoreMax=</varname> enabled, see
+      <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+      details.</para>
+
+      <example>
+        <title>Table output</title>
+        <programlisting>$ systemd-analyze fdstore systemd-journald.service
+FDNAME TYPE DEVNO   INODE RDEVNO PATH             FLAGS
+stored sock 0:8   4218620 -      socket:[4218620] ro
+stored sock 0:8   4213198 -      socket:[4213198] ro
+stored sock 0:8   4213190 -      socket:[4213190] ro
+…</programlisting>
+      </example>
 
+      <para>Note: the "DEVNO" column refers to the major/minor numbers of the device node backing the file
+      system the file descriptor's inode is on. The "RDEVNO" column refers to the major/minor numbers of the
+      device node itself if the file descriptor refers to one. Compare with corresponding
+      <varname>.st_dev</varname> and <varname>.st_rdev</varname> fields in <type>struct stat</type> (see
+      <citerefentry
+      project='man-pages'><refentrytitle>stat</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+      details). The listed inode numbers in the "INODE" column are on the file system indicated by
+      "DEVNO".</para>
     </refsect2>
+
   </refsect1>
 
   <refsect1>
index 7044548206084c3f6cfc410918b85c7e8f870be7..c92a250ca22fc8bb12dc849b460b0e0faac8aede 100644 (file)
@@ -60,6 +60,9 @@
     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>
 
@@ -431,7 +434,8 @@ user.coredump.exe="/usr/lib64/firefox/firefox"
       <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>
index 06c57a22ecdbd52ee6be31460be94682daa9bfeb..eac50bffde5d923c11d615405449c0ca68728040 100644 (file)
         <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>
index cfce8a40ad58bcabe728d5a8750d036332b23a61..42666c96f8852be81b4ae621d03acd0a4c354354 100644 (file)
         <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>
 
index e66e0f1e9c31db4794f1234e425e25b562a607dd..c8a702ad58dae10fb170c2cbc26b3f8e8570c0cf 100644 (file)
 
     <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
index 5a154686f5f7b305ca9676db8c5de55720a70eef..1b469fe85c4a9a628130cd3e53ac2202768a874d 100644 (file)
         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&lt;/some/file 5&lt;/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>
index 39a16d8e8fb8035728728ec2314d6d103ec70b6f..f3a12e0a1c2bf8c75bf19d62da2e020addb3d454 100644 (file)
@@ -19,6 +19,8 @@
   <refnamediv>
     <refname>systemd-sysext</refname>
     <refname>systemd-sysext.service</refname>
+    <refname>systemd-confext</refname>
+    <refname>systemd-confext.service</refname>
     <refpurpose>Activates System Extension Images</refpurpose>
   </refnamediv>
 
 
     <para><literallayout><filename>systemd-sysext.service</filename></literallayout></para>
 
+    <cmdsynopsis>
+      <command>systemd-confext</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">COMMAND</arg>
+    </cmdsynopsis>
+
+    <para><literallayout><filename>systemd-confext.service</filename></literallayout></para>
+
   </refsynopsisdiv>
 
   <refsect1>
     service manager supports via <option>RootDirectory=</option>/<option>RootImage=</option>. Similar to
     them they may optionally carry Verity authentication information.</para>
 
-    <para>System extensions are automatically looked for in the directories
-    <filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename>,
-    <filename>/var/lib/extensions/</filename>, <filename>/usr/lib/extensions/</filename> and
-    <filename>/usr/local/lib/extensions/</filename>. The first two listed directories are not suitable for
+    <para>System extensions are searched for in the directories
+    <filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename> and
+    <filename>/var/lib/extensions/</filename>. The first two listed directories are not suitable for
     carrying large binary images, however are still useful for carrying symlinks to them. The primary place
     for installing system extensions is <filename>/var/lib/extensions/</filename>. Any directories found in
-    these search directories are considered directory based extension images, any files with the
+    these search directories are considered directory based extension images; any files with the
     <filename>.raw</filename> suffix are considered disk image based extension images.</para>
 
     <para>During boot OS extension images are activated automatically, if the
     The <filename>extension-release</filename> file follows the same format and semantics, and carries the same
     content, as the <filename>os-release</filename> file of the OS, but it describes the resources carried
     in the extension image.</para>
+
+    <para>The <command>systemd-confext</command> concept follows the same principle as the
+    <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    functionality but instead of working on <filename>/usr</filename> and <filename>/opt</filename>,
+    <command>confext</command> will extend only <filename>/etc</filename>. Files and directories contained
+    in the confext images outside of the <filename>/etc/</filename> hierarchy are <emphasis>not</emphasis>
+    merged, and hence have no effect when included in the image. Formats for these images are of the
+    same as sysext images.</para>
+
+    <para>Confexts are looked for in the directories <filename>/run/confexts/</filename>,
+    <filename>/var/lib/confexts/</filename>, <filename>/usr/lib/confexts/</filename> and
+    <filename>/usr/local/lib/confexts/</filename>. The first two listed directories are not suitable for
+    carrying large binary images, however are still useful for carrying symlinks to them. The primary place
+    for installing system extensions is <filename>/var/lib/confexts/</filename>. Any directories found in
+    these search directories are considered directory based confext images, any files with the
+    <filename>.raw</filename> suffix are considered disk image based confext images.</para>
+
+    <para>Again, just like sysext images, the confext images will contain a
+    <filename>/etc/extension-release.d/extension-release.<replaceable>$name</replaceable></filename>
+    file, which must match the image name (with the usual escape hatch of xattr), and again with content
+    being one or more of <varname>ID=</varname>, <varname>VERSION_ID=</varname>, and
+    <varname>CONFEXT_LEVEL</varname>. Confext images will then be checked and matched against the
+    base OS layer.</para>
   </refsect1>
 
   <refsect1>
     <filename>/usr/</filename> as if it was installed in the OS image itself.) This case works regardless if
     the underlying host <filename>/usr/</filename> is managed as immutable disk image or is a traditional
     package manager controlled (i.e. writable) tree.</para>
-  </refsect1>
+
+    <para>For the confext case, the OSConfig project aims to perform runtime reconfiguration of OS services.
+    Sometimes, there is a need to swap certain configuration parameter values or restart only a specific
+    service without deployment of new code or a complete OS deployment. In other words, we want to be able
+    to tie the most frequently configured options to runtime updateable flags that can be changed without a
+    system reboot. This will help reduce servicing times when there is a need for changing the OS configuration.</para></refsect1>
 
   <refsect1>
     <title>Commands</title>
 
-    <para>The following commands are understood:</para>
+    <para>The following commands are understood by both the sysext and confext concepts:</para>
 
     <variablelist>
       <varlistentry>
         <term><option>status</option></term>
 
         <listitem><para>When invoked without any command verb, or when <option>status</option> is specified
-        the current merge status is shown, separately for both <filename>/usr/</filename> and
-        <filename>/opt/</filename>.</para></listitem>
+        the current merge status is shown, separately (for both <filename>/usr/</filename> and
+        <filename>/opt/</filename> of sysext and for <filename>/etc/</filename> of confext).</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <listitem><para>Merges all currently installed system extension images into
         <filename>/usr/</filename> and <filename>/opt/</filename>, by overmounting these hierarchies with an
         <literal>overlayfs</literal> file system combining the underlying hierarchies with those included in
-        the extension images. This command will fail if the hierarchies are already merged.</para></listitem>
+        the extension images. This command will fail if the hierarchies are already merged. For confext, the merge
+        happens into the <filename>/etc/</filename> directory instead.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>unmerge</option></term>
         <listitem><para>Unmerges all currently installed system extension images from
-        <filename>/usr/</filename> and <filename>/opt/</filename>, by unmounting the
-        <literal>overlayfs</literal> file systems created by <option>merge</option>
+        <filename>/usr/</filename> and <filename>/opt/</filename> for sysext and <filename>/etc/</filename>,
+        for confext, by unmounting the <literal>overlayfs</literal> file systems created by <option>merge</option>
         prior.</para></listitem>
       </varlistentry>
 
         mounted the existing <literal>overlayfs</literal> instance is unmounted temporarily, and then
         replaced by a new version. This command is useful after installing/removing system extension images,
         in order to update the <literal>overlayfs</literal> file system accordingly. If no system extensions
-        are installed when this command is executed, the equivalent of <option>unmerge</option> is
-        executed, without establishing any new <literal>overlayfs</literal> instance. Note that currently
-        there's a brief moment where neither the old nor the new <literal>overlayfs</literal> file system is
-        mounted. This implies that all resources supplied by a system extension will briefly disappear — even
-        if it exists continuously during the refresh operation.</para></listitem>
+        are installed when this command is executed, the equivalent of <option>unmerge</option> is executed,
+        without establishing any new <literal>overlayfs</literal> instance.
+        Note that currently there's a brief moment where neither the old nor the new <literal>overlayfs</literal>
+        file system is mounted. This implies that all resources supplied by a system extension will briefly
+        disappear — even if it exists continuously during the refresh operation.</para></listitem>
       </varlistentry>
 
       <varlistentry>
 
         <listitem><para>Operate relative to the specified root directory, i.e. establish the
         <literal>overlayfs</literal> mount not on the top-level host <filename>/usr/</filename> and
-        <filename>/opt/</filename> hierarchies, but below some specified root directory.</para></listitem>
+        <filename>/opt/</filename> hierarchies for sysext or <filename>/etc/</filename> for confext,
+        but below some specified root directory.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>--force</option></term>
 
         <listitem><para>When merging system extensions into <filename>/usr/</filename> and
-        <filename>/opt/</filename>, ignore version incompatibilities, i.e. force merging regardless of
-        whether the version information included in the extension images matches the host or
-        not.</para></listitem>
+        <filename>/opt/</filename> for sysext and <filename>/etc/</filename> for confext,
+        ignore version incompatibilities, i.e. force merging regardless of
+        whether the version information included in the images matches the host or not.</para></listitem>
       </varlistentry>
 
       <xi:include href="standard-options.xml" xpointer="no-pager" />
index c2e32f9f3df4760e2c06b4607e025a85b9ad5c6c..49eda985b49bc723209cb7bae1a884ee0e8b01ce 100644 (file)
         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>
index b71cedfa269e87ea983e1a021eab5d68ffc18779..ca0eefdc07576675dfd899778d9b5a929e2cdfbe 100644 (file)
         <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>
index e8be2ff46851f5c48e22bc62be3e287e550ae4ae..f64a8e538ff473370f248594f83395ca584b989f 100644 (file)
 
       <varlistentry>
         <term><varname>ExecStart=</varname></term>
-        <listitem><para>Commands with their arguments that are
-        executed when this service is started. The value is split into
-        zero or more command lines according to the rules described
-        below (see section "Command Lines" below).
-        </para>
+        <listitem><para>Commands that are executed when this service is started. The value is split into zero
+        or more command lines according to the rules described in the section "Command Lines" below.</para>
 
         <para>Unless <varname>Type=</varname> is <option>oneshot</option>, exactly one command must be given. When
         <varname>Type=oneshot</varname> is used, zero or more commands may be specified. Commands may be specified by
         <varname>ExecStop=</varname> line set. (Services lacking both <varname>ExecStart=</varname> and
         <varname>ExecStop=</varname> are not valid.)</para>
 
-        <para>For each of the specified commands, the first argument must be either an absolute path to an executable
-        or a simple file name without any slashes. Optionally, this filename may be prefixed with a number of special
-        characters:</para>
-
-        <table>
-          <title>Special executable prefixes</title>
-
-          <tgroup cols='2'>
-            <colspec colname='prefix'/>
-            <colspec colname='meaning'/>
-
-            <thead>
-              <row>
-                <entry>Prefix</entry>
-                <entry>Effect</entry>
-              </row>
-            </thead>
-            <tbody>
-              <row>
-                <entry><literal>@</literal></entry>
-                <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <literal>argv[0]</literal> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
-              </row>
-
-              <row>
-                <entry><literal>-</literal></entry>
-                <entry>If the executable path is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success.</entry>
-              </row>
-
-              <row>
-                <entry><literal>:</literal></entry>
-                <entry>If the executable path is prefixed with <literal>:</literal>, environment variable substitution (as described by the "Command Lines" section below) is not applied.</entry>
-              </row>
-
-              <row>
-                <entry><literal>+</literal></entry>
-                <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
-              </row>
-
-              <row>
-                <entry><literal>!</literal></entry>
-
-                <entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
-              </row>
-
-              <row>
-                <entry><literal>!!</literal></entry>
-
-                <entry>This prefix is very similar to <literal>!</literal>, however it only has an effect on systems lacking support for ambient process capabilities, i.e. without support for <varname>AmbientCapabilities=</varname>. It's intended to be used for unit files that take benefit of ambient capabilities to run processes with minimal privileges wherever possible while remaining compatible with systems that lack ambient capabilities support. Note that when <literal>!!</literal> is used, and a system lacking ambient capability support is detected any configured <varname>SystemCallFilter=</varname> and <varname>CapabilityBoundingSet=</varname> stanzas are implicitly modified, in order to permit spawned processes to drop credentials and capabilities themselves, even if this is configured to not be allowed. Moreover, if this prefix is used and a system lacking ambient capability support is detected <varname>AmbientCapabilities=</varname> will be skipped and not be applied. On systems supporting ambient capabilities, <literal>!!</literal> has no effect and is redundant.</entry>
-              </row>
-            </tbody>
-          </tgroup>
-        </table>
-
-        <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
-        <literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
-        order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
-        time. Note that these prefixes are also supported for the other command line settings,
-        i.e. <varname>ExecStartPre=</varname>, <varname>ExecStartPost=</varname>, <varname>ExecReload=</varname>,
-        <varname>ExecStop=</varname> and <varname>ExecStopPost=</varname>.</para>
-
         <para>If more than one command is specified, the commands are
         invoked sequentially in the order they appear in the unit
         file. If one of the commands fails (and is not prefixed with
         fully stopped and no job is queued or being executed for it. If this option is used,
         <varname>NotifyAccess=</varname> (see above) should be set to open access to the notification socket
         provided by systemd. If <varname>NotifyAccess=</varname> is not set, it will be implicitly set to
-        <option>main</option>.</para></listitem>
+        <option>main</option>.</para>
+
+        <para>The <command>fdstore</command> command of
+        <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        may be used to list the current contents of a service's file descriptor store.</para>
+
+        <para>Note that the service manager will only pass file descriptors contained in the file descriptor
+        store to the service's own processes, never to other clients via IPC or similar. However, it does
+        allow unprivileged clients to query the list of currently open file descriptors of a
+        service. Sensitive data may hence be safely placed inside the referenced files, but should not be
+        attached to the metadata (e.g. included in filenames) of the stored file
+        descriptors.</para></listitem>
       </varlistentry>
 
       <varlistentry>
 
     <para>The command to execute may contain spaces, but control characters are not allowed.</para>
 
+    <para>Each command may be prefixed with a number of special characters:</para>
+
+    <table>
+      <title>Special executable prefixes</title>
+
+      <tgroup cols='2'>
+        <colspec colname='prefix'/>
+        <colspec colname='meaning'/>
+
+        <thead>
+          <row>
+            <entry>Prefix</entry>
+            <entry>Effect</entry>
+          </row>
+        </thead>
+        <tbody>
+          <row>
+            <entry><literal>@</literal></entry>
+            <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <constant>argv[0]</constant> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
+          </row>
+
+          <row>
+            <entry><literal>-</literal></entry>
+            <entry>If the executable path is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success.</entry>
+          </row>
+
+          <row>
+            <entry><literal>:</literal></entry>
+            <entry>If the executable path is prefixed with <literal>:</literal>, environment variable substitution (as described by the "Command Lines" section below) is not applied.</entry>
+          </row>
+
+          <row>
+            <entry><literal>+</literal></entry>
+            <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
+          </row>
+
+          <row>
+            <entry><literal>!</literal></entry>
+
+            <entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
+          </row>
+
+          <row>
+            <entry><literal>!!</literal></entry>
+
+            <entry>This prefix is very similar to <literal>!</literal>, however it only has an effect on systems lacking support for ambient process capabilities, i.e. without support for <varname>AmbientCapabilities=</varname>. It's intended to be used for unit files that take benefit of ambient capabilities to run processes with minimal privileges wherever possible while remaining compatible with systems that lack ambient capabilities support. Note that when <literal>!!</literal> is used, and a system lacking ambient capability support is detected any configured <varname>SystemCallFilter=</varname> and <varname>CapabilityBoundingSet=</varname> stanzas are implicitly modified, in order to permit spawned processes to drop credentials and capabilities themselves, even if this is configured to not be allowed. Moreover, if this prefix is used and a system lacking ambient capability support is detected <varname>AmbientCapabilities=</varname> will be skipped and not be applied. On systems supporting ambient capabilities, <literal>!!</literal> has no effect and is redundant.</entry>
+          </row>
+        </tbody>
+      </tgroup>
+    </table>
+
+    <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
+    <literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
+    order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
+    time.</para>
+
+    <para>For each command, the first argument must be either an absolute path to an executable or a simple
+    file name without any slashes. If the command is not a full (absolute) path, it will be resolved to a
+    full path using a fixed search path determined at compilation time. Searched directories include
+    <filename>/usr/local/bin/</filename>, <filename>/usr/bin/</filename>, <filename>/bin/</filename> on
+    systems using split <filename>/usr/bin/</filename> and <filename>/bin/</filename> directories, and their
+    <filename>sbin/</filename> counterparts on systems using split <filename>bin/</filename> and
+    <filename>sbin/</filename>. It is thus safe to use just the executable name in case of executables
+    located in any of the "standard" directories, and an absolute path must be used in other cases. Using an
+    absolute path is recommended to avoid ambiguity. Hint: this search path may be queried using
+    <command>systemd-path search-binaries-default</command>.</para>
+
     <para>The command line accepts <literal>%</literal> specifiers as described in
     <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
 
     For this type of expansion, quotes are respected when splitting
     into words, and afterwards removed.</para>
 
-    <para>If the command is not a full (absolute) path, it will be resolved to a full path using a
-    fixed search path determined at compilation time. Searched directories include
-    <filename>/usr/local/bin/</filename>, <filename>/usr/bin/</filename>, <filename>/bin/</filename>
-    on systems using split <filename>/usr/bin/</filename> and <filename>/bin/</filename>
-    directories, and their <filename>sbin/</filename> counterparts on systems using split
-    <filename>bin/</filename> and <filename>sbin/</filename>. It is thus safe to use just the
-    executable name in case of executables located in any of the "standard" directories, and an
-    absolute path must be used in other cases. Using an absolute path is recommended to avoid
-    ambiguity. Hint: this search path may be queried using
-    <command>systemd-path search-binaries-default</command>.</para>
-
     <para>Example:</para>
 
     <programlisting>Environment="ONE=one" 'TWO=two two'
@@ -1360,6 +1364,17 @@ ExecStart=/bin/echo $ONE $TWO $THREE</programlisting>
 
     <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 / &gt;/dev/null &amp; \; \
 ls</programlisting>
 
index 9411ea2e0387153de85c5ded0198c4d8fbc7ed0b..c618e403f71ed8b9a62de7f71af33316174ad7ff 100644 (file)
           <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>
index b50423dc7759c75d5f69438000de38c0f24a8160..a23b9c8946e92a90c8afe301408cfeaf32141187 100644 (file)
@@ -647,13 +647,13 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
 # 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>
index 24114af4ecf4c42543bb5355a0e6b35982fbab4d..7ad3371ee7887c2f7006ce486cb3206733362a65 100755 (executable)
@@ -178,14 +178,15 @@ if [ -d mkosi.kernel/ ]; then
             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
index dbc50793a7a5aa824c638b0705b4d0551fda7318..2c7eb63e71c0c09e0c6684dfe53954d47955c790 100644 (file)
@@ -1,7 +1,5 @@
 # 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_.
@@ -12,7 +10,8 @@ OutputDirectory=mkosi.output
 
 [Content]
 BuildDirectory=mkosi.builddir
-Cache=mkosi.cache
+CacheDirectory=mkosi.cache
+ExtraTrees=src:/root/src
 Packages=
         acl
         bash-completion
@@ -65,8 +64,7 @@ BuildPackages=
         zstd
 
 [Host]
-QemuHeadless=yes
-Netdev=yes
+Acl=yes
 QemuMem=2G
 ExtraSearchPaths=build/
 KernelCommandLineExtra=systemd.crash_shell
@@ -74,6 +72,8 @@ KernelCommandLineExtra=systemd.crash_shell
                        systemd.journald.forward_to_console
                        systemd.journald.max_level_console=warning
                        systemd.mask=auditd
+                       # Tell the kernel to only log warning and up to the console.
+                       loglevel=4
 
 [Validation]
 Password=
similarity index 77%
rename from mkosi.conf.d/arch/10-arch.conf
rename to mkosi.conf.d/20-arch.conf
index e1c75b3996f8d36d4a04d463ee0be3a10d9c4f00..c926c88c1a014f05a40a9a42f50f75bdb2114cc6 100644 (file)
@@ -2,10 +2,7 @@
 #
 # 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]
@@ -31,6 +28,8 @@ Packages=
         polkit
         popt
         python-pefile
+        python-psutil
+        python-pytest
         quota-tools
         shadow
         tpm2-tss
@@ -47,4 +46,3 @@ BuildPackages=
         python-jinja
         python-lxml
         python-pyelftools
-        python-pytest
similarity index 66%
rename from mkosi.conf.d/centos/10-centos.conf
rename to mkosi.conf.d/20-centos.conf
index 2532b35f51ed63379a23b30c5c9899522d400d12..09b7973195252fad3bae23404d4d7256d106ccc9 100644 (file)
@@ -1,16 +1,10 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-# We use python3*dist() throughout this file because we need to make sure the python3.9dis() packages are
-# installed on CentOS Stream 8. mkosi doesn't support release specific configuration yet so we use the globs
-# to get the necessary packages on both CentOS Stream 8 and CentOS Stream 9.
+[Match]
+Distribution=centos
 
 [Distribution]
-Distribution=centos
 Repositories=epel
-RepositoryDirectory=mkosi.conf.d/centos/mkosi.reposdir
 
 [Content]
 Packages=
@@ -45,10 +39,7 @@ Packages=
         polkit
         popt
         procps-ng
-        python3[.][9]dist(pefile)
-        python3[.][9]dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
-        python3[.][9]dist(pytest)
-        python39
+        python3-docutils
         quota
         tpm2-tss
         vim-common
@@ -60,7 +51,6 @@ BuildPackages=
         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
@@ -101,7 +91,3 @@ BuildPackages=
         pkgconfig(tss2-rc)
         pkgconfig(valgrind)
         pkgconfig(xkbcommon)
-        python3*dist(docutils)
-        python3*dist(jinja2)
-        python3*dist(lxml)
-        python3*dist(pyelftools)
similarity index 90%
rename from mkosi.conf.d/debian/10-debian.conf
rename to mkosi.conf.d/20-debian.conf
index e9b5775a3733874afd724f398762eafdf52c4c5d..5244194a01d1b032ba02f4de55e7b2a448eaa895 100644 (file)
@@ -1,10 +1,9 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=debian
 
 [Distribution]
-Distribution=debian
 Release=testing
 
 [Content]
@@ -40,6 +39,8 @@ Packages=
         policykit-1
         procps
         python3-pefile
+        python3-psutil
+        python3-pytest
         quota
         xxd
 
@@ -92,5 +93,4 @@ BuildPackages=
         python3-jinja2
         python3-lxml
         python3-pyelftools
-        python3-pytest
         xsltproc
similarity index 89%
rename from mkosi.conf.d/fedora/10-fedora.conf
rename to mkosi.conf.d/20-fedora.conf
index b4c641cd67e6f4b210587004f660df18596bfb93..5967c3670f3a383f86e14e87e6d64c2be0c0c3a2 100644 (file)
@@ -1,10 +1,9 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=fedora
 
 [Distribution]
-Distribution=fedora
 Release=37
 
 [Content]
@@ -41,6 +40,8 @@ Packages=
         popt
         procps-ng
         python3dist(pefile)
+        python3dist(psutil)
+        python3dist(pytest)
         quota
         tpm2-tss
         vim-common
@@ -90,8 +91,7 @@ BuildPackages=
         pkgconfig(valgrind)
         pkgconfig(xencontrol)
         pkgconfig(xkbcommon)
-        python3dist(docutils)
+        python3-docutils
         python3dist(jinja2)
         python3dist(lxml)
         python3dist(pyelftools)
-        python3dist(pytest)
similarity index 91%
rename from mkosi.conf.d/opensuse/10-opensuse.conf
rename to mkosi.conf.d/20-opensuse.conf
index ae0486850c896f9b49ee28f2b5bbe53831516cf7..96093951e1d419719461751d85da9abc1702dd17 100644 (file)
@@ -1,10 +1,9 @@
 # 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]
@@ -39,6 +38,8 @@ Packages=
         libxkbcommon0
         pam
         python3-pefile
+        python3-psutil
+        python3-pytest
         shadow
         tpm2-0-tss
         vim
@@ -95,7 +96,6 @@ BuildPackages=
         python3-Jinja2
         python3-lxml
         python3-pyelftools
-        python3-pytest
         qrencode-devel
         shadow
         system-group-obsolete
similarity index 90%
rename from mkosi.conf.d/ubuntu/10-ubuntu.conf
rename to mkosi.conf.d/20-ubuntu.conf
index ffc1d54456239ef545cb12282fdb297bc398552b..97deb7094735749c887ba4b5e02b4a911e9479c6 100644 (file)
@@ -1,10 +1,9 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=ubuntu
 
 [Distribution]
-Distribution=ubuntu
 Release=jammy
 Repositories=main,universe
 
@@ -42,6 +41,8 @@ Packages=
         policykit-1
         procps
         python3-pefile
+        python3-psutil
+        python3-pytest
         quota
         xxd
 
@@ -93,5 +94,4 @@ BuildPackages=
         python3-jinja2
         python3-lxml
         python3-pyelftools
-        python3-pytest
         xsltproc
diff --git a/mkosi.conf.d/21-centos-8/mkosi.conf b/mkosi.conf.d/21-centos-8/mkosi.conf
new file mode 100644 (file)
index 0000000..d610212
--- /dev/null
@@ -0,0 +1,19 @@
+# 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)
similarity index 84%
rename from mkosi.conf.d/centos/mkosi.reposdir/powertools.repo
rename to mkosi.conf.d/21-centos-8/mkosi.reposdir/powertools.repo
index 5c7149a123378c96b2dd1d44ca712009657e9b2e..1462257c087e9e3e5759a27bf44f11ddf4113e17 100644 (file)
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
 [powertools-hotfixes]
 name=powertools-hotfixes
 mirrorlist=http://mirrorlist.centos.org/?release=$stream&arch=$basearch&repo=PowerTools
diff --git a/mkosi.conf.d/21-centos-9.conf b/mkosi.conf.d/21-centos-9.conf
new file mode 100644 (file)
index 0000000..0febf2c
--- /dev/null
@@ -0,0 +1,18 @@
+# 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)
index 522e1fecb658790af77fb9923e5073019e797aee..1a2163e3a54274525036357ab68a826ac24522d6 100644 (file)
@@ -1,2 +1,3 @@
 set debuginfod enabled off
 set build-id-verbose 0
+set substitute-path ../src /root/src
index d12074d4da5e9d6e5881f684cfa9e02ef4793756..ab3ffe2fea330861790ffd07b23a40a9460f03f6 100644 (file)
@@ -45,6 +45,8 @@ CONFIG_DEVTMPFS=y
 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
@@ -59,8 +61,11 @@ CONFIG_HIGH_RES_TIMERS=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
@@ -73,6 +78,7 @@ CONFIG_INTEGRITY_ASYMMETRIC_KEYS=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
@@ -159,16 +165,11 @@ CONFIG_SCSI_VIRTIO=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
@@ -192,6 +193,8 @@ CONFIG_VIRTIO_CONSOLE=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
index 26b0f9575e31ba442817a48cf7667024c8c52939..f66601e3ea2fd8653335ac3f3dd4b3772525aec0 100644 (file)
--- a/po/ru.po
+++ b/po/ru.po
@@ -7,21 +7,22 @@
 # 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"
@@ -980,14 +981,14 @@ msgstr ""
 
 #: 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
index cebd25a87180e89f7235979625974db27cbc7612..ee160a79008c96dae0cbc0bd030677ad6877b50d 100644 (file)
@@ -139,7 +139,7 @@ _busctl() {
         fi
     done
 
-    n=$(($COMP_CWORD - $i))
+    n=$((COMP_CWORD - i))
 
     if [[ -z ${verb-} ]]; then
         comps=${VERBS[*]}
index 71789277b09f8fced1e59c7d930be4eba5269d80..31601dceb29ab641232dfb3b21c8eb3fe0498241 100644 (file)
@@ -78,7 +78,7 @@ _portablectl() {
         fi
     done
 
-    n=$(($COMP_CWORD - $i))
+    n=$((COMP_CWORD - i))
 
     if [[ -z ${verb-} ]]; then
         comps=${VERBS[*]}
diff --git a/shell-completion/bash/systemd-confext b/shell-completion/bash/systemd-confext
new file mode 100644 (file)
index 0000000..c8eca3b
--- /dev/null
@@ -0,0 +1,85 @@
+# 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
diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c
new file mode 100644 (file)
index 0000000..13db7f5
--- /dev/null
@@ -0,0 +1,110 @@
+/* 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;
+}
diff --git a/src/analyze/analyze-fdstore.h b/src/analyze/analyze-fdstore.h
new file mode 100644 (file)
index 0000000..0b990db
--- /dev/null
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+int verb_fdstore(int argc, char *argv[], void *userdata);
index 12c0bd653fa68d6dbcfe6ededeae2d4f8b78508c..0246da4b45dae0d2602140f223f9b893b54ca37d 100644 (file)
 #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"
@@ -36,7 +38,6 @@
 #include "analyze-timestamp.h"
 #include "analyze-unit-files.h"
 #include "analyze-unit-paths.h"
-#include "analyze-compare-versions.h"
 #include "analyze-verify.h"
 #include "build.h"
 #include "bus-error.h"
@@ -229,6 +230,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  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"
@@ -531,9 +533,9 @@ static int parse_argv(int argc, char *argv[]) {
                 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),
@@ -620,6 +622,7 @@ static int run(int argc, char *argv[]) {
                 { "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           },
                 {}
         };
 
index ac40600a6dedd53325fee303e35385df97cf811b..695089a0beda7da92d29915db365807fd7e62f0e 100644 (file)
@@ -11,6 +11,7 @@ systemd_analyze_sources = files(
         'analyze-dot.c',
         'analyze-dump.c',
         'analyze-exit-status.c',
+        'analyze-fdstore.c',
         'analyze-filesystems.c',
         'analyze-inspect-elf.c',
         'analyze-log-control.c',
index e9c09b78252b8b567124070e08491c822d02014d..de41d7b641d24cc07f2696c97ec6a65bb81cba9c 100644 (file)
@@ -142,14 +142,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 /* 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;
 
@@ -199,13 +197,11 @@ static int parse_argv(int argc, char *argv[]) {
         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) {
index 096526a8570e224592eabe42f0c6ebb30f15776f..788f3abc37f7d2636a96dc06e293cc9330f9160f 100644 (file)
@@ -197,10 +197,10 @@ Architecture uname_architecture(void);
 #  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)
index 6c02aeab8ba2ebea7764d192351c5b30a9c80c2e..318454ca88eb81dee3796cd870ea149583b86130 100644 (file)
@@ -72,13 +72,7 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) {
                                  strna(n1));
 }
 
-int chaseat(
-                int dir_fd,
-                const char *path,
-                ChaseFlags flags,
-                char **ret_path,
-                int *ret_fd) {
-
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
         _cleanup_free_ char *buffer = NULL, *done = NULL;
         _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
         unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
@@ -164,6 +158,17 @@ int chaseat(
          *    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)) &&
@@ -195,7 +200,7 @@ int chaseat(
         }
 
         /* 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);
@@ -409,10 +414,10 @@ int chaseat(
                         _cleanup_free_ char *f = NULL;
 
                         r = path_extract_filename(done, &f);
-                        if (r < 0 && r != -EDESTADDRREQ)
+                        if (r < 0 && r != -EADDRNOTAVAIL)
                                 return r;
 
-                        /* If we get EDESTADDRREQ we clear done and it will get reinitialized by the next block. */
+                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
                         free_and_replace(done, f);
                 }
 
@@ -468,13 +473,7 @@ chased_one:
         return 0;
 }
 
-int chase(
-                const char *path,
-                const char *original_root,
-                ChaseFlags flags,
-                char **ret_path,
-                int *ret_fd) {
-
+int chase(const char *path, const char *original_root, ChaseFlags flags, char **ret_path, int *ret_fd) {
         _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
         _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
         int r;
@@ -517,18 +516,18 @@ int chase(
         path = path_startswith(absolute, empty_to_root(root));
         if (!path)
                 return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
-                                        SYNTHETIC_ERRNO(ECHRNG),
-                                        "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
-                                        absolute, empty_to_root(root));
+                                      SYNTHETIC_ERRNO(ECHRNG),
+                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+                                      absolute, empty_to_root(root));
 
         fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
         if (fd < 0)
                 return -errno;
 
-        flags |= CHASE_AT_RESOLVE_IN_ROOT;
-        flags &= ~CHASE_PREFIX_ROOT;
+        if (!empty_or_root(root))
+                flags |= CHASE_AT_RESOLVE_IN_ROOT;
 
-        r = chaseat(fd, path, flags, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
+        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
         if (r < 0)
                 return r;
 
@@ -558,13 +557,7 @@ int chase(
         return r;
 }
 
-int chase_and_open(
-                const char *path,
-                const char *root,
-                ChaseFlags chase_flags,
-                int open_flags,
-                char **ret_path) {
-
+int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL, *fname = NULL;
         mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
@@ -589,14 +582,13 @@ int chase_and_open(
         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;
 
@@ -606,13 +598,7 @@ int chase_and_open(
         return r;
 }
 
-int chase_and_opendir(
-                const char *path,
-                const char *root,
-                ChaseFlags chase_flags,
-                char **ret_path,
-                DIR **ret_dir) {
-
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         DIR *d;
@@ -648,13 +634,7 @@ int chase_and_opendir(
         return 0;
 }
 
-int chase_and_stat(
-                const char *path,
-                const char *root,
-                ChaseFlags chase_flags,
-                char **ret_path,
-                struct stat *ret_stat) {
-
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -683,13 +663,7 @@ int chase_and_stat(
         return 0;
 }
 
-int chase_and_access(
-                const char *path,
-                const char *root,
-                ChaseFlags chase_flags,
-                int access_mode,
-                char **ret_path) {
-
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -753,13 +727,7 @@ int chase_and_fopen_unlocked(
         return 0;
 }
 
-int chase_and_unlink(
-                const char *path,
-                const char *root,
-                ChaseFlags chase_flags,
-                int unlink_flags,
-                char **ret_path) {
-
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
         _cleanup_free_ char *p = NULL, *fname = NULL;
         _cleanup_close_ int fd = -EBADF;
         int r;
@@ -796,13 +764,7 @@ int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_f
         return pfd;
 }
 
-int chase_and_openat(
-                int dir_fd,
-                const char *path,
-                ChaseFlags chase_flags,
-                int open_flags,
-                char **ret_path) {
-
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL, *fname = NULL;
         mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
@@ -821,14 +783,13 @@ int chase_and_openat(
         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;
 
@@ -838,13 +799,7 @@ int chase_and_openat(
         return r;
 }
 
-int chase_and_opendirat(
-                int dir_fd,
-                const char *path,
-                ChaseFlags chase_flags,
-                char **ret_path,
-                DIR **ret_dir) {
-
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         DIR *d;
@@ -880,13 +835,7 @@ int chase_and_opendirat(
         return 0;
 }
 
-int chase_and_statat(
-                int dir_fd,
-                const char *path,
-                ChaseFlags chase_flags,
-                char **ret_path,
-                struct stat *ret_stat) {
-
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -915,13 +864,7 @@ int chase_and_statat(
         return 0;
 }
 
-int chase_and_accessat(
-                int dir_fd,
-                const char *path,
-                ChaseFlags chase_flags,
-                int access_mode,
-                char **ret_path) {
-
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -985,13 +928,7 @@ int chase_and_fopenat_unlocked(
         return 0;
 }
 
-int chase_and_unlinkat(
-                int dir_fd,
-                const char *path,
-                ChaseFlags chase_flags,
-                int unlink_flags,
-                char **ret_path) {
-
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
         _cleanup_free_ char *p = NULL, *fname = NULL;
         _cleanup_close_ int fd = -EBADF;
         int r;
@@ -1016,12 +953,7 @@ int chase_and_unlinkat(
         return 0;
 }
 
-int chase_and_open_parent_at(
-                int dir_fd,
-                const char *path,
-                ChaseFlags chase_flags,
-                char **ret_filename) {
-
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
         int pfd, r;
 
         assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
index 0a330ecb55137d1e92b855f3066443ebe45dc0de..59621dc05ba069b1d880181231abfdf0767f517b 100644 (file)
@@ -65,6 +65,16 @@ static const char* const compression_table[_COMPRESSION_MAX] = {
 
 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
index 583b105c66fdeda841f8f096bcb9c6df0ce2028a..2201bca74ca0297df067a99f77cc386fad95ed6d 100644 (file)
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <errno.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <unistd.h>
 
@@ -17,6 +18,8 @@ typedef enum Compression {
 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,
index 640bddc4851401c191091c9b7e556a5a47f6b576..3e674a8dba88cfe0619ec5c9dc44f93148407870 100644 (file)
@@ -1,6 +1,9 @@
 /* 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)
index de02255eef6493c1af47adec44b2980827477444..f82e13bdb02875449bcf2ee7d5233bd8dbb94c2a 100644 (file)
@@ -83,7 +83,7 @@ int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
 
         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);
index 38aa4efa87b602a4771e1b3c6ee99b01fa7e2193..e109de991360b28676b55a060d5718873c1328cc 100644 (file)
@@ -50,3 +50,7 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) {
 }
 
 #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;
+}
index b968bf948c4644c0d1da600123425dca01944eee..3cc4f44bcd925dc45f22d31ea7dcdd27d8a37110 100644 (file)
@@ -534,6 +534,11 @@ bool fdname_is_valid(const char *s) {
 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
@@ -935,3 +940,16 @@ int dir_fd_is_root(int dir_fd) {
          */
         return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
 }
+
+const char *accmode_to_string(int flags) {
+        switch (flags & O_ACCMODE) {
+        case O_RDONLY:
+                return "ro";
+        case O_WRONLY:
+                return "wo";
+        case O_RDWR:
+                return "rw";
+        default:
+                return NULL;
+        }
+}
index a6353cf48e4b824eda6880a55f923bcc0e56f50d..655ad2928475173d04b138a3ecf42dff1ccc8571 100644 (file)
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <sys/socket.h>
@@ -101,6 +102,9 @@ int read_nr_open(void);
 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 \
@@ -115,3 +119,5 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f
 
 #define FORMAT_PROC_FD_PATH(fd) \
         format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
+
+const char *accmode_to_string(int flags);
index 257c11ad715fc063926aff5a7e046ddc4fc1da9c..3722eb638b4c4f62875beecdc3dcfba9847f76f3 100644 (file)
@@ -824,7 +824,7 @@ 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);
@@ -1106,7 +1106,7 @@ int xopenat(int dir_fd, const char *path, int flags, mode_t mode) {
 
         if (isempty(path)) {
                 assert(!FLAGS_SET(flags, O_CREAT|O_EXCL));
-                return fd_reopen(dir_fd, flags);
+                return fd_reopen(dir_fd, flags & ~O_NOFOLLOW);
         }
 
         if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) {
index 7caa587ebf075331e0c2afe772d41835c2732d3b..75f59c43431f78ae530e1ffb30a3a22a80ff6969 100644 (file)
@@ -88,6 +88,8 @@ typedef struct 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
@@ -395,7 +397,7 @@ static int write_to_console(
              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;
 
@@ -434,6 +436,10 @@ static int write_to_console(
 
         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);
@@ -493,6 +499,8 @@ static int write_to_syslog(
                 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 = {
@@ -554,6 +562,8 @@ static int write_to_kmsg(
                 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"),
         };
@@ -665,13 +675,17 @@ static int write_to_journal(
         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");
 
@@ -847,16 +861,9 @@ int log_object_internalv(
         /* 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,
@@ -1572,6 +1579,15 @@ void log_setup(void) {
                 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) {
index 1e22293187ece6e7d97b3623a51379b130ac120d..9008d473909e13f642b8e1275ceba1e7f992a739 100644 (file)
@@ -426,6 +426,16 @@ typedef struct LogRateLimit {
 #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.
index 00937d2af03d91ff58743f76baf8e7487114c501..79e95a8f6fda9e0839ea21130f2d0dc60d86110b 100644 (file)
 #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
index 3b7cf7a0bf4c0d5b6f36378c77f9b7b54d8570ba..41af1482bc7bfbf0d139bcc4c913df485d6389ef 100644 (file)
@@ -33,11 +33,8 @@ int mkdirat_safe_internal(
         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;
 
index c8b23b10e471c4d2073376792fbd91c075f1e81a..8cb8d9302be26d999435c823c038b49974e0163e 100644 (file)
 #include "utf8.h"
 #include "xattr-util.h"
 
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+        const char *release_file_directory;
+        const char *release_file_path_prefix;
+} image_class_release_info[_IMAGE_CLASS_MAX] = {
+        [IMAGE_SYSEXT] = {
+                .release_file_directory = "/usr/lib/extension-release.d/",
+                .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
+        },
+        [IMAGE_CONFEXT] = {
+                .release_file_directory = "/etc/extension-release.d/",
+                .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
+        }
+};
+
 bool image_name_is_valid(const char *s) {
         if (!filename_is_valid(s))
                 return false;
@@ -36,10 +51,12 @@ bool image_name_is_valid(const char *s) {
         return true;
 }
 
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check) {
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
         int r;
 
         assert(path);
+        assert(image_class >= 0);
+        assert(image_class < _IMAGE_CLASS_MAX);
 
         /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
          * always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from
@@ -48,8 +65,9 @@ int path_is_extension_tree(const char *path, const char *extension, bool relax_e
                 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)
@@ -96,18 +114,21 @@ static int extension_release_strict_xattr_value(int extension_release_fd, const
         return false;
 }
 
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
         _cleanup_free_ char *q = NULL;
         int r, fd;
 
         if (extension) {
+                assert(image_class >= 0);
+                assert(image_class < _IMAGE_CLASS_MAX);
+
                 const char *extension_full_path;
 
                 if (!image_name_is_valid(extension))
                         return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "The extension name %s is invalid.", extension);
 
-                extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
+                extension_full_path = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
                 r = chase(extension_full_path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
                 log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
 
@@ -120,10 +141,10 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
                         _cleanup_free_ char *extension_release_dir_path = NULL;
                         _cleanup_closedir_ DIR *extension_release_dir = NULL;
 
-                        r = chase_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
+                        r = chase_and_opendir(image_class_release_info[image_class].release_file_directory, root, CHASE_PREFIX_ROOT,
                                               &extension_release_dir_path, &extension_release_dir);
                         if (r < 0)
-                                return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
+                                return log_debug_errno(r, "Cannot open %s%s, ignoring: %m", root, image_class_release_info[image_class].release_file_directory);
 
                         r = -ENOENT;
                         FOREACH_DIRENT(de, extension_release_dir, return -errno) {
@@ -137,7 +158,7 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
                                         continue;
 
                                 if (!image_name_is_valid(image_name)) {
-                                        log_debug("%s/%s is not a valid extension-release file name, ignoring.",
+                                        log_debug("%s/%s is not a valid release file name, ignoring.",
                                                   extension_release_dir_path, de->d_name);
                                         continue;
                                 }
@@ -149,7 +170,7 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
                                                                                   O_PATH|O_CLOEXEC|O_NOFOLLOW);
                                 if (extension_release_fd < 0)
                                         return log_debug_errno(errno,
-                                                               "Failed to open extension-release file %s/%s: %m",
+                                                               "Failed to open release file %s/%s: %m",
                                                                extension_release_dir_path,
                                                                de->d_name);
 
@@ -219,16 +240,16 @@ int open_extension_release(const char *root, const char *extension, bool relax_e
         return 0;
 }
 
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+int fopen_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
         _cleanup_free_ char *p = NULL;
         _cleanup_close_ int fd = -EBADF;
         FILE *f;
         int r;
 
         if (!ret_file)
-                return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
+                return open_extension_release(root, image_class, extension, relax_extension_release_check, ret_path, NULL);
 
-        r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+        r = open_extension_release(root, image_class, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
         if (r < 0)
                 return r;
 
@@ -243,24 +264,27 @@ int fopen_extension_release(const char *root, const char *extension, bool relax_
         return 0;
 }
 
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
+static int parse_release_internal(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, va_list ap) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
         int r;
 
-        r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+        r = fopen_extension_release(root, image_class, extension, relax_extension_release_check, &p, &f);
         if (r < 0)
                 return r;
 
         return parse_env_filev(f, p, ap);
 }
 
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int _parse_extension_release(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) {
         va_list ap;
         int r;
 
+        assert(image_class >= 0);
+        assert(image_class < _IMAGE_CLASS_MAX);
+
         va_start(ap, extension);
-        r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+        r = parse_release_internal(root, image_class, relax_extension_release_check, extension, ap);
         va_end(ap);
 
         return r;
@@ -271,7 +295,7 @@ int _parse_os_release(const char *root, ...) {
         int r;
 
         va_start(ap, root);
-        r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
+        r = parse_release_internal(root, -1, /* relax_extension_release_check= */ false, NULL, ap);
         va_end(ap);
 
         return r;
@@ -318,12 +342,15 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char
         return 0;
 }
 
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret) {
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
         int r;
 
-        r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+        assert(image_class >= 0);
+        assert(image_class < _IMAGE_CLASS_MAX);
+
+        r = fopen_extension_release(root, image_class, extension, relax_extension_release_check, &p, &f);
         if (r < 0)
                 return r;
 
index 3bafeaeb92d2031a9c2d599c5de48ecfeed0d308..2f641ba9cd1f18046db318776aa4c17f78815b0c 100644 (file)
@@ -5,33 +5,44 @@
 #include <stdio.h>
 
 #include "time-util.h"
+typedef enum ImageClass {
+        IMAGE_MACHINE,
+        IMAGE_PORTABLE,
+        IMAGE_SYSEXT,
+        IMAGE_CONFEXT,
+        _IMAGE_CLASS_MAX,
+        _IMAGE_CLASS_INVALID = -EINVAL,
+} ImageClass;
+
+const char* image_class_to_string(ImageClass cl) _const_;
+ImageClass image_class_from_string(const char *s) _pure_;
 
 /* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
  * in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
 
 bool image_name_is_valid(const char *s) _pure_;
 
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check);
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
 static inline int path_is_os_tree(const char *path) {
-        return path_is_extension_tree(path, NULL, false);
+        return path_is_extension_tree(IMAGE_SYSEXT, path, NULL, false);
 }
 
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
 static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
-        return open_extension_release(root, NULL, false, ret_path, ret_fd);
+        return open_extension_release(root, IMAGE_SYSEXT, NULL, false, ret_path, ret_fd);
 }
 
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
+int fopen_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
 static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
-        return fopen_extension_release(root, NULL, false, ret_path, ret_file);
+        return fopen_extension_release(root, IMAGE_SYSEXT, NULL, false, ret_path, ret_file);
 }
 
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+int _parse_extension_release(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
 int _parse_os_release(const char *root, ...) _sentinel_;
-#define parse_extension_release(root, relax_extension_release_check, extension, ...) _parse_extension_release(root, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_extension_release(root, image_class, relax_extension_release_check, extension, ...) _parse_extension_release(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
 #define parse_os_release(root, ...) _parse_os_release(root, __VA_ARGS__, NULL)
 
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret);
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
 int load_os_release_pairs(const char *root, char ***ret);
 int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
 
index 150605e86d592522c361a5b96f08e48d63058f69..e97770707f2099c8a29ad1d0ac2eb932287efb7b 100644 (file)
@@ -152,17 +152,6 @@ int null_or_empty_path_with_root(const char *fn, const char *root) {
         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;
 
@@ -466,6 +455,20 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
         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);
@@ -482,3 +485,26 @@ int inode_compare_func(const struct stat *a, const struct stat *b) {
 }
 
 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;
+}
index 4bf15a731d9f8f4401bd6f170b2a27e6ca9deca9..e6b84d215e0afca050b91743f581f645cb1c67df 100644 (file)
@@ -30,7 +30,6 @@ static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
 
 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);
@@ -80,6 +79,8 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b);
 
 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)              \
@@ -102,3 +103,5 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
 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);
index d3aa5c27e2b788d9e0c2a0353ea51fa54c37eb24..796c262acfd1501544aa54ed27cd227bf8dfd8ff 100644 (file)
@@ -979,7 +979,7 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
                    &ttynr) != 1)
                 return -EIO;
 
-        if (major(ttynr) == 0 && minor(ttynr) == 0)
+        if (devnum_is_zero(ttynr))
                 return -ENXIO;
 
         if (d)
index bdb1860246fecba7c269a12912d2f893df6e6eff..a0fab46a193b3c6e62381425baad30335015398e 100644 (file)
@@ -180,27 +180,29 @@ static const char* const scope_state_table[_SCOPE_STATE_MAX] = {
 DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
 
 static const char* const service_state_table[_SERVICE_STATE_MAX] = {
-        [SERVICE_DEAD]           = "dead",
-        [SERVICE_CONDITION]      = "condition",
-        [SERVICE_START_PRE]      = "start-pre",
-        [SERVICE_START]          = "start",
-        [SERVICE_START_POST]     = "start-post",
-        [SERVICE_RUNNING]        = "running",
-        [SERVICE_EXITED]         = "exited",
-        [SERVICE_RELOAD]         = "reload",
-        [SERVICE_RELOAD_SIGNAL]  = "reload-signal",
-        [SERVICE_RELOAD_NOTIFY]  = "reload-notify",
-        [SERVICE_STOP]           = "stop",
-        [SERVICE_STOP_WATCHDOG]  = "stop-watchdog",
-        [SERVICE_STOP_SIGTERM]   = "stop-sigterm",
-        [SERVICE_STOP_SIGKILL]   = "stop-sigkill",
-        [SERVICE_STOP_POST]      = "stop-post",
-        [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
-        [SERVICE_FINAL_SIGTERM]  = "final-sigterm",
-        [SERVICE_FINAL_SIGKILL]  = "final-sigkill",
-        [SERVICE_FAILED]         = "failed",
-        [SERVICE_AUTO_RESTART]   = "auto-restart",
-        [SERVICE_CLEANING]       = "cleaning",
+        [SERVICE_DEAD]                       = "dead",
+        [SERVICE_CONDITION]                  = "condition",
+        [SERVICE_START_PRE]                  = "start-pre",
+        [SERVICE_START]                      = "start",
+        [SERVICE_START_POST]                 = "start-post",
+        [SERVICE_RUNNING]                    = "running",
+        [SERVICE_EXITED]                     = "exited",
+        [SERVICE_RELOAD]                     = "reload",
+        [SERVICE_RELOAD_SIGNAL]              = "reload-signal",
+        [SERVICE_RELOAD_NOTIFY]              = "reload-notify",
+        [SERVICE_STOP]                       = "stop",
+        [SERVICE_STOP_WATCHDOG]              = "stop-watchdog",
+        [SERVICE_STOP_SIGTERM]               = "stop-sigterm",
+        [SERVICE_STOP_SIGKILL]               = "stop-sigkill",
+        [SERVICE_STOP_POST]                  = "stop-post",
+        [SERVICE_FINAL_WATCHDOG]             = "final-watchdog",
+        [SERVICE_FINAL_SIGTERM]              = "final-sigterm",
+        [SERVICE_FINAL_SIGKILL]              = "final-sigkill",
+        [SERVICE_FAILED]                     = "failed",
+        [SERVICE_DEAD_BEFORE_AUTO_RESTART]   = "dead-before-auto-restart",
+        [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
+        [SERVICE_AUTO_RESTART]               = "auto-restart",
+        [SERVICE_CLEANING]                   = "cleaning",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
index bae132ea097c185eca57a6576c35b2a089d1fd58..2fab42e9c7204adb66deca0cc380bea66df69d36 100644 (file)
@@ -144,6 +144,8 @@ typedef enum ServiceState {
         SERVICE_FINAL_SIGTERM,     /* In case the STOP_POST executable hangs, we shoot that down, too */
         SERVICE_FINAL_SIGKILL,
         SERVICE_FAILED,
+        SERVICE_DEAD_BEFORE_AUTO_RESTART,
+        SERVICE_FAILED_BEFORE_AUTO_RESTART,
         SERVICE_AUTO_RESTART,
         SERVICE_CLEANING,
         _SERVICE_STATE_MAX,
index 70b7eb16063f3c64461c1b7c0f416b60271ac260..41422579d6415ecdca607043e7e9cd4dd0570a3c 100644 (file)
@@ -71,10 +71,11 @@ int unit_symlink_name_compatible(const char *symlink, const char *target, bool i
 }
 
 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
@@ -87,8 +88,13 @@ int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, con
          * -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 */
 
@@ -762,7 +768,10 @@ int unit_file_find_fragment(
         }
 
         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 */
index a08683bceab096bece04de7049ac431b5a0d90e5..f7ff35d0e07041e9c31a7a96b26f188c1991d38a 100644 (file)
@@ -80,7 +80,8 @@ int take_etc_passwd_lock(const char *root);
 #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 */
index a50f984a23631a056c3d5a339a9b7fb28fbf689e..2dd50360ecd471a21a1600fcf566a6a39e252f7e 100644 (file)
@@ -189,29 +189,29 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t
         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;
 }
@@ -391,6 +391,10 @@ static int install_binaries(const char *esp_path, const char *arch, bool force)
         /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
         if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
                 r = chase_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+        if (r == -ENOENT && arg_graceful) {
+                log_debug("Source directory does not exist, ignoring.");
+                return 0;
+        }
         if (r < 0)
                 return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
 
@@ -415,7 +419,7 @@ static int install_binaries(const char *esp_path, const char *arch, bool force)
                 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;
@@ -852,9 +856,11 @@ static int remove_boot_efi(const char *esp_path) {
                         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);
index cc0c3ad34ad839a929b656478eafb46e422d3b3e..3da6478259010004afa22e1e31f8cd6d687e18ff 100644 (file)
@@ -207,7 +207,7 @@ static int enumerate_binaries(
                 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"))
@@ -216,15 +216,23 @@ static int enumerate_binaries(
                 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);
index 6cae9b4debc0184dc9478da712f662e9f5baab46..d93cc20318419fc7671c9d5bb0d9ed19e63b3008 100644 (file)
@@ -63,7 +63,7 @@ int get_file_version(int fd, char **ret) {
         struct stat st;
         char *buf;
         const char *s, *e;
-        char *x = NULL;
+        char *marker = NULL;
         int r;
 
         assert(fd >= 0);
@@ -73,42 +73,43 @@ int get_file_version(int fd, char **ret) {
                 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;
 }
 
index 710cf8da65b832b4bc114f9f37d19d1b3ae63a7c..82c7e498ba608e49b9bc883cd6aa1d4551b19dc6 100644 (file)
@@ -248,7 +248,6 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         int c, r;
-        bool b;
 
         assert(argc >= 0);
         assert(argv);
@@ -338,11 +337,11 @@ static int parse_argv(int argc, char *argv[]) {
                         if (streq(optarg, "auto"))  /* retained for backwards compatibility */
                                 arg_make_entry_directory = -1; /* yes if machine-id is permanent */
                         else {
-                                r = parse_boolean_argument("--make-entry-directory=", optarg, &b);
+                                r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
                                 if (r < 0)
                                         return r;
 
-                                arg_make_entry_directory = b;
+                                arg_make_entry_directory = r;
                         }
                         break;
 
@@ -366,7 +365,8 @@ static int parse_argv(int argc, char *argv[]) {
                         }
                         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;
index c88ef93443c97c263873e732bba9a433506757fc..2cb9efc14e18d5b5526b460c231b0a9998cbd09b 100644 (file)
@@ -2840,6 +2840,10 @@ static int method_set_show_status(sd_bus_message *message, void *userdata, sd_bu
         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),
 
@@ -3385,6 +3389,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
                                 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),
index ddfffe80fd6c1977852ffdc9ec77b4655746bc27..0f6e3152330d07ba9624250247567312f6b92937 100644 (file)
@@ -4,6 +4,7 @@
 
 #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"
@@ -16,6 +17,7 @@
 #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"
@@ -33,7 +35,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, Service
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
 static BUS_DEFINE_PROPERTY_GET2(property_get_notify_access, "s", Service, service_get_notify_access, notify_access_to_string);
-static BUS_DEFINE_PROPERTY_GET(property_get_restart_usec_current, "t", Service, service_restart_usec);
+static BUS_DEFINE_PROPERTY_GET(property_get_restart_usec_next, "t", Service, service_restart_usec_next);
 static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, service_timeout_abort_usec);
 static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode);
@@ -218,6 +220,72 @@ int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_b
         return bus_service_method_mount(message, userdata, error, true);
 }
 
+int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        Service *s = ASSERT_PTR(userdata);
+        int r;
+
+        assert(message);
+
+        r = mac_selinux_unit_access_check(UNIT(s), message, "status", error);
+        if (r < 0)
+                return r;
+
+        if (s->n_fd_store_max == 0 && s->n_fd_store == 0)
+                return sd_bus_error_setf(error, BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED, "File descriptor store not enabled for %s.", UNIT(s)->id);
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(suuutuusu)");
+        if (r < 0)
+                return r;
+
+        LIST_FOREACH(fd_store, i, s->fd_store) {
+                _cleanup_free_ char *path = NULL;
+                struct stat st;
+                int flags;
+
+                if (fstat(i->fd, &st) < 0) {
+                        log_debug_errno(errno, "Failed to stat() file descriptor entry '%s', skipping.", strna(i->fdname));
+                        continue;
+                }
+
+                flags = fcntl(i->fd, F_GETFL);
+                if (flags < 0) {
+                        log_debug_errno(errno, "Failed to issue F_GETFL on file descriptor entry '%s', skipping.", strna(i->fdname));
+                        continue;
+                }
+
+                /* glibc implies O_LARGEFILE everywhere on 64bit off_t builds, but forgets to hide it away on
+                 * F_GETFL, but provides no definition to check for that. Let's mask the flag away manually,
+                 * to not confuse clients. */
+                flags &= ~RAW_O_LARGEFILE;
+
+                (void) fd_get_path(i->fd, &path);
+
+                r = sd_bus_message_append(
+                                reply,
+                                "(suuutuusu)",
+                                i->fdname,
+                                (uint32_t) st.st_mode,
+                                (uint32_t) major(st.st_dev), (uint32_t) minor(st.st_dev),
+                                (uint64_t) st.st_ino,
+                                (uint32_t) major(st.st_rdev), (uint32_t) minor(st.st_rdev),
+                                path,
+                                (uint32_t) flags);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_send(NULL, reply, NULL);
+}
+
 const sd_bus_vtable bus_service_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -228,7 +296,7 @@ const sd_bus_vtable bus_service_vtable[] = {
         SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RestartSteps", "u", bus_property_get_unsigned, offsetof(Service, restart_steps), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RestartUSecMax", "t", bus_property_get_usec, offsetof(Service, restart_usec_max), SD_BUS_VTABLE_PROPERTY_CONST),
-        SD_BUS_PROPERTY("RestartUSecCurrent", "t", property_get_restart_usec_current, 0, 0),
+        SD_BUS_PROPERTY("RestartUSecNext", "t", property_get_restart_usec_next, 0, 0),
         SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("TimeoutAbortUSec", "t", property_get_timeout_abort_usec, 0, 0),
@@ -292,6 +360,12 @@ const sd_bus_vtable bus_service_vtable[] = {
                                  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),
@@ -649,7 +723,7 @@ int bus_service_set_property(
                 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)
index 9a054658025b9d5d3368530d68ca5e8fa882c1fd..aea6cf77f30a2bd2fe46c42493a3c79705709a81 100644 (file)
@@ -12,3 +12,4 @@ int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitW
 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);
index a9e63b0678821f6c461a73c1376b5320d9106f6f..3f083a8174550bc98ffb4a495ee2a88a13dd6301 100644 (file)
@@ -2407,14 +2407,13 @@ int bus_unit_set_properties(
         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;
@@ -2422,7 +2421,7 @@ int bus_unit_set_properties(
 
                 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;
@@ -2430,7 +2429,7 @@ int bus_unit_set_properties(
                         /* 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;
@@ -2438,11 +2437,11 @@ int bus_unit_set_properties(
 
                 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));
@@ -2456,7 +2455,7 @@ int bus_unit_set_properties(
                 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,
@@ -2464,23 +2463,32 @@ int bus_unit_set_properties(
 
                 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) {
index 3824ae747fdfe28acf19fabb8d854ab4308f16a1..19cf3faca6c6ef664961bccea69e658b042c2a94 100644 (file)
@@ -739,18 +739,28 @@ int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) {
         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;
@@ -758,20 +768,19 @@ int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, con
                 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;
 }
 
@@ -803,16 +812,22 @@ int dynamic_creds_realize(DynamicCreds *creds, char **suggested_paths, uid_t *ui
         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);
 }
index 847ef475ca555d300a8a00a70a240d3ebdf430e6..6539d17571b2112afc2ca3e587a1caefdf4b3396 100644 (file)
@@ -33,8 +33,11 @@ int dynamic_user_current(DynamicUser *d, uid_t *ret);
 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);
index bfdd2026c673c89e05d78b70e3db0cae26b0ba29..aff07e7b74427df75ba616af098b80c897d0199a 100644 (file)
@@ -2210,7 +2210,7 @@ bool exec_needs_mount_namespace(
         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 ||
@@ -2412,6 +2412,8 @@ static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) {
 }
 
 static bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type) {
+        assert(context);
+
         if (!context->dynamic_user)
                 return false;
 
@@ -3742,16 +3744,16 @@ static int apply_mount_namespace(
                  * 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) {
@@ -4057,7 +4059,6 @@ static void append_socket_pair(int *array, size_t *n, const int pair[static 2])
 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) {
@@ -4081,16 +4082,16 @@ static int close_remaining_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)
@@ -4398,13 +4399,28 @@ static int collect_open_file_fds(
         return 0;
 }
 
+static void log_command_line(Unit *unit, const char *msg, const char *executable, char **argv) {
+        assert(unit);
+        assert(msg);
+        assert(executable);
+
+        if (!DEBUG_LOGGING)
+                return;
+
+        _cleanup_free_ char *cmdline = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
+
+        log_unit_struct(unit, LOG_DEBUG,
+                        "EXECUTABLE=%s", executable,
+                        LOG_UNIT_MESSAGE(unit, "%s: %s", msg, strnull(cmdline)),
+                        LOG_UNIT_INVOCATION_ID(unit));
+}
+
 static int exec_child(
                 Unit *unit,
                 const ExecCommand *command,
                 const ExecContext *context,
                 const ExecParameters *params,
                 ExecRuntime *runtime,
-                DynamicCreds *dcreds,
                 const CGroupContext *cgroup_context,
                 int socket_fd,
                 const int named_iofds[static 3],
@@ -4537,7 +4553,7 @@ static int exec_child(
         }
 #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");
@@ -4583,7 +4599,7 @@ static int exec_child(
                 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
@@ -4599,7 +4615,7 @@ static int exec_child(
                         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)
@@ -4618,8 +4634,8 @@ static int exec_child(
                         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);
@@ -4657,8 +4673,7 @@ static int exec_child(
                 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);
 
@@ -4686,16 +4701,16 @@ static int exec_child(
                 }
         }
 
-        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);
@@ -5045,10 +5060,10 @@ static int exec_child(
                 }
         }
 
-        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,
@@ -5066,10 +5081,10 @@ static int exec_child(
                         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");
@@ -5199,9 +5214,10 @@ static int exec_child(
         }
 #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)
@@ -5223,9 +5239,9 @@ static int exec_child(
         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;
@@ -5498,19 +5514,7 @@ static int exec_child(
         } 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;
@@ -5550,7 +5554,6 @@ int exec_spawn(Unit *unit,
                const ExecContext *context,
                const ExecParameters *params,
                ExecRuntime *runtime,
-               DynamicCreds *dcreds,
                const CGroupContext *cgroup_context,
                pid_t *ret) {
 
@@ -5558,7 +5561,6 @@ int exec_spawn(Unit *unit,
         _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);
@@ -5596,21 +5598,13 @@ int exec_spawn(Unit *unit,
         if (r < 0)
                 return log_unit_error_errno(unit, r, "Failed to load environment files: %m");
 
-        line = quote_command_line(command->argv, SHELL_ESCAPE_EMPTY);
-        if (!line)
-                return log_oom();
-
         /* Fork with up-to-date SELinux label database, so the child inherits the up-to-date db
            and, until the next SELinux policy changes, we save further reloads in future children. */
         mac_selinux_maybe_reload();
 
-        log_unit_struct(unit, LOG_DEBUG,
-                        LOG_UNIT_MESSAGE(unit, "About to execute %s", line),
-                        "EXECUTABLE=%s", command->path, /* We won't know the real executable path until we create
-                                                           the mount namespace in the child, but we want to log
-                                                           from the parent, so we need to use the (possibly
-                                                           inaccurate) path here. */
-                        LOG_UNIT_INVOCATION_ID(unit));
+        /* We won't know the real executable path until we create the mount namespace in the child, but we
+           want to log from the parent, so we use the possibly inaccurate path here. */
+        log_command_line(unit, "About to execute", command->path, command->argv);
 
         if (params->cgroup_path) {
                 r = exec_parameters_get_cgroup_path(params, &subcgroup_path);
@@ -5640,7 +5634,6 @@ int exec_spawn(Unit *unit,
                                context,
                                params,
                                runtime,
-                               dcreds,
                                cgroup_context,
                                socket_fd,
                                named_iofds,
@@ -6899,7 +6892,7 @@ void exec_command_append_list(ExecCommand **l, ExecCommand *e) {
                 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, ...) {
@@ -6956,18 +6949,37 @@ static void *remove_tmpdir_thread(void *p) {
         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);
@@ -6977,7 +6989,7 @@ static ExecRuntime* exec_runtime_free(ExecRuntime *rt, bool destroy) {
                         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);
@@ -6987,21 +6999,12 @@ static ExecRuntime* exec_runtime_free(ExecRuntime *rt, bool destroy) {
                         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);
 
@@ -7009,11 +7012,11 @@ static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
         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,
@@ -7023,16 +7026,16 @@ static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
         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);
@@ -7040,11 +7043,11 @@ static int exec_runtime_add(
 
         /* 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;
 
@@ -7066,16 +7069,16 @@ static int exec_runtime_add(
 
         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;
@@ -7085,7 +7088,7 @@ static int exec_runtime_make(
         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;
@@ -7110,24 +7113,24 @@ static int exec_runtime_make(
                         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) {
@@ -7136,11 +7139,11 @@ int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool
         }
 
         /* 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;
         }
@@ -7152,27 +7155,14 @@ ref:
         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)
@@ -7227,33 +7217,33 @@ int exec_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
         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;
@@ -7292,9 +7282,9 @@ int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value,
         } 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;
@@ -7309,7 +7299,7 @@ int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value,
         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};
@@ -7421,25 +7411,66 @@ int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
         }
 
 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) {
index ff537b77cb6df58453cc8d5de40dbed71a4256a9..254a1ee2d13a5eafc14c8fde9b0d1dd323ac8455 100644 (file)
@@ -4,6 +4,8 @@
 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;
@@ -106,7 +108,7 @@ struct ExecCommand {
  * 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;
@@ -124,6 +126,11 @@ struct ExecRuntime {
         int ipcns_storage_socket[2];
 };
 
+struct ExecRuntime {
+        ExecSharedRuntime *shared;
+        DynamicCreds *dynamic_creds;
+};
+
 typedef enum ExecDirectoryType {
         EXEC_DIRECTORY_RUNTIME = 0,
         EXEC_DIRECTORY_STATE,
@@ -440,7 +447,6 @@ int exec_spawn(Unit *unit,
                const ExecContext *context,
                const ExecParameters *exec_params,
                ExecRuntime *runtime,
-               DynamicCreds *dynamic_creds,
                const CGroupContext *cgroup_context,
                pid_t *ret);
 
@@ -483,13 +489,20 @@ void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int
 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);
 
index 23ba22f00312e843c25d533237c817e2ee09dfe9..9588c625b28dfeddb6feb9489cb4c359ce3f97fc 100644 (file)
@@ -1655,7 +1655,6 @@ static void cmdline_take_random_seed(void) {
 }
 
 static void initialize_coredump(bool skip_setup) {
-#if ENABLE_COREDUMP
         if (getpid_cached() != 1)
                 return;
 
@@ -1669,7 +1668,6 @@ static void initialize_coredump(bool skip_setup) {
          * 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) {
@@ -2698,7 +2696,7 @@ static bool early_skip_setup_check(int argc, char *argv[]) {
         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 */
index 61a464c06bfe041b652702e2ca9e6beb1a7e91d6..080383ced2b40ab9cb34ec7637ea9e39c6eec099 100644 (file)
@@ -174,7 +174,7 @@ int manager_serialize(
         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;
 
@@ -519,7 +519,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                 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)
index 14fb2a0f1f890b89e52c07f7c921d28d00aeba98..47a502df7fb4592d6fe7950d09c23c679db816de 100644 (file)
@@ -1555,8 +1555,8 @@ Manager* manager_free(Manager *m) {
         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);
@@ -1861,6 +1861,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo
                 /* 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)
@@ -3459,7 +3463,7 @@ int manager_reload(Manager *m) {
         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);
@@ -4540,7 +4544,7 @@ static void manager_vacuum(Manager *m) {
         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) {
index 25fcd275d4e647240bfd20ce754970d27840e9c3..fc95b751201c509ecc2fb52a2de2960506d8d25e 100644 (file)
@@ -429,8 +429,8 @@ struct Manager {
         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;
index bbe22269469ffe77e641e16632f8cf31b6388067..586151bf67bbacdd30affef314dfedd23f7def65 100644 (file)
@@ -254,12 +254,10 @@ static void mount_done(Unit *u) {
         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);
@@ -790,10 +788,8 @@ static int mount_coldplug(Unit *u) {
                         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;
@@ -922,7 +918,6 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
                        &m->exec_context,
                        &exec_params,
                        m->exec_runtime,
-                       &m->dynamic_creds,
                        &m->cgroup_context,
                        &pid);
         if (r < 0)
@@ -948,14 +943,12 @@ static void mount_enter_dead(Mount *m, MountResult f) {
 
         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);
@@ -2268,7 +2261,6 @@ const UnitVTable mount_vtable = {
         .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"
index 1a0d9fc5e5921ece427617dd57dbaab32f1d1adb..d6d6d335a45d4879b898fb02428d0b801ebce631 100644 (file)
@@ -76,7 +76,6 @@ struct Mount {
         CGroupContext cgroup_context;
 
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         MountState state, deserialized_state;
 
index c75a2cb29f6651e5b3f7fc5ad449cb691a04b57b..a71beeb18bc123c46e83208c322ef086befc2cad 100644 (file)
@@ -18,7 +18,7 @@
 #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"
@@ -1423,7 +1423,7 @@ static int apply_one_mount(
                 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)
@@ -1435,7 +1435,8 @@ static int apply_one_mount(
                                 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)
@@ -2155,7 +2156,7 @@ int setup_namespace(
         }
 
         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;
         }
index 02d514d56a869793291195743255e30119f092f2..48229f96ee3da629f4c57a0eb8399b0cec3d6927 100644 (file)
@@ -17,6 +17,7 @@
 #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"
@@ -66,6 +67,8 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
         [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
         [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
         [SERVICE_FAILED] = UNIT_FAILED,
+        [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+        [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
         [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
         [SERVICE_CLEANING] = UNIT_MAINTENANCE,
 };
@@ -92,6 +95,8 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
         [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
         [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
         [SERVICE_FAILED] = UNIT_FAILED,
+        [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+        [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
         [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
         [SERVICE_CLEANING] = UNIT_MAINTENANCE,
 };
@@ -264,36 +269,38 @@ static void service_start_watchdog(Service *s) {
                 log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m");
 }
 
-usec_t service_restart_usec(Service *s) {
-        unsigned n_restarts;
-        long double unit;
+usec_t service_restart_usec_next(Service *s) {
+        unsigned n_restarts_next;
+        usec_t value;
 
         assert(s);
 
-        /* s->n_restarts is not yet updated when we're in these states, so let's add 1 to it manually.
-         * Note that for SERVICE_AUTO_RESTART a restart job might have been enqueued,
-         * i.e. s->n_restarts is already increased. But we assume it's not since the time
-         * between job enqueuing and running is usually neglectable compared to the time
-         * we'll be sleeping. */
-        n_restarts = s->n_restarts +
-                     (IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART) ? 1 : 0);
+        /* When the service state is in SERVICE_*_BEFORE_AUTO_RESTART or SERVICE_AUTO_RESTART,
+         * we still need to add 1 to s->n_restarts manually because s->n_restarts is not updated
+         * until a restart job is enqueued. Note that for SERVICE_AUTO_RESTART, that might have been
+         * the case, i.e. s->n_restarts is already increased. But we assume it's not since the time
+         * between job enqueuing and running is usually neglectable compared to the time we'll be sleeping. */
+        n_restarts_next = s->n_restarts + 1;
 
-        /* n_restarts can equal to 0 if no restart has happened nor planned */
-        if (n_restarts <= 1 ||
+        if (n_restarts_next <= 1 ||
             s->restart_steps == 0 ||
             s->restart_usec_max == USEC_INFINITY ||
-            s->restart_usec == s->restart_usec_max)
-                return s->restart_usec;
-
-        if (n_restarts > s->restart_steps)
-                return s->restart_usec_max;
-
-        /* Enforced in service_verify() and above */
-        assert(s->restart_usec_max > s->restart_usec);
+            s->restart_usec >= s->restart_usec_max)
+                value = s->restart_usec;
+        else if (n_restarts_next > s->restart_steps)
+                value = s->restart_usec_max;
+        else {
+                /* Enforced in service_verify() and above */
+                assert(s->restart_usec_max > s->restart_usec);
 
-        unit = powl(s->restart_usec_max - s->restart_usec, 1.0L / s->restart_steps);
+                /* ((restart_usec_max - restart_usec)^(1/restart_steps))^(n_restart_next - 1) */
+                value = usec_add(s->restart_usec,
+                                 (usec_t) powl(s->restart_usec_max - s->restart_usec,
+                                               (long double) (n_restarts_next - 1) / s->restart_steps));
+        }
 
-        return usec_add(s->restart_usec, (usec_t) powl(unit, n_restarts - 1));
+        log_unit_debug(UNIT(s), "Next restart interval calculated as: %s", FORMAT_TIMESPAN(value, 0));
+        return value;
 }
 
 static void service_extend_event_source_timeout(Service *s, sd_event_source *source, usec_t extended) {
@@ -380,9 +387,6 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
 static void service_release_fd_store(Service *s) {
         assert(s);
 
-        if (s->n_keep_fd_store > 0)
-                return;
-
         log_unit_debug(UNIT(s), "Releasing all stored fds");
         while (s->fd_store)
                 service_fd_store_unlink(s->fd_store);
@@ -395,6 +399,10 @@ static void service_release_resources(Unit *u) {
 
         assert(s);
 
+        /* Don't release resources if this is a transitionary failed/dead state */
+        if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
+                return;
+
         if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
                 return;
 
@@ -417,13 +425,11 @@ static void service_done(Unit *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);
@@ -446,8 +452,6 @@ static void service_done(Unit *u) {
 
         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);
@@ -871,8 +875,43 @@ static int service_load(Unit *u) {
         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;
 
@@ -974,8 +1013,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
         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;
 
@@ -996,6 +1034,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
                         prefix, s->n_fd_store_max,
                         prefix, s->n_fd_store);
 
+        service_dump_fdstore(s, f, prefix);
+
         if (s->open_files)
                 LIST_FOREACH(open_files, of, s->open_files) {
                         _cleanup_free_ char *ofs = NULL;
@@ -1201,7 +1241,9 @@ static void service_set_state(Service *s, ServiceState state) {
                 s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
         }
 
-        if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
+        if (IN_SET(state,
+                   SERVICE_DEAD, SERVICE_FAILED,
+                   SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART)) {
                 unit_unwatch_all_pids(UNIT(s));
                 unit_dequeue_rewatch_pids(UNIT(s));
         }
@@ -1264,7 +1306,7 @@ static usec_t service_coldplug_timeout(Service *s) {
                 return usec_add(UNIT(s)->state_change_timestamp.monotonic, service_timeout_abort_usec(s));
 
         case SERVICE_AUTO_RESTART:
-                return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, service_restart_usec(s));
+                return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, service_restart_usec_next(s));
 
         case SERVICE_CLEANING:
                 return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->exec_context.timeout_clean_usec);
@@ -1314,9 +1356,11 @@ static int service_coldplug(Unit *u) {
                         return r;
         }
 
-        if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) {
+        if (!IN_SET(s->deserialized_state,
+                    SERVICE_DEAD, SERVICE_FAILED,
+                    SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
+                    SERVICE_CLEANING)) {
                 (void) unit_enqueue_rewatch_pids(u);
-                (void) unit_setup_dynamic_creds(u);
                 (void) unit_setup_exec_runtime(u);
         }
 
@@ -1363,10 +1407,9 @@ static int service_collect_fds(
 
                 /* 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)
@@ -1768,7 +1811,6 @@ static int service_spawn_internal(
                        &s->exec_context,
                        &exec_params,
                        s->exec_runtime,
-                       &s->dynamic_creds,
                        &s->cgroup_context,
                        &pid);
         if (r < 0)
@@ -1895,14 +1937,14 @@ static bool service_will_restart(Unit *u) {
 
         if (s->will_auto_restart)
                 return true;
-        if (s->state == SERVICE_AUTO_RESTART)
+        if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
                 return true;
 
         return unit_will_restart_default(u);
 }
 
 static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
-        ServiceState end_state;
+        ServiceState end_state, restart_state;
         int r;
 
         assert(s);
@@ -1918,12 +1960,15 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
         if (s->result == SERVICE_SUCCESS) {
                 unit_log_success(UNIT(s));
                 end_state = SERVICE_DEAD;
+                restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
         } else if (s->result == SERVICE_SKIP_CONDITION) {
                 unit_log_skip(UNIT(s), service_result_to_string(s->result));
                 end_state = SERVICE_DEAD;
+                restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
         } else {
                 unit_log_failure(UNIT(s), service_result_to_string(s->result));
                 end_state = SERVICE_FAILED;
+                restart_state = SERVICE_FAILED_BEFORE_AUTO_RESTART;
         }
         unit_warn_leftover_processes(UNIT(s), unit_log_leftover_process_stop);
 
@@ -1941,30 +1986,33 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
                         s->will_auto_restart = true;
         }
 
-        /* Make sure service_release_resources() doesn't destroy our FD store, while we are changing through
-         * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
-        s->n_keep_fd_store ++;
-
-        service_set_state(s, end_state);
-
         if (s->will_auto_restart) {
                 s->will_auto_restart = false;
 
-                r = service_arm_timer(s, /* relative= */ true, service_restart_usec(s));
-                if (r < 0) {
-                        s->n_keep_fd_store--;
+                /* We make two state changes here: one that maps to the high-level UNIT_INACTIVE/UNIT_FAILED
+                 * state (i.e. a state indicating deactivation), and then one that that maps to the
+                 * high-level UNIT_STARTING state (i.e. a state indicating activation). We do this so that
+                 * external software can watch the state changes and see all service failures, even if they
+                 * are only transitionary and followed by an automatic restart. We have fine-grained
+                 * low-level states for this though so that software can distinguish the permanent UNIT_INACTIVE
+                 * state from this transitionary UNIT_INACTIVE state by looking at the low-level states. */
+                service_set_state(s, restart_state);
+
+                r = service_arm_timer(s, /* relative= */ true, service_restart_usec_next(s));
+                if (r < 0)
                         goto fail;
-                }
 
                 service_set_state(s, SERVICE_AUTO_RESTART);
-        } else
+        } else {
+                service_set_state(s, end_state);
+
                 /* If we shan't restart, then flush out the restart counter. But don't do that immediately, so that the
                  * user can still introspect the counter. Do so on the next start. */
                 s->flush_n_restarts = true;
+        }
 
         /* The new state is in effect, let's decrease the fd store ref counter again. Let's also re-add us to the GC
          * queue, so that the fd store is possibly gc'ed again */
-        s->n_keep_fd_store--;
         unit_add_to_gc_queue(UNIT(s));
 
         /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
@@ -1974,7 +2022,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
         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);
@@ -1982,9 +2030,6 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
         /* 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. */
@@ -2653,14 +2698,11 @@ static int service_start(Unit *u) {
         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));
@@ -2708,34 +2750,53 @@ static int service_stop(Unit *u) {
         /* Don't create restart jobs from manual stops. */
         s->forbid_restart = true;
 
-        /* Already on it */
-        if (IN_SET(s->state,
-                   SERVICE_STOP, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
-                   SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
+        switch (s->state) {
+
+        case SERVICE_STOP:
+        case SERVICE_STOP_SIGTERM:
+        case SERVICE_STOP_SIGKILL:
+        case SERVICE_STOP_POST:
+        case SERVICE_FINAL_WATCHDOG:
+        case SERVICE_FINAL_SIGTERM:
+        case SERVICE_FINAL_SIGKILL:
+                /* Already on it */
                 return 0;
 
-        /* A restart will be scheduled or is in progress. */
-        if (s->state == SERVICE_AUTO_RESTART) {
+        case SERVICE_AUTO_RESTART:
+                /* A restart will be scheduled or is in progress. */
                 service_set_state(s, SERVICE_DEAD);
                 return 0;
-        }
 
-        /* If there's already something running we go directly into kill mode. */
-        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP_WATCHDOG)) {
+        case SERVICE_CONDITION:
+        case SERVICE_START_PRE:
+        case SERVICE_START:
+        case SERVICE_START_POST:
+        case SERVICE_RELOAD:
+        case SERVICE_RELOAD_SIGNAL:
+        case SERVICE_RELOAD_NOTIFY:
+        case SERVICE_STOP_WATCHDOG:
+                /* If there's already something running we go directly into kill mode. */
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
                 return 0;
-        }
 
-        /* If we are currently cleaning, then abort it, brutally. */
-        if (s->state == SERVICE_CLEANING) {
+        case SERVICE_CLEANING:
+                /* If we are currently cleaning, then abort it, brutally. */
                 service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS);
                 return 0;
-        }
 
-        assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
+        case SERVICE_RUNNING:
+        case SERVICE_EXITED:
+                service_enter_stop(s, SERVICE_SUCCESS);
+                return 1;
 
-        service_enter_stop(s, SERVICE_SUCCESS);
-        return 1;
+        case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+        case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+        case SERVICE_DEAD:
+        case SERVICE_FAILED:
+        default:
+                /* Unknown state, or unit_stop() should already have handled these */
+                assert_not_reached();
+        }
 }
 
 static int service_reload(Unit *u) {
@@ -3338,6 +3399,11 @@ static bool service_may_gc(Unit *u) {
             control_pid_good(s) > 0)
                 return false;
 
+        /* Only allow collection of actually dead services, i.e. not those that are in the transitionary
+         * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */
+        if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED))
+                return false;
+
         return true;
 }
 
@@ -3378,30 +3444,30 @@ fail:
 }
 
 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);
 }
@@ -3499,11 +3565,9 @@ static void service_notify_cgroup_empty_event(Unit *u) {
 
         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) &&
@@ -3561,6 +3625,9 @@ static void service_notify_cgroup_empty_event(Unit *u) {
          * up the cgroup earlier and should do it now. */
         case SERVICE_DEAD:
         case SERVICE_FAILED:
+        case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+        case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+        case SERVICE_AUTO_RESTART:
                 unit_prune_cgroup(u);
                 break;
 
@@ -4166,7 +4233,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
                 if (s->restart_usec > 0)
                         log_unit_debug(UNIT(s),
                                        "Service restart interval %s expired, scheduling restart.",
-                                       FORMAT_TIMESPAN(service_restart_usec(s), USEC_PER_SEC));
+                                       FORMAT_TIMESPAN(service_restart_usec_next(s), USEC_PER_SEC));
                 else
                         log_unit_debug(UNIT(s),
                                        "Service has no hold-off time (RestartSec=0), scheduling restart.");
@@ -4943,7 +5010,6 @@ const UnitVTable service_vtable = {
         .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"
index 9d5b15d8c2e71a2b663408a5b0f86c37e6e360fd..03edb18e31e5c99d340c213045f7702fabf50e2a 100644 (file)
@@ -158,7 +158,6 @@ struct Service {
 
         /* Runtime data of the execution context */
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         pid_t main_pid, control_pid;
 
@@ -207,7 +206,6 @@ struct Service {
         ServiceFDStore *fd_store;
         size_t n_fd_store;
         unsigned n_fd_store_max;
-        unsigned n_keep_fd_store;
 
         char *usb_function_descriptors;
         char *usb_function_strings;
@@ -247,7 +245,7 @@ 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);
 
-usec_t service_restart_usec(Service *s);
+usec_t service_restart_usec_next(Service *s);
 
 const char* service_restart_to_string(ServiceRestart i) _const_;
 ServiceRestart service_restart_from_string(const char *s) _pure_;
index 6b153a92faa7e005ea1c4b30e42d40aaf51037fd..c26daced1ac0bd902f14c8d892a34ec7a833dad0 100644 (file)
@@ -150,12 +150,10 @@ static void socket_done(Unit *u) {
 
         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);
@@ -1532,16 +1530,18 @@ static int socket_address_listen_in_cgroup(
 
         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);
         }
@@ -1559,10 +1559,11 @@ static int socket_address_listen_in_cgroup(
 
                 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);
@@ -1905,10 +1906,8 @@ static int socket_coldplug(Unit *u) {
                         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;
@@ -1947,7 +1946,6 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
                        &s->exec_context,
                        &exec_params,
                        s->exec_runtime,
-                       &s->dynamic_creds,
                        &s->cgroup_context,
                        &pid);
         if (r < 0)
@@ -2049,13 +2047,11 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
 
         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);
@@ -2488,7 +2484,7 @@ static int socket_start(Unit *u) {
 
                 /* 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);
         }
 
@@ -3291,7 +3287,7 @@ static void socket_trigger_notify(Unit *u, Unit *other) {
                 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);
@@ -3468,7 +3464,6 @@ const UnitVTable socket_vtable = {
         .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"
index 6813bdcf8c8e1faed413937db02d638e5b01ae5d..308a0727568991425a58791da77639287783f9c8 100644 (file)
@@ -91,7 +91,6 @@ struct Socket {
         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
index d0b557cf400c863ec105b051fbf7ac097df6760b..c6e2c8b1bd94536cd2d557bd78607acc491abf7b 100644 (file)
@@ -170,12 +170,10 @@ static void swap_done(Unit *u) {
         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);
@@ -593,10 +591,8 @@ static int swap_coldplug(Unit *u) {
                         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;
@@ -689,7 +685,6 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
                        &s->exec_context,
                        &exec_params,
                        s->exec_runtime,
-                       &s->dynamic_creds,
                        &s->cgroup_context,
                        &pid);
         if (r < 0)
@@ -719,13 +714,11 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
         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) {
@@ -1619,7 +1612,6 @@ const UnitVTable swap_vtable = {
         .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"
index c0e3f118e18da15333adc7917c388bddf0d02728..d61c7112cf6b4fc51412cc467edded4d81eac5a6 100644 (file)
@@ -68,7 +68,6 @@ struct Swap {
         CGroupContext cgroup_context;
 
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         SwapState state, deserialized_state;
 
index 21457dc6a5f6b3678e25f9b89981be3007be9e3d..8055d9e5335254bc187f7cad961c84ba67996b21 100644 (file)
@@ -522,7 +522,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
                         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;
index 0418593d251523ee317492d68f92ffa9df97467f..846d15b4151d064ad596b0b41ccc398e69f8e951 100644 (file)
@@ -1546,7 +1546,8 @@ static int unit_add_mount_dependencies(Unit *u) {
 
 static int unit_add_oomd_dependencies(Unit *u) {
         CGroupContext *c;
-        bool wants_oomd;
+        CGroupMask mask;
+        int r;
 
         assert(u);
 
@@ -1557,10 +1558,20 @@ static int unit_add_oomd_dependencies(Unit *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);
 }
 
@@ -3454,7 +3465,7 @@ static int get_name_owner_handler(sd_bus_message *message, void *userdata, sd_bu
 
         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",
@@ -4299,20 +4310,18 @@ static const char* unit_drop_in_dir(Unit *u, UnitWriteFlags flags) {
         return NULL;
 }
 
-char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
+const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
+        assert(s);
         assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
+        assert(buf);
 
         _cleanup_free_ char *t = NULL;
 
-        if (!s)
-                return NULL;
-
-        /* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the
-         * allocated return buffer pointer is also written to *buf, except if no escaping was necessary, in
-         * which case *buf is set to NULL, and the input pointer is returned as-is. This means the return
-         * value always contains a properly escaped version, but *buf when passed only contains a pointer if
-         * an allocation was necessary. If *buf is not specified, then the return value always needs to be
-         * freed. Callers can use this to optimize memory allocations. */
+        /* Returns a string with any escaping done. If no escaping was necessary, *buf is set to NULL, and
+         * the input pointer is returned as-is. If an allocation was needed, the return buffer pointer is
+         * written to *buf. This means the return value always contains a properly escaped version, but *buf
+         * only contains a pointer if an allocation was made. Callers can use this to optimize memory
+         * allocations. */
 
         if (flags & UNIT_ESCAPE_SPECIFIERS) {
                 t = specifier_escape(s);
@@ -4322,8 +4331,8 @@ char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
                 s = t;
         }
 
-        /* We either do c-escaping or shell-escaping, to additionally escape characters that we parse for
-         * ExecStart= and friend, i.e. '$' and ';' and quotes. */
+        /* We either do C-escaping or shell-escaping, to additionally escape characters that we parse for
+         * ExecStart= and friends, i.e. '$' and ';' and quotes. */
 
         if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
                 char *t2 = shell_escape(s, "$;'\"");
@@ -4342,12 +4351,8 @@ char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
                 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) {
@@ -4791,7 +4796,10 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask)
 }
 
 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;
@@ -4804,34 +4812,38 @@ int unit_setup_exec_runtime(Unit *u) {
         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) {
@@ -5565,10 +5577,6 @@ int unit_prepare_exec(Unit *u) {
         if (r < 0)
                 return r;
 
-        r = unit_setup_dynamic_creds(u);
-        if (r < 0)
-                return r;
-
         return 0;
 }
 
index 8f53773a119816d5a5baa57e43480ce1e22a576d..420405b2b7707f912e5fa094eea101e33d6881be 100644 (file)
@@ -570,14 +570,10 @@ typedef struct UnitVTable {
         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;
 
@@ -967,9 +963,8 @@ CGroupContext *unit_get_cgroup_context(Unit *u) _pure_;
 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);
index 3eb94cd36a291bd6b22aa2a0268b5dbbe3ec061d..ef32efe1e2e8d97212c4808d3b4e55b4e3011978 100644 (file)
@@ -807,13 +807,11 @@ static int parse_argv(int argc, char *argv[]) {
                         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;
 
index fc6cc74dadbd79dfaf8dabf9143fd19d06801c3b..4dc3c1794d89a5d2d01a45032fe5a734089c030c 100644 (file)
@@ -142,7 +142,8 @@ int enroll_tpm2(struct crypt_device *cd,
         _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;
@@ -214,7 +215,9 @@ int enroll_tpm2(struct crypt_device *cd,
                       &blob, &blob_size,
                       &hash, &hash_size,
                       &pcr_bank,
-                      &primary_alg);
+                      &primary_alg,
+                      &srk_buf,
+                      &srk_buf_size);
         if (r < 0)
                 return r;
 
@@ -245,6 +248,7 @@ int enroll_tpm2(struct crypt_device *cd,
                                 primary_alg,
                                 blob, blob_size,
                                 hash, hash_size,
+                                srk_buf, srk_buf_size,
                                 &secret2, &secret2_size);
                 if (r < 0)
                         return r;
@@ -283,6 +287,7 @@ int enroll_tpm2(struct crypt_device *cd,
                         hash, hash_size,
                         use_pin ? binary_salt : NULL,
                         use_pin ? sizeof(binary_salt) : 0,
+                        srk_buf, srk_buf_size,
                         flags,
                         &v);
         if (r < 0)
index 29360ef0fc96387351265eceb836c798ce625713..be57873ee447045cd30ced6ee2686122524c45fb 100644 (file)
@@ -207,38 +207,29 @@ static int parse_argv(int argc, char *argv[]) {
                 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)
index b5d66e389da6af8d05672f186acb26b199e1af74..aab3a4b4c067f9f56ed6147f3d57c9d89dec0704 100644 (file)
@@ -42,8 +42,8 @@ _public_ int cryptsetup_token_open_pin(
                 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;
@@ -92,6 +92,8 @@ _public_ int cryptsetup_token_open_pin(
                         &policy_hash_size,
                         &salt,
                         &salt_size,
+                        &srk_buf,
+                        &srk_buf_size,
                         &flags);
         if (r < 0)
                 return log_debug_open_error(cd, r);
@@ -114,6 +116,8 @@ _public_ int cryptsetup_token_open_pin(
                         policy_hash_size,
                         salt,
                         salt_size,
+                        srk_buf,
+                        srk_buf_size,
                         flags,
                         &decrypted_key,
                         &decrypted_key_size);
@@ -172,9 +176,9 @@ _public_ void cryptsetup_token_dump(
                 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;
@@ -201,6 +205,8 @@ _public_ void cryptsetup_token_dump(
                         &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");
@@ -234,6 +240,7 @@ _public_ void cryptsetup_token_dump(
         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));
 }
 
 /*
index 307488726935ac28b6f5c9ae64c079675e6cb330..e2fa49b94f46feb7d8933aed12225b18e10db5da 100644 (file)
@@ -29,6 +29,8 @@ int acquire_luks2_key(
                 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) {
@@ -89,5 +91,6 @@ int acquire_luks2_key(
                         primary_alg,
                         key_data, key_data_size,
                         policy_hash, policy_hash_size,
+                        srk_buf, srk_buf_size,
                         ret_decrypted_key, ret_decrypted_key_size);
 }
index 36d514caa0d5d5ff6c9c49452bc14e4d0bcc39df..1143f5fd9f53d496f588bf182a090bc133f68fb0 100644 (file)
@@ -22,6 +22,8 @@ int acquire_luks2_key(
                 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);
index a375a227586d87383823e430f9ae1c0d94a9c2f0..5e277b0dd624409c2e5cc355cbc91b8486dc2f7b 100644 (file)
@@ -72,6 +72,8 @@ int acquire_tpm2_key(
                 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,
@@ -141,6 +143,8 @@ int acquire_tpm2_key(
                                 blob_size,
                                 policy_hash,
                                 policy_hash_size,
+                                srk_buf,
+                                srk_buf_size,
                                 ret_decrypted_key,
                                 ret_decrypted_key_size);
 
@@ -181,6 +185,8 @@ int acquire_tpm2_key(
                                 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
@@ -210,6 +216,8 @@ int find_tpm2_auto_data(
                 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) {
@@ -219,9 +227,9 @@ int find_tpm2_auto_data(
         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;
@@ -244,6 +252,7 @@ int find_tpm2_auto_data(
                                 &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;
@@ -270,6 +279,8 @@ int find_tpm2_auto_data(
                         *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;
                 }
index f6549b7d1d97aa87c2c14e4a7c459ab9ac260862..a510ac625701cd278f855aa6e8d1d2483689ee6a 100644 (file)
@@ -30,6 +30,8 @@ int acquire_tpm2_key(
                 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,
@@ -53,6 +55,8 @@ int find_tpm2_auto_data(
                 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);
@@ -78,6 +82,8 @@ static inline int acquire_tpm2_key(
                 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,
@@ -105,6 +111,8 @@ static inline int find_tpm2_auto_data(
                 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) {
index fa160c1f8cf7e16891af9f34dc45380a7bcbf1de..f9283ce6f48bea9a05b8f9dac4383e8332a2b24c 100644 (file)
@@ -1659,6 +1659,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                                         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,
@@ -1704,8 +1705,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                          * 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;
@@ -1722,6 +1723,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                                                 &blob, &blob_size,
                                                 &policy_hash, &policy_hash_size,
                                                 &salt, &salt_size,
+                                                &srk_buf, &srk_buf_size,
                                                 &tpm2_flags,
                                                 &keyslot,
                                                 &token);
@@ -1752,6 +1754,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                                                 blob, blob_size,
                                                 policy_hash, policy_hash_size,
                                                 salt, salt_size,
+                                                srk_buf, srk_buf_size,
                                                 tpm2_flags,
                                                 until,
                                                 arg_headless,
index 3973de893187624af1f5d18597236ceb6a391e54..3e68ed1cb029c3e2889ba422fc1e5f4bc6cf53b9 100644 (file)
@@ -23,6 +23,7 @@
 #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"
@@ -71,6 +72,7 @@ static bool arg_force = false;
 static bool arg_delete_root_password = false;
 static bool arg_root_password_is_hashed = false;
 static bool arg_welcome = true;
+static bool arg_reset = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -228,6 +230,20 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i
         }
 }
 
+static int should_configure(int dir_fd, const char *filename) {
+        assert(dir_fd >= 0);
+        assert(filename);
+
+        if (faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+                if (errno != ENOENT)
+                        return log_error_errno(errno, "Failed to access %s: %m", filename);
+
+                return true; /* missing */
+        }
+
+        return arg_force; /* exists, but if --force was given we should still configure the file. */
+}
+
 static bool locale_is_ok(const char *name) {
 
         if (arg_root)
@@ -309,28 +325,38 @@ static int prompt_locale(void) {
         return 0;
 }
 
-static int process_locale(void) {
-        const char *etc_localeconf;
+static int process_locale(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         char* locales[3];
         unsigned i = 0;
         int r;
 
-        etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
-        if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming locale information has been configured.",
-                          etc_localeconf);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/locale.conf",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/locale.conf, assuming locale information has been configured.");
+        if (r <= 0)
+                return r;
 
-        if (arg_copy_locale && arg_root) {
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
 
-                (void) mkdir_parents(etc_localeconf, 0755);
-                r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, COPY_REFLINK);
+        if (arg_copy_locale && r == 0) {
+                r = copy_file_atomic_at(AT_FDCWD, "/etc/locale.conf", pfd, f, 0644, COPY_REFLINK);
                 if (r != -ENOENT) {
                         if (r < 0)
-                                return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
+                                return log_error_errno(r, "Failed to copy host's /etc/locale.conf: %m");
 
-                        log_info("%s copied.", etc_localeconf);
+                        log_info("Copied host's /etc/locale.conf.");
                         return 0;
                 }
         }
@@ -349,12 +375,11 @@ static int process_locale(void) {
 
         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;
 }
 
@@ -390,27 +415,37 @@ static int prompt_keymap(void) {
                            kmaps, 60, keymap_is_valid, &arg_keymap);
 }
 
-static int process_keymap(void) {
-        const char *etc_vconsoleconf;
+static int process_keymap(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         char **keymap;
         int r;
 
-        etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
-        if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming console has been configured.",
-                          etc_vconsoleconf);
-                return 0;
-        }
+        assert(rfd >= 0);
 
-        if (arg_copy_keymap && arg_root) {
+        pfd = chase_and_open_parent_at(rfd, "/etc/vconsole.conf",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m");
 
-                (void) mkdir_parents(etc_vconsoleconf, 0755);
-                r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, COPY_REFLINK);
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/vconsole.conf, assuming console has been configured.");
+        if (r <= 0)
+                return r;
+
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+        if (arg_copy_keymap && r == 0) {
+                r = copy_file_atomic_at(AT_FDCWD, "/etc/vconsole.conf", pfd, f, 0644, COPY_REFLINK);
                 if (r != -ENOENT) {
                         if (r < 0)
-                                return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
+                                return log_error_errno(r, "Failed to copy host's /etc/vconsole.conf: %m");
 
-                        log_info("%s copied.", etc_vconsoleconf);
+                        log_info("Copied host's /etc/vconsole.conf.");
                         return 0;
                 }
         }
@@ -426,15 +461,11 @@ static int process_keymap(void) {
 
         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;
 }
 
@@ -476,31 +507,43 @@ static int prompt_timezone(void) {
         return 0;
 }
 
-static int process_timezone(void) {
-        const char *etc_localtime, *e;
+static int process_timezone(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
+        const char *e;
         int r;
 
-        etc_localtime = prefix_roota(arg_root, "/etc/localtime");
-        if (laccess(etc_localtime, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming timezone has been configured.",
-                          etc_localtime);
-                return 0;
-        }
+        assert(rfd >= 0);
 
-        if (arg_copy_timezone && arg_root) {
-                _cleanup_free_ char *p = NULL;
+        pfd = chase_and_open_parent_at(rfd, "/etc/localtime",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/localtime: %m");
 
-                r = readlink_malloc("/etc/localtime", &p);
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/localtime, assuming timezone has been configured.");
+        if (r <= 0)
+                return r;
+
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+        if (arg_copy_timezone && r == 0) {
+                _cleanup_free_ char *s = NULL;
+
+                r = readlink_malloc("/etc/localtime", &s);
                 if (r != -ENOENT) {
                         if (r < 0)
-                                return log_error_errno(r, "Failed to read host timezone: %m");
+                                return log_error_errno(r, "Failed to read host's /etc/localtime: %m");
 
-                        (void) mkdir_parents(etc_localtime, 0755);
-                        r = symlink_atomic(p, etc_localtime);
+                        r = symlinkat_atomic_full(s, pfd, f, /* make_relative= */ false);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to create %s symlink: %m", etc_localtime);
+                                return log_error_errno(r, "Failed to create /etc/localtime symlink: %m");
 
-                        log_info("%s copied.", etc_localtime);
+                        log_info("Copied host's /etc/localtime.");
                         return 0;
                 }
         }
@@ -514,12 +557,11 @@ static int process_timezone(void) {
 
         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;
 }
 
@@ -563,16 +605,24 @@ static int prompt_hostname(void) {
         return 0;
 }
 
-static int process_hostname(void) {
-        const char *etc_hostname;
+static int process_hostname(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         int r;
 
-        etc_hostname = prefix_roota(arg_root, "/etc/hostname");
-        if (laccess(etc_hostname, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming hostname has been configured.",
-                          etc_hostname);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/hostname",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/hostname: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/hostname, assuming hostname has been configured.");
+        if (r <= 0)
+                return r;
 
         r = prompt_hostname();
         if (r < 0)
@@ -581,39 +631,45 @@ static int process_hostname(void) {
         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;
 }
 
@@ -681,7 +737,7 @@ static int prompt_root_password(void) {
         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);
@@ -689,17 +745,14 @@ static int find_shell(const char *path, const char *root) {
         if (!valid_shell(path))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path);
 
-        r = chase(path, root, CHASE_PREFIX_ROOT, NULL, NULL);
-        if (r < 0) {
-                const char *p;
-                p = prefix_roota(root, path);
-                return log_error_errno(r, "Failed to resolve shell %s: %m", p);
-        }
+        r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to resolve shell %s: %m", path);
 
         return 0;
 }
 
-static int prompt_root_shell(void) {
+static int prompt_root_shell(int rfd) {
         int r;
 
         if (arg_root_shell)
@@ -733,7 +786,7 @@ static int prompt_root_shell(void) {
                         break;
                 }
 
-                r = find_shell(s, arg_root);
+                r = find_shell(rfd, s);
                 if (r < 0)
                         continue;
 
@@ -744,18 +797,21 @@ static int prompt_root_shell(void) {
         return 0;
 }
 
-static int write_root_passwd(const char *passwd_path, const char *password, const char *shell) {
+static int write_root_passwd(int etc_fd, const char *password, const char *shell) {
         _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
         _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
         int r;
 
         assert(password);
 
-        r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
+        r = fopen_temporary_at_label(etc_fd, "passwd", "passwd", &passwd, &passwd_tmp);
         if (r < 0)
                 return r;
 
-        original = fopen(passwd_path, "re");
+        r = xfopenat(etc_fd, "passwd", "re", O_NOFOLLOW, &original);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
         if (original) {
                 struct passwd *i;
 
@@ -805,25 +861,28 @@ static int write_root_passwd(const char *passwd_path, const char *password, cons
         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;
 
@@ -874,26 +933,46 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor
         if (r < 0)
                 return r;
 
-        r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
+        r = renameat_and_apply_smack_floor_label(etc_fd, shadow_tmp, etc_fd, "shadow");
         if (r < 0)
                 return r;
 
         return 0;
 }
 
-static int process_root_account(void) {
-        _cleanup_close_ int lock = -EBADF;
+static int process_root_account(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_(release_lock_file) LockFile lock = LOCK_FILE_INIT;
         _cleanup_(erase_and_freep) char *_hashed_password = NULL;
         const char *password, *hashed_password;
-        const char *etc_passwd, *etc_shadow;
-        int r;
+        int k = 0, r;
 
-        etc_passwd = prefix_roota(arg_root, "/etc/passwd");
-        etc_shadow = prefix_roota(arg_root, "/etc/shadow");
+        assert(rfd >= 0);
 
-        if (laccess(etc_passwd, F_OK) >= 0 && laccess(etc_shadow, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s and %s, assuming root account has been initialized.",
-                          etc_passwd, etc_shadow);
+        pfd = chase_and_open_parent_at(rfd, "/etc/passwd",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       NULL);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/passwd: %m");
+
+        /* Ensure that passwd and shadow are in the same directory and are not symlinks. */
+
+        FOREACH_STRING(s, "passwd", "shadow") {
+                r = verify_regular_at(pfd, s, /* follow = */ false);
+                if (IN_SET(r, -EISDIR, -ELOOP, -EBADFD))
+                        return log_error_errno(r, "/etc/%s is not a regular file", s);
+                if (r < 0 && r != -ENOENT)
+                        return log_error_errno(r, "Failed to check whether /etc/%s is a regular file: %m", s);
+
+                r = should_configure(pfd, s);
+                if (r < 0)
+                        return r;
+
+                k += r;
+        }
+
+        if (k == 0) {
+                log_debug("Found /etc/passwd and /etc/shadow, assuming root account has been initialized.");
                 return 0;
         }
 
@@ -904,11 +983,15 @@ static int process_root_account(void) {
                 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;
@@ -921,11 +1004,11 @@ static int process_root_account(void) {
                         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;
@@ -960,43 +1043,92 @@ static int process_root_account(void) {
         else
                 password = hashed_password = PASSWORD_LOCKED_AND_INVALID;
 
-        r = write_root_passwd(etc_passwd, password, arg_root_shell);
+        r = write_root_passwd(pfd, password, arg_root_shell);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_passwd);
+                return log_error_errno(r, "Failed to write /etc/passwd: %m");
 
-        log_info("%s written", etc_passwd);
+        log_info("/etc/passwd written.");
 
-        r = write_root_shadow(etc_shadow, hashed_password);
+        r = write_root_shadow(pfd, hashed_password);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
+                return log_error_errno(r, "Failed to write /etc/shadow: %m");
 
-        log_info("%s written.", etc_shadow);
+        log_info("/etc/shadow written.");
         return 0;
 }
 
-static int process_kernel_cmdline(void) {
-        const char *etc_kernel_cmdline;
+static int process_kernel_cmdline(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         int r;
 
-        etc_kernel_cmdline = prefix_roota(arg_root, "/etc/kernel/cmdline");
-        if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming kernel has been configured.",
-                          etc_kernel_cmdline);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/kernel/cmdline, assuming kernel command line has been configured.");
+        if (r <= 0)
+                return r;
 
         if (!arg_kernel_cmdline) {
                 log_debug("Creation of /etc/kernel/cmdline was not requested, skipping.");
                 return 0;
         }
 
-        r = write_string_file(etc_kernel_cmdline, arg_kernel_cmdline,
-                              WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
-                              (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+        r = write_string_file_at(pfd, "cmdline", arg_kernel_cmdline,
+                                 WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_kernel_cmdline);
+                return log_error_errno(r, "Failed to write /etc/kernel/cmdline: %m");
+
+        log_info("/etc/kernel/cmdline written.");
+        return 0;
+}
+
+static int reset_one(int rfd, const char *path) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
+
+        assert(rfd >= 0);
+        assert(path);
+
+        pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f);
+        if (pfd == -ENOENT)
+                return 0;
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to resolve %s: %m", path);
+
+        if (unlinkat(pfd, f, 0) < 0)
+                return errno == ENOENT ? 0 : log_error_errno(errno, "Failed to remove %s: %m", path);
+
+        log_info("Removed %s", path);
+        return 0;
+}
+
+static int process_reset(int rfd) {
+        int r;
+
+        assert(rfd >= 0);
+
+        if (!arg_reset)
+                return 0;
+
+        FOREACH_STRING(p,
+                       "/etc/locale.conf",
+                       "/etc/vconsole.conf",
+                       "/etc/hostname",
+                       "/etc/machine-id",
+                       "/etc/kernel/cmdline") {
+                r = reset_one(rfd, p);
+                if (r < 0)
+                        return r;
+        }
 
-        log_info("%s written.", etc_kernel_cmdline);
         return 0;
 }
 
@@ -1041,6 +1173,7 @@ static int help(void) {
                "     --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);
@@ -1082,6 +1215,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_FORCE,
                 ARG_DELETE_ROOT_PASSWORD,
                 ARG_WELCOME,
+                ARG_RESET,
         };
 
         static const struct option options[] = {
@@ -1117,6 +1251,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "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                   },
                 {}
         };
 
@@ -1210,10 +1345,6 @@ static int parse_argv(int argc, char *argv[]) {
                         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();
@@ -1323,6 +1454,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_welcome = r;
                         break;
 
+                case ARG_RESET:
+                        arg_reset = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1351,6 +1486,7 @@ static int parse_argv(int argc, char *argv[]) {
 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);
@@ -1389,7 +1525,7 @@ static int run(int argc, char *argv[]) {
                                 DISSECT_IMAGE_FSCK |
                                 DISSECT_IMAGE_GROWFS,
                                 &unlink_dir,
-                                /* ret_dir_fd= */ NULL,
+                                &rfd,
                                 &loop_device);
                 if (r < 0)
                         return r;
@@ -1397,33 +1533,49 @@ static int run(int argc, char *argv[]) {
                 arg_root = strdup(unlink_dir);
                 if (!arg_root)
                         return log_oom();
+        } else {
+                rfd = open(empty_to_root(arg_root), O_DIRECTORY|O_CLOEXEC);
+                if (rfd < 0)
+                        return log_error_errno(errno, "Failed to open %s: %m", empty_to_root(arg_root));
+        }
+
+        LOG_SET_PREFIX(arg_image ?: arg_root);
+
+        if (arg_root_shell) {
+                r = find_shell(rfd, arg_root_shell);
+                if (r < 0)
+                        return r;
         }
 
-        r = process_locale();
+        r = process_reset(rfd);
+        if (r < 0)
+                return r;
+
+        r = process_locale(rfd);
         if (r < 0)
                 return r;
 
-        r = process_keymap();
+        r = process_keymap(rfd);
         if (r < 0)
                 return r;
 
-        r = process_timezone();
+        r = process_timezone(rfd);
         if (r < 0)
                 return r;
 
-        r = process_hostname();
+        r = process_hostname(rfd);
         if (r < 0)
                 return r;
 
-        r = process_machine_id();
+        r = process_machine_id(rfd);
         if (r < 0)
                 return r;
 
-        r = process_root_account();
+        r = process_root_account(rfd);
         if (r < 0)
                 return r;
 
-        r = process_kernel_cmdline();
+        r = process_kernel_cmdline(rfd);
         if (r < 0)
                 return r;
 
index 69effacaa2c2dcaaa8b3ebb8ba89334c9c100781..cc2c5512dd08f8a1d0cf625812e7753db1ddf946 100644 (file)
@@ -698,10 +698,10 @@ static int parse_fstab(bool initrd) {
                         }
 
                         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)) {
index 158164ae81c116ab5b625cd24d06879c162cb89b..b265f8741e2aaa8fac93005bf42af07b4b2295a5 100644 (file)
@@ -3576,38 +3576,29 @@ static int parse_argv(int argc, char *argv[]) {
                         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);
@@ -3727,8 +3718,6 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_DROP_CACHES: {
-                        bool drop_caches;
-
                         if (isempty(optarg)) {
                                 r = drop_from_identity("dropCaches");
                                 if (r < 0)
@@ -3736,7 +3725,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 break;
                         }
 
-                        r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
+                        r = parse_boolean_argument("--drop-caches=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
index a8958fcb0cfd0d332afa8d1d9972388fad6bc253..4d1820f9b006bf8640d91625e620a0bec57c2a4a 100644 (file)
@@ -2124,7 +2124,7 @@ static int vl_method_synchronize(Varlink *link, JsonVariant *parameters, Varlink
         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
index bc833e4cb263c66a3dca2f78be85ffc6138060b0..4cbf16f0df2754e42ece790191d9fbfc4ee45d78 100755 (executable)
@@ -1,16 +1,21 @@
 #!/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"
@@ -52,9 +57,9 @@ grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
 "$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"
@@ -86,7 +91,7 @@ grep -qE '^initrd .*/the-token/1.1.1/initrd' "$entry"
 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"
@@ -97,14 +102,14 @@ if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
     # 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"
@@ -112,7 +117,7 @@ if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
     # 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"
index 0eaedec87c77fe41afcc7f58de95c71d39dd4e4b..df26fd75cd19a453d09a49113a61ad5d06e27664 100644 (file)
@@ -32,6 +32,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
         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),
index b6c2e93ea5b0580b3419fc7d4690a5b4f275b6ff..3a0eef49ef57b93d6c99a4037bd0b2d90c30b7db 100644 (file)
@@ -32,6 +32,8 @@
 #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"
index d96b7256a1b162d173a60f223f43a50803be1c11..83d68562dc73fe6437fa370d6cddad0e8faa9508 100644 (file)
@@ -693,7 +693,7 @@ _public_ int sd_bus_get_name_creds(
                                                 "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 */
index 413e2dd43f34dc0c523687567d8d86a273a40515..7a2303350c2ea14640485df9c0f71323c4988c5e 100644 (file)
 #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
 };
 
index 47c5d98896313d1b986d27b37be41e2bda0c7e7e..529eff2fd1991e6bc7aa4ede7bb8afeda6141dd1 100644 (file)
@@ -15,7 +15,7 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) {
 
         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);
index 974aafa5b735b59925e3becb34459cb588e8a086..76c9d1c051a3b4c273273b7b0dfcc18c874f4fdf 100644 (file)
@@ -4,8 +4,8 @@
 #include <fcntl.h>
 #include <unistd.h>
 
-#include "chase.h"
 #include "fd-util.h"
+#include "fs-util.h"
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
@@ -41,8 +41,9 @@ bool id128_is_valid(const char *s) {
         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;
 
@@ -98,23 +99,34 @@ int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret) {
                 return -EUCLEAN;
         }
 
-        r = sd_id128_from_string(buffer, ret);
-        return r == -EINVAL ? -EUCLEAN : r;
+        r = sd_id128_from_string(buffer, &id);
+        if (r == -EINVAL)
+                return -EUCLEAN;
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+                return -ENOMEDIUM;
+
+        if (ret)
+                *ret = id;
+        return 0;
 }
 
-int id128_read(const char *root, const char *p, Id128FormatFlag f, sd_id128_t *ret) {
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) {
         _cleanup_close_ int fd = -EBADF;
 
-        assert(p);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
 
-        fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path = */ NULL);
+        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, 0);
         if (fd < 0)
                 return fd;
 
         return id128_read_fd(fd, f, ret);
 }
 
-int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id) {
         char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
         size_t sz;
         int r;
@@ -122,6 +134,9 @@ int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
         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;
@@ -144,12 +159,15 @@ int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
         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);
 }
@@ -187,9 +205,9 @@ int id128_get_product(sd_id128_t *ret) {
         /* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
          * particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
 
-        r = id128_read(NULL, "/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
+        r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
         if (r == -ENOENT)
-                r = id128_read(NULL, "/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
+                r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
         if (r < 0)
                 return r;
 
index 2ebca8fd95145da6d6f9a6eac57acbede46f263a..7bcbd8e55840cec02bfed6f2c8f6ef3dfd11cccb 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
 #include <stdbool.h>
 
 #include "sd-id128.h"
 
 bool id128_is_valid(const char *s) _pure_;
 
-typedef enum Id128FormatFlag {
-        ID128_FORMAT_PLAIN = 1 << 0,  /* formatted as 32 hex chars as-is */
-        ID128_FORMAT_UUID  = 1 << 1,  /* formatted as 36 character uuid string */
-        ID128_FORMAT_ANY   = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
+typedef enum Id128Flag {
+        ID128_FORMAT_PLAIN  = 1 << 0,  /* formatted as 32 hex chars as-is */
+        ID128_FORMAT_UUID   = 1 << 1,  /* formatted as 36 character uuid string */
+        ID128_FORMAT_ANY    = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
 
         ID128_SYNC_ON_WRITE = 1 << 2, /* Sync the file after write. Used only when writing an ID. */
-} Id128FormatFlag;
-
-int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret);
-int id128_read(const char *root, const char *p, Id128FormatFlag f, sd_id128_t *ret);
-
-int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id);
-int id128_write(const char *p, Id128FormatFlag f, sd_id128_t id);
+        ID128_REFUSE_NULL   = 1 << 3, /* Refuse all zero ID with -ENOMEDIUM. */
+} Id128Flag;
+
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret);
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret);
+static inline int id128_read(const char *path, Id128Flag f, sd_id128_t *ret) {
+        return id128_read_at(AT_FDCWD, path, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id);
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id);
+static inline int id128_write(const char *path, Id128Flag f, sd_id128_t id) {
+        return id128_write_at(AT_FDCWD, path, f, id);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret);
+int id128_get_machine_at(int rfd, sd_id128_t *ret);
 
 void id128_hash_func(const sd_id128_t *p, struct siphash *state);
 int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
index 03f0ddd182970901b01271ab422709c29e8e5166..6a82a7f7b890b8b1db308927a806ca0a17907e07 100644 (file)
@@ -7,6 +7,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "hexdecoct.h"
 #include "hmac.h"
@@ -15,6 +16,7 @@
 #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"
@@ -126,12 +128,9 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) {
         int r;
 
         if (sd_id128_is_null(saved_machine_id)) {
-                r = id128_read(NULL, "/etc/machine-id", ID128_FORMAT_PLAIN, &saved_machine_id);
+                r = id128_read("/etc/machine-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &saved_machine_id);
                 if (r < 0)
                         return r;
-
-                if (sd_id128_is_null(saved_machine_id))
-                        return -ENOMEDIUM;
         }
 
         if (ret)
@@ -139,19 +138,48 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) {
         return 0;
 }
 
+int id128_get_machine_at(int rfd, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        r = dir_fd_is_root_or_cwd(rfd);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return sd_id128_get_machine(ret);
+
+        fd = chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd < 0)
+                return fd;
+
+        return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        if (empty_or_root(root))
+                return sd_id128_get_machine(ret);
+
+        fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd < 0)
+                return fd;
+
+        return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
 _public_ int sd_id128_get_boot(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_boot_id = {};
         int r;
 
         if (sd_id128_is_null(saved_boot_id)) {
-                r = id128_read(NULL, "/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID, &saved_boot_id);
+                r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id);
                 if (r == -ENOENT && proc_mounted() == 0)
                         return -ENOSYS;
                 if (r < 0)
                         return r;
-
-                if (sd_id128_is_null(saved_boot_id))
-                        return -ENOMEDIUM;
         }
 
         if (ret)
index 8da4ca9cadcd3e0d08e600dc149d4c723732f355..a616390ad8429a01dc1312676e00c11751a79aee 100644 (file)
@@ -27,6 +27,7 @@
 #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"
@@ -313,27 +314,79 @@ JournalFile* journal_file_close(JournalFile *f) {
 }
 
 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(
@@ -355,7 +408,7 @@ 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(
@@ -1619,24 +1672,31 @@ static int journal_file_append_field(
 }
 
 static Compression maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
-        Compression compression = COMPRESSION_NONE;
-
         assert(f);
         assert(f->header);
 
 #if HAVE_COMPRESSION
-        if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) {
-                compression = compress_blob(src, size, dst, size - 1, rsize);
-                if (compression > 0)
-                        log_debug("Compressed data object %"PRIu64" -> %zu using %s",
-                                  size, *rsize, compression_to_string(compression));
-                else
-                        /* Compression didn't work, we don't really care why, let's continue without compression */
-                        compression = COMPRESSION_NONE;
+        Compression c;
+        int r;
+
+        c = JOURNAL_FILE_COMPRESSION(f);
+        if (c == COMPRESSION_NONE || size < f->compress_threshold_bytes)
+                return COMPRESSION_NONE;
+
+        r = compress_blob_explicit(c, src, size, dst, size - 1, rsize);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to compress data object using %s, ignoring: %m", compression_to_string(c));
+                /* Compression didn't work, we don't really care why, let's continue without compression */
+                return COMPRESSION_NONE;
         }
-#endif
 
-        return compression;
+        assert(r == c);
+        log_debug("Compressed data object %"PRIu64" -> %zu using %s", size, *rsize, compression_to_string(c));
+
+        return c;
+#else
+        return COMPRESSION_NONE;
+#endif
 }
 
 static int journal_file_append_data(
@@ -3956,20 +4016,21 @@ int journal_file_open(
         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;
                 }
         }
index 70d2276ced1a4b2f02c914c28bd0261bda00b7cf..a4d8912aa8e074a2812a8c989e83dc2f0d1d29b5 100644 (file)
@@ -321,10 +321,16 @@ bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec, int log
 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);
index 2d2989086437fe2a2e3d4ec4a57cdfbc700bce33..223164ea1bd25510ae147f5680962802c48c203a 100644 (file)
@@ -162,7 +162,7 @@ static int run(int argc, char *argv[]) {
                 if (r < 0)
                         return r;
 
-                r = id128_read(arg_root, "/etc/machine-id", ID128_FORMAT_PLAIN, &id);
+                r = id128_get_machine(arg_root, &id);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read machine ID back: %m");
         } else {
index 65015b132f0322f61c7082af26ffcd4407a9588d..9b21afe405b70e7cfa87173e675618ca4fcc913f 100644 (file)
@@ -80,10 +80,61 @@ static bool arg_full = false;
 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);
@@ -614,8 +665,8 @@ static int link_get_property(
                 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);
@@ -624,17 +675,7 @@ static int link_get_property(
         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) {
@@ -711,6 +752,7 @@ static void acquire_wlan_link_info(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;
@@ -730,7 +772,6 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
         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)
@@ -774,8 +815,8 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
         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);
 
@@ -786,13 +827,17 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
 }
 
 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);
@@ -829,24 +874,24 @@ static int list_links(int argc, char *argv[], void *userdata) {
         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,
@@ -868,24 +913,21 @@ static int list_links(int argc, char *argv[], void *userdata) {
 
 /* 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)
@@ -895,7 +937,7 @@ static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
         if (!desc)
                 return -ENOMEM;
 
-        *ret = desc;
+        *ret = TAKE_PTR(desc);
 
         return 0;
 }
@@ -906,7 +948,8 @@ static int get_gateway_description(
                 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;
 
@@ -914,7 +957,7 @@ static int get_gateway_description(
         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)
@@ -936,35 +979,37 @@ static int get_gateway_description(
 
                 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;
                 }
 
@@ -972,18 +1017,21 @@ static int get_gateway_description(
                         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;
                 }
@@ -995,7 +1043,7 @@ static int get_gateway_description(
                 if (r < 0)
                         continue;
 
-                r = ieee_oui(hwdb, &mac, gateway_description);
+                r = ieee_oui(hwdb, &mac, ret);
                 if (r < 0)
                         continue;
 
@@ -1021,37 +1069,33 @@ static int dump_list(Table *table, const char *prefix, char * const *l) {
         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();
         }
@@ -1065,7 +1109,7 @@ static int dump_addresses(
                 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;
@@ -1073,27 +1117,27 @@ static int dump_addresses(
         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();
         }
@@ -1146,7 +1190,7 @@ static int dump_address_labels(sd_netlink *rtnl) {
 
                 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;
                 }
 
@@ -1188,21 +1232,20 @@ static int list_address_labels(int argc, char *argv[], void *userdata) {
         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;
 }
 
@@ -2388,12 +2431,16 @@ static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) {
 }
 
 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);
@@ -2516,14 +2563,14 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
         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;
                 }
 
@@ -2552,7 +2599,7 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
                         }
 
                         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,
@@ -2701,10 +2748,14 @@ static int link_renew_one(sd_bus *bus, int index, const char *name) {
 }
 
 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)
@@ -2731,10 +2782,14 @@ static int link_force_renew_one(sd_bus *bus, int index, const char *name) {
 }
 
 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)
@@ -2749,10 +2804,14 @@ static int link_force_renew(int argc, char *argv[], void *userdata) {
 }
 
 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));
@@ -2761,13 +2820,17 @@ static int verb_reload(int argc, char *argv[], void *userdata) {
 }
 
 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();
@@ -2916,7 +2979,7 @@ static int parse_argv(int argc, char *argv[]) {
         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         },
@@ -2932,53 +2995,10 @@ static int networkctl_main(sd_bus *bus, int argc, char *argv[]) {
                 {}
         };
 
-        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();
@@ -2987,17 +3007,7 @@ static int run(int argc, char* argv[]) {
         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);
index e1e89e8357527752c6af8f63d6cc5cf9732a122b..0c4032633fb760c9381ab9294c9bcca0f87439a1 100644 (file)
@@ -4,6 +4,7 @@
 #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"
 
@@ -54,8 +55,8 @@ static const sd_bus_vtable network_vtable[] = {
 };
 
 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);
@@ -65,7 +66,9 @@ static char *network_bus_path(Network *network) {
         if (!name)
                 return NULL;
 
-        networkname = basename(name);
+        r = path_extract_filename(name, &networkname);
+        if (r < 0)
+                return NULL;
 
         d = strrchr(networkname, '.');
         if (!d)
index 7320ffebdeffe7a39e6e596f913f585810c7bdbf..8489b8387309e36f9d043eaa1d448384f0095798 100644 (file)
@@ -11,6 +11,8 @@
 #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"
@@ -33,9 +35,13 @@ static gid_t arg_gid = GID_INVALID;
 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;
@@ -60,6 +66,8 @@ static int help(void) {
                "     --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,
@@ -103,6 +111,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UID,
                 ARG_NO_BLOCK,
                 ARG_EXEC,
+                ARG_FD,
+                ARG_FDNAME,
         };
 
         static const struct option options[] = {
@@ -117,9 +127,12 @@ static int parse_argv(int argc, char *argv[]) {
                 { "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;
 
@@ -198,6 +211,60 @@ static int parse_argv(int argc, char *argv[]) {
                         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;
 
@@ -212,11 +279,15 @@ static int parse_argv(int argc, char *argv[]) {
             !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;
 
@@ -243,13 +314,16 @@ static int parse_argv(int argc, char *argv[]) {
                         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;
@@ -302,6 +376,18 @@ static int run(int argc, char* argv[]) {
                 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);
@@ -338,13 +424,28 @@ static int run(int argc, char* argv[]) {
                                                   * 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)
index 75349c3b0ecd9657d92566f68094a8502b152899..ce95b10b1cee17f636f914feb10194b8f8520272 100644 (file)
@@ -2831,7 +2831,6 @@ static int mount_tunnel_open(void) {
 }
 
 static int setup_machine_id(const char *directory) {
-        sd_id128_t id;
         int r;
 
         /* If the UUID in the container is already set, then that's what counts, and we use. If it isn't set, and the
@@ -2841,7 +2840,7 @@ static int setup_machine_id(const char *directory) {
          * in the container and our idea of the container UUID will always be in sync (at least if PID 1 in the
          * container behaves nicely). */
 
-        r = id128_read(directory, "/etc/machine-id", ID128_FORMAT_PLAIN, &id);
+        r = id128_get_machine(directory, &arg_uuid);
         if (r < 0) {
                 if (!ERRNO_IS_MACHINE_ID_UNSET(r)) /* If the file is missing, empty, or uninitialized, we don't mind */
                         return log_error_errno(r, "Failed to read machine ID from container image: %m");
@@ -2851,12 +2850,6 @@ static int setup_machine_id(const char *directory) {
                         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;
index 43f2901016fd47d3575534e8f1b5a4cff8d032f8..71c69569561f178dfb2b665ad35ad81fa15f6e45 100644 (file)
@@ -3389,8 +3389,8 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) {
                 _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;
@@ -3415,7 +3415,9 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) {
                               &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");
 
@@ -3447,6 +3449,7 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) {
                                 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)
@@ -3960,6 +3963,8 @@ static int partition_populate_directory(Partition *p, Hashmap *denylist, char **
 
         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");
@@ -3980,6 +3985,8 @@ static int partition_populate_directory(Partition *p, Hashmap *denylist, char **
         if (r < 0)
                 return r;
 
+        log_info("Successfully populated %s filesystem.", p->format);
+
         *ret = TAKE_PTR(root);
         return 0;
 }
@@ -3990,7 +3997,7 @@ static int partition_populate_filesystem(Partition *p, const char *node, Hashmap
         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
@@ -4027,7 +4034,7 @@ static int partition_populate_filesystem(Partition *p, const char *node, Hashmap
                 _exit(EXIT_SUCCESS);
         }
 
-        log_info("Successfully populated %s filesystem with files.", p->format);
+        log_info("Successfully populated %s filesystem.", p->format);
         return 0;
 }
 
@@ -4906,24 +4913,14 @@ static int context_read_seed(Context *context, const char *root) {
                 return 0;
 
         if (!arg_randomize) {
-                _cleanup_close_ int fd = -EBADF;
-
-                fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, NULL);
-                if (fd == -ENOENT)
-                        log_info("No machine ID set, using randomized partition UUIDs.");
-                else if (fd < 0)
-                        return log_error_errno(fd, "Failed to determine machine ID of image: %m");
-                else {
-                        r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &context->seed);
-                        if (r < 0) {
-                                if (!ERRNO_IS_MACHINE_ID_UNSET(r))
-                                        return log_error_errno(r, "Failed to parse machine ID of image: %m");
+                r = id128_get_machine(root, &context->seed);
+                if (r >= 0)
+                        return 0;
 
-                                log_info("No machine ID set, using randomized partition UUIDs.");
-                        }
+                if (!ERRNO_IS_MACHINE_ID_UNSET(r))
+                        return log_error_errno(r, "Failed to parse machine ID of image: %m");
 
-                        return 0;
-                }
+                log_info("No machine ID set, using randomized partition UUIDs.");
         }
 
         r = sd_id128_randomize(&context->seed);
@@ -5443,6 +5440,7 @@ static int context_minimize(Context *context) {
                 _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;
 
@@ -5463,6 +5461,11 @@ static int context_minimize(Context *context) {
 
                 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;
@@ -5520,6 +5523,14 @@ static int context_minimize(Context *context) {
                 /* 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;
@@ -5553,6 +5564,9 @@ static int context_minimize(Context *context) {
                 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. */
index dbebebf4cefb77c568868d2c92886a5682798b41..f3fc06f6fd5c2f5ff0b582c7b9683a547022d1b5 100644 (file)
@@ -16,7 +16,7 @@
 #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"
@@ -198,7 +198,7 @@ static int extract_now(
         /* 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);
@@ -607,7 +607,7 @@ static int extract_image_and_extensions(
                         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)
@@ -948,17 +948,17 @@ static int append_release_log_fields(
 
         static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
                  [IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
-                 [IMAGE_EXTENSION] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
+                 [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
         };
         static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
                  [IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
-                 [IMAGE_EXTENSION] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
+                 [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
         };
         _cleanup_strv_free_ char **fields = NULL;
         const char *id = NULL, *version = NULL;
         int r;
 
-        assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_EXTENSION));
+        assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT));
         assert(!strv_isempty((char *const *)field_ids[type]));
         assert(!strv_isempty((char *const *)field_versions[type]));
         assert(field_name);
@@ -1106,7 +1106,7 @@ static int install_chroot_dropin(
                                  * still be able to identify what applies to what. */
                                 r = append_release_log_fields(&text,
                                                               ordered_hashmap_get(extension_releases, ext->name),
-                                                              IMAGE_EXTENSION,
+                                                              IMAGE_SYSEXT,
                                                               "PORTABLE_EXTENSION_NAME_AND_VERSION");
                                 if (r < 0)
                                         return r;
index 505e3e7ba940731fb7b4351c8726d9107ad072ed..e4d3e94c68379e6e39da92e1ab43d2a0de71fcae 100644 (file)
@@ -55,8 +55,8 @@ assert_cc(sizeof(DnsPacketHeader) == 12);
 /* 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;
index c95875ec1012c7eaddfaeeef12e7e3f643b23672..45f1d3631196564bc5d61674e3dca19400b10d77 100644 (file)
@@ -556,6 +556,9 @@ static DnsScopeMatch match_subnet_reverse_lookups(
         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)
@@ -569,6 +572,10 @@ static DnsScopeMatch match_subnet_reverse_lookups(
                 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)
index 70e726e417a9cde74e51e972f767508015cda4eb..9385b75e4b400cc33c1a72026572405737d18432 100644 (file)
@@ -816,7 +816,11 @@ ResolveSupport link_get_mdns_support(Link *link) {
         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);
@@ -829,6 +833,7 @@ int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr
         *a = (LinkAddress) {
                 .family = family,
                 .in_addr = *in_addr,
+                .in_addr_broadcast = *in_addr_broadcast,
                 .link = l,
                 .prefixlen = UCHAR_MAX,
         };
index d2043a10008050b0f84d423760d9257b3de911cc..0695a6ff838b9c0fcf5e0663efbc9398260baa31 100644 (file)
@@ -26,6 +26,7 @@ struct LinkAddress {
 
         int family;
         union in_addr_union in_addr;
+        union in_addr_union in_addr_broadcast;
         unsigned char prefixlen;
 
         unsigned char flags, scope;
@@ -111,7 +112,11 @@ int link_save_user(Link *l);
 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);
index ec854774af28195f78583ce583e689d329c0b79b..57e26f4975e5c08f852e415ee587ffa7ec03f8f8 100644 (file)
@@ -106,7 +106,7 @@ fail:
 
 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;
@@ -134,6 +134,7 @@ static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, voi
         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);
@@ -164,7 +165,7 @@ static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, voi
         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;
                 }
index 0d8a8aa0f67b0f09aad2c6b06c74e9626a4bca7f..7cc63e88486dabae79833b553bec9c85b7d99cbe 100644 (file)
@@ -34,6 +34,15 @@ static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
 
 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);
 
@@ -1422,6 +1431,7 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
                         }
 
                         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)),
@@ -1444,6 +1454,7 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
                          * 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)),
index ac4d1890b09c90a93305eaef0232e78ce17fbbfe..ddd149eadbbf84a15f8c4656baae81a8f7cb84b6 100644 (file)
@@ -79,6 +79,7 @@ typedef struct BootConfig {
         }
 
 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);
 
index c68405754152805e825b2b40d64434beb60d6bf0..2789364c98fe84bbcd1b3f01e49a0e94f88ebaef 100644 (file)
@@ -247,16 +247,17 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs
         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, &regfd);
+        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, 0);
         if (fd < 0)
                 return fd;
 
@@ -271,7 +272,7 @@ int btrfs_get_block_device_fd(int fd, dev_t *dev) {
 
         /* We won't do this for btrfs RAID */
         if (fsi.num_devices != 1) {
-                *dev = 0;
+                *ret = 0;
                 return 0;
         }
 
@@ -306,26 +307,13 @@ int btrfs_get_block_device_fd(int fd, dev_t *dev) {
                 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
index 080d5a607d8d5c5e64a8deaa2f9aee85f4aee65a..75a8ed85490ec0f37fcbd9e0f048873b7e2d8e55 100644 (file)
@@ -49,8 +49,13 @@ int btrfs_is_subvol(const char *path);
 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);
index 750ee2571e91e3568cd0a3def796dd7acd823e7e..d570f49e7b51619b8dc5f39751b99c489c52d61c 100644 (file)
@@ -703,7 +703,9 @@ int encrypt_credential_and_warn(
                               &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.");
@@ -1033,6 +1035,8 @@ int decrypt_credential_and_warn(
                                     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),
@@ -1046,6 +1050,8 @@ int decrypt_credential_and_warn(
                                 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)
index fa018cb9123cc12474d96b57111612a2c713fe15..0343d2e20b256a09213a8d1f5a3c672789ead9a8 100644 (file)
@@ -22,6 +22,7 @@
 #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"
@@ -58,11 +59,18 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
                             "/usr/local/lib/portables\0"
                             "/usr/lib/portables\0",
 
-        [IMAGE_EXTENSION] = "/etc/extensions\0"             /* only place symlinks here */
-                            "/run/extensions\0"             /* and here too */
-                            "/var/lib/extensions\0"         /* the main place for images */
-                            "/usr/local/lib/extensions\0"
-                            "/usr/lib/extensions\0",
+        /* Note that we don't allow storing extensions under /usr/, unlike with other image types. That's
+         * because extension images are supposed to extend /usr/, so you get into recursive races, especially
+         * with directory-based extensions, as the kernel's OverlayFS explicitly checks for this and errors
+         * out with -ELOOP if it finds that a lowerdir= is a child of another lowerdir=. */
+        [IMAGE_SYSEXT] =    "/etc/extensions\0"            /* only place symlinks here */
+                            "/run/extensions\0"            /* and here too */
+                            "/var/lib/extensions\0",       /* the main place for images */
+
+        [IMAGE_CONFEXT] =   "/run/confexts\0"              /* only place symlinks here */
+                            "/var/lib/confexts\0"          /* the main place for images */
+                            "/usr/local/lib/confexts\0"
+                            "/usr/lib/confexts\0",
 };
 
 static Image *image_free(Image *i) {
@@ -1149,6 +1157,16 @@ int image_read_metadata(Image *i) {
                 _cleanup_free_ char *hostname = NULL;
                 _cleanup_free_ char *path = NULL;
 
+                if (i->class == IMAGE_SYSEXT) {
+                        r = extension_has_forbidden_content(i->path);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+                                                       "Conflicting content found in image %s, refusing.",
+                                                       i->name);
+                }
+
                 r = chase("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
                 if (r < 0 && r != -ENOENT)
                         log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name);
@@ -1160,23 +1178,9 @@ int image_read_metadata(Image *i) {
 
                 path = mfree(path);
 
-                r = chase("/etc/machine-id", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
-                if (r < 0 && r != -ENOENT)
-                        log_debug_errno(r, "Failed to chase /etc/machine-id in image %s: %m", i->name);
-                else if (r >= 0) {
-                        _cleanup_close_ int fd = -EBADF;
-
-                        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                        if (fd < 0)
-                                log_debug_errno(errno, "Failed to open %s: %m", path);
-                        else {
-                                r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id);
-                                if (r < 0)
-                                        log_debug_errno(r, "Image %s contains invalid machine ID.", i->name);
-                        }
-                }
-
-                path = mfree(path);
+                r = id128_get_machine(i->path, &machine_id);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read machine ID in image %s, ignoring: %m", i->name);
 
                 r = chase("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
                 if (r < 0 && r != -ENOENT)
@@ -1191,7 +1195,7 @@ int image_read_metadata(Image *i) {
                 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");
 
@@ -1326,7 +1330,8 @@ 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",
+        [IMAGE_SYSEXT] = "extension",
+        [IMAGE_CONFEXT] = "confext"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
index 3c6928619c0eb02d579f250379284bfcc5883093..342b1615774d5a0285ffcae64aa19aad219903e0 100644 (file)
@@ -9,18 +9,11 @@
 #include "hashmap.h"
 #include "lock-util.h"
 #include "macro.h"
+#include "os-util.h"
 #include "path-util.h"
 #include "string-util.h"
 #include "time-util.h"
 
-typedef enum ImageClass {
-        IMAGE_MACHINE,
-        IMAGE_PORTABLE,
-        IMAGE_EXTENSION,
-        _IMAGE_CLASS_MAX,
-        _IMAGE_CLASS_INVALID = -EINVAL,
-} ImageClass;
-
 typedef enum ImageType {
         IMAGE_DIRECTORY,
         IMAGE_SUBVOLUME,
@@ -77,9 +70,6 @@ int image_read_only(Image *i, bool b);
 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);
 
index 2920a51dc298f29529c3f1ee1950e092bb2902ad..a68610b80e25d349ca5a87c335970cf52420b74d 100644 (file)
@@ -39,7 +39,7 @@
 #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"
@@ -1787,11 +1787,16 @@ int dissected_image_mount(
                                         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)
@@ -3049,7 +3054,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
                                  * 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;
@@ -3601,7 +3606,7 @@ int verity_dissect_and_mount(
 
                 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);
 
@@ -3611,7 +3616,8 @@ int verity_dissect_and_mount(
                                 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)
index ac68cbc4cb16681e3d25cecb3cba6567310c4b67..56f16188e1b7d10ea6f7881995db3af59134de0b 100644 (file)
@@ -39,7 +39,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b
         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;
         }
@@ -427,14 +427,14 @@ int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ex_opts) {
         _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)
@@ -444,7 +444,6 @@ int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ex_opts) {
                         if (r < 0)
                                 return r;
                 }
-        }
 
         *ex_opts = TAKE_PTR(ret_opts);
 
@@ -466,9 +465,7 @@ static const char* const exec_command_strings[] = {
 };
 
 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];
 
diff --git a/src/shared/extension-release.h b/src/shared/extension-release.h
deleted file mode 100644 (file)
index 5c3fee2..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/* 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);
similarity index 51%
rename from src/shared/extension-release.c
rename to src/shared/extension-util.c
index 2da8e7ea94bec49d261fac8891d42da32d8a7b7b..43a19bf2625d21d6eeb327a6afb4adef61c4a8a1 100644 (file)
@@ -2,8 +2,9 @@
 
 #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"
@@ -12,39 +13,42 @@ 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) {
+                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;
                 }
         }
@@ -53,21 +57,21 @@ int extension_release_validate(
          * 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;
@@ -80,18 +84,18 @@ int extension_release_validate(
         }
 
         /* 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)) {
@@ -99,7 +103,7 @@ int extension_release_validate(
 
                 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;
                 }
@@ -109,7 +113,7 @@ int extension_release_validate(
                                   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;
@@ -119,19 +123,41 @@ int extension_release_validate(
         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;
+}
diff --git a/src/shared/extension-util.h b/src/shared/extension-util.h
new file mode 100644 (file)
index 0000000..3cad219
--- /dev/null
@@ -0,0 +1,23 @@
+/* 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);
index cfa12a509e30a2b483a9f94b6932ec9d23ec7fdb..d816a3e4efb9640dcbfeec8be3244a054466f95b 100644 (file)
@@ -15,6 +15,7 @@
 #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)
@@ -23,27 +24,29 @@ FDSet *fdset_new(void) {
         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;
 }
 
@@ -77,8 +80,22 @@ int fdset_put(FDSet *s, int fd) {
         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);
@@ -88,12 +105,10 @@ int fdset_put_dup(FDSet *s, int fd) {
                 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) {
@@ -110,53 +125,46 @@ int fdset_remove(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) {
@@ -174,53 +182,66 @@ 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) {
index 39d15ee4aaf076b14312221a054deb8b90d535ba..9700cdbbca8b6d468b3e85aa5c80e6bf53cd275c 100644 (file)
@@ -13,6 +13,7 @@ FDSet* fdset_new(void);
 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);
@@ -24,6 +25,8 @@ int fdset_new_listen_fds(FDSet **ret, bool unset);
 
 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);
index d03b0548ec9265df6dfbfa999c4049e88876a943..572b8f62ad681576d61f2dac83893c3740e955b5 100644 (file)
@@ -23,9 +23,15 @@ int write_string_file_atomic_label_ts(const char *fn, const char *line, struct t
 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. "
index f6abe51de5f61f8511685cac9cb0dbd5b069607d..0d45249d6327ce2369b5b77c00235663420fbd97 100644 (file)
@@ -238,12 +238,13 @@ static int verify_esp_udev(
 }
 
 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;
@@ -251,24 +252,22 @@ static int verify_fsroot_dir(
         /* 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 */
@@ -288,29 +287,7 @@ static int verify_fsroot_dir(
         }
 
         /* 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);
@@ -327,25 +304,17 @@ success:
         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,
@@ -355,10 +324,13 @@ static int verify_esp(
 
         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:
          *
@@ -374,13 +346,25 @@ static int verify_esp(
         /* 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))
@@ -393,7 +377,7 @@ static int verify_esp(
                 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;
 
@@ -414,12 +398,16 @@ static int verify_esp(
         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)
@@ -434,8 +422,8 @@ finish:
         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,
@@ -445,9 +433,7 @@ int find_esp_and_warn(
                 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:
@@ -456,48 +442,43 @@ int find_esp_and_warn(
          *   -EACCESS → when unprivileged_mode is true, and we can't access something
          */
 
-        if (path) {
-                r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
+        assert(rfd >= 0 || rfd == AT_FDCWD);
 
-                r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags);
-                if (r < 0)
-                        return r;
+        r = dir_fd_is_root_or_cwd(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+        if (r == 0)
+                flags |= VERIFY_ESP_RELAX_CHECKS;
 
-                goto found;
-        }
+        if (path)
+                return verify_esp(rfd, path, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags);
 
         path = getenv("SYSTEMD_ESP_PATH");
         if (path) {
+                _cleanup_free_ char *p = NULL;
+                _cleanup_close_ int fd = -EBADF;
                 struct stat st;
 
-                r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                if (!path_is_valid(p) || !path_is_absolute(p))
+                if (!path_is_valid(path) || !path_is_absolute(path))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
-                                               p);
+                                               "$SYSTEMD_ESP_PATH does not refer to an absolute path, refusing to use it: %s",
+                                               path);
+
+                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, &p, &fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to resolve path %s: %m", path);
 
                 /* Note: when the user explicitly configured things with an env var we won't validate the
                  * path beyond checking it refers to a directory. After all we want this to be useful for
                  * testing. */
 
-                if (stat(p, &st) < 0)
+                if (fstat(fd, &st) < 0)
                         return log_error_errno(errno, "Failed to stat '%s': %m", p);
                 if (!S_ISDIR(st.st_mode))
                         return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p);
 
+                if (ret_path)
+                        *ret_path = TAKE_PTR(p);
                 if (ret_part)
                         *ret_part = 0;
                 if (ret_pstart)
@@ -509,36 +490,72 @@ int find_esp_and_warn(
                 if (ret_devid)
                         *ret_devid = st.st_dev;
 
-                goto found;
+                return 0;
         }
 
         FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") {
-                r = chase(dir, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r == -ENOENT)
-                        continue;
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               dir,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid,
+                r = verify_esp(rfd, dir, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid,
                                flags | VERIFY_ESP_SEARCHING);
                 if (r >= 0)
-                        goto found;
+                        return 0;
                 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR, -ENOTTY)) /* This one is not it */
                         return r;
-
-                p = mfree(p);
         }
 
         /* No logging here */
         return -ENOKEY;
+}
 
-found:
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
+int find_esp_and_warn(
+                const char *root,
+                const char *path,
+                bool unprivileged_mode,
+                char **ret_path,
+                uint32_t *ret_part,
+                uint64_t *ret_pstart,
+                uint64_t *ret_psize,
+                sd_id128_t *ret_uuid,
+                dev_t *ret_devid) {
+
+        _cleanup_close_ int rfd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        uint32_t part;
+        uint64_t pstart, psize;
+        sd_id128_t uuid;
+        dev_t devid;
+        int r;
+
+        rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC);
+        if (rfd < 0)
+                return -errno;
+
+        r = find_esp_and_warn_at(rfd, path, unprivileged_mode,
+                                 ret_path ? &p : NULL,
+                                 ret_part ? &part : NULL,
+                                 ret_pstart ? &pstart : NULL,
+                                 ret_psize ? &psize : NULL,
+                                 ret_uuid ? &uuid : NULL,
+                                 ret_devid ? &devid : NULL);
+        if (r < 0)
+                return r;
+
+        if (ret_path) {
+                char *q = path_join(empty_to_root(root), p);
+                if (!q)
+                        return -ENOMEM;
+
+                *ret_path = TAKE_PTR(q);
+        }
+        if (ret_part)
+                *ret_part = part;
+        if (ret_pstart)
+                *ret_pstart = pstart;
+        if (ret_psize)
+                *ret_psize = psize;
+        if (ret_uuid)
+                *ret_uuid = uuid;
+        if (ret_devid)
+                *ret_devid = devid;
 
         return 0;
 }
@@ -701,23 +718,34 @@ static int verify_xbootldr_udev(
 }
 
 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;
 
@@ -731,12 +759,16 @@ static int verify_xbootldr(
         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)
@@ -745,85 +777,100 @@ finish:
         return 0;
 }
 
-int find_xbootldr_and_warn(
-                const char *root,
+int find_xbootldr_and_warn_at(
+                int rfd,
                 const char *path,
                 bool unprivileged_mode,
                 char **ret_path,
                 sd_id128_t *ret_uuid,
                 dev_t *ret_devid) {
 
-        _cleanup_free_ char *p = NULL;
         int r;
 
         /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
 
-        if (path) {
-                r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                r = verify_xbootldr(p, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid);
-                if (r < 0)
-                        return r;
+        assert(rfd >= 0 || rfd == AT_FDCWD);
 
-                goto found;
-        }
+        if (path)
+                return verify_xbootldr(rfd, path, /* searching= */ false, unprivileged_mode, ret_path, ret_uuid, ret_devid);
 
         path = getenv("SYSTEMD_XBOOTLDR_PATH");
         if (path) {
+                _cleanup_free_ char *p = NULL;
+                _cleanup_close_ int fd = -EBADF;
                 struct stat st;
 
-                r = chase(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                if (!path_is_valid(p) || !path_is_absolute(p))
+                if (!path_is_valid(path) || !path_is_absolute(path))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
-                                               p);
+                                               "$SYSTEMD_XBOOTLDR_PATH does not refer to an absolute path, refusing to use it: %s",
+                                               path);
 
-                if (stat(p, &st) < 0)
+                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, &p, &fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to resolve path %s: %m", p);
+
+                if (fstat(fd, &st) < 0)
                         return log_error_errno(errno, "Failed to stat '%s': %m", p);
                 if (!S_ISDIR(st.st_mode))
                         return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p);
 
+                if (ret_path)
+                        *ret_path = TAKE_PTR(p);
                 if (ret_uuid)
                         *ret_uuid = SD_ID128_NULL;
                 if (ret_devid)
                         *ret_devid = st.st_dev;
 
-                goto found;
+                return 0;
         }
 
-        r = chase("/boot", root, CHASE_PREFIX_ROOT, &p, NULL);
-        if (r == -ENOENT)
+        r = verify_xbootldr(rfd, "/boot", /* searching= */ true, unprivileged_mode, ret_path, ret_uuid, ret_devid);
+        if (r < 0) {
+                if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+                        return r;
+
                 return -ENOKEY;
-        if (r < 0)
-                return log_error_errno(r,
-                                       "Failed to resolve path /boot%s%s: %m",
-                                       root ? " under directory " : "",
-                                       strempty(root));
+        }
+
+        return 0;
+}
 
-        r = verify_xbootldr(p, /* searching= */ true, unprivileged_mode, ret_uuid, ret_devid);
-        if (r >= 0)
-                goto found;
-        if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+int find_xbootldr_and_warn(
+        const char *root,
+        const char *path,
+        bool unprivileged_mode,
+        char **ret_path,
+        sd_id128_t *ret_uuid,
+        dev_t *ret_devid) {
+
+        _cleanup_close_ int rfd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        sd_id128_t uuid;
+        dev_t devid;
+        int r;
+
+        rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC);
+        if (rfd < 0)
+                return -errno;
+
+        r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode,
+                                      ret_path ? &p : NULL,
+                                      ret_uuid ? &uuid : NULL,
+                                      ret_devid ? &devid : NULL);
+        if (r < 0)
                 return r;
 
-        return -ENOKEY;
+        if (ret_path) {
+                char *q = path_join(empty_to_root(root), p);
+                if (!q)
+                        return -ENOMEM;
 
-found:
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
+                *ret_path = TAKE_PTR(q);
+        }
+        if (ret_uuid)
+                *ret_uuid = uuid;
+        if (ret_devid)
+                *ret_devid = devid;
 
         return 0;
 }
index 78d7f4551ed80127764df4db46e082319ee61532..94f320195bf0154fd451534ec28f447e33ce3f65 100644 (file)
@@ -8,5 +8,8 @@
 
 #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);
index 351a5ede118445f9cd1eb40726f73b08f954afad..204e8b68b6d5903c9b066862e60b68990816e819 100644 (file)
@@ -7,6 +7,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "devnum-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
@@ -24,6 +25,7 @@
 #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"
@@ -107,6 +109,7 @@ typedef struct TableData {
                 gid_t gid;
                 pid_t pid;
                 mode_t mode;
+                dev_t devnum;
                 /* … add more here as we start supporting more cell data types … */
         };
 } TableData;
@@ -348,8 +351,12 @@ static size_t table_data_size(TableDataType type, const void *data) {
                 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();
         }
@@ -867,6 +874,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         gid_t gid;
                         pid_t pid;
                         mode_t mode;
+                        dev_t devnum;
                 } buffer;
 
                 switch (type) {
@@ -1022,10 +1030,16 @@ int table_add_many_internal(Table *t, TableDataType first_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);
 
@@ -1273,6 +1287,8 @@ int table_hide_column_from_display_internal(Table *t, ...) {
 }
 
 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
+        int r;
+
         assert(a);
         assert(b);
 
@@ -1377,8 +1393,16 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                         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:
                         ;
                 }
@@ -1882,6 +1906,22 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
                 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();
         }
@@ -2696,11 +2736,20 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
                 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;
         }
index 5a2b366b59c9b9e9f8c3ae59e1df660c3bb80680..148418c70f6143a1ad0d3db7d6086ba8185ffc6b 100644 (file)
@@ -51,7 +51,9 @@ typedef enum TableDataType {
         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
index 4b4309037b47f946084bd733d482da533d8a01c9..f27c3d768bfcd5b7cd0b4102d3f0a6299d55cc91 100644 (file)
@@ -8,6 +8,7 @@
 #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) {
index cb0697d1b9331f23f91f24539d311e6f95a7f58e..0f2e2d1a6751fc47083c474e467a64fee3e438fe 100644 (file)
@@ -64,7 +64,7 @@ shared_sources = files(
         'ethtool-util.c',
         'exec-util.c',
         'exit-status.c',
-        'extension-release.c',
+        'extension-util.c',
         'fdset.c',
         'fileio-label.c',
         'find-esp.c',
index bfaa4d1b55b311aeed66f5ccee1e265f4f331ee4..16d2fb651fca2faea6822eaef1231e71e20c8a83 100644 (file)
@@ -479,6 +479,13 @@ int make_filesystem(
         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,
index 6cc4265737b3c4b13b9b0f7839a8cbe07ccd3242..e99c321418df3f613d79dd2210e3cb1ab19675ed 100644 (file)
@@ -11,6 +11,7 @@
 #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"
@@ -28,9 +29,11 @@ static bool is_physical_fs(const struct statfs *sfs) {
 
 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);
@@ -39,16 +42,24 @@ static int patch_dirfd_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) {
@@ -64,18 +75,18 @@ int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags
         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,
@@ -99,22 +110,84 @@ int fstatat_harder(int dfd,
         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,
@@ -169,13 +242,16 @@ static int rm_rf_inner_child(
                 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;
@@ -191,6 +267,7 @@ static int rm_rf_inner_child(
 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) {
@@ -207,6 +284,22 @@ int rm_rf_children(
                 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 */
@@ -223,14 +316,20 @@ int rm_rf_children(
                          * 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 */
@@ -287,17 +386,25 @@ int rm_rf_children(
                                  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;
@@ -306,14 +413,20 @@ int rm_rf_children(
                 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);
@@ -345,19 +458,20 @@ int rm_rf(const char *path, RemoveFlags flags) {
                 /* 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;
index 65e555f716da141079626f85eae636376cf29cfc..a4a4017f061939980f856b1e77c3ed8f5551ac93 100644 (file)
@@ -187,19 +187,9 @@ int specifier_machine_id(char specifier, const void *data, const char *root, con
 
         assert(ret);
 
-        if (root) {
-                _cleanup_close_ int fd = -EBADF;
-
-                fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
-                if (fd < 0)
-                        /* Translate error for missing os-release file to EUNATCH. */
-                        return fd == -ENOENT ? -EUNATCH : fd;
-
-                r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &id);
-        } else
-                r = sd_id128_get_machine(&id);
-        if (r < 0)
-                return r;
+        r = id128_get_machine(root, &id);
+        if (r < 0) /* Translate error for missing /etc/machine-id file to EUNATCH. */
+                return r == -ENOENT ? -EUNATCH : r;
 
         return specifier_id128(specifier, &id, root, userdata, ret);
 }
index 28d743a5767830e4d823dbf7974b63ea06971164..1ffd78c7414597a748530914857406eae8a1e202 100644 (file)
@@ -13,6 +13,7 @@
 #include "fs-util.h"
 #include "hexdecoct.h"
 #include "hmac.h"
+#include "lock-util.h"
 #include "memory-util.h"
 #include "openssl-util.h"
 #include "parse-util.h"
@@ -30,6 +31,7 @@ static void *libtss2_mu_dl = NULL;
 
 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;
@@ -44,10 +46,14 @@ TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySes
 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);
@@ -66,6 +72,7 @@ int dlopen_tpm2(void) {
                         &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),
@@ -80,10 +87,14 @@ int dlopen_tpm2(void) {
                         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));
@@ -249,7 +260,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) {
                 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);
@@ -334,55 +345,200 @@ static int tpm2_credit_random(Tpm2Context *c) {
         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
@@ -395,7 +551,42 @@ static int tpm2_make_primary(
         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,
@@ -403,7 +594,7 @@ static int tpm2_make_primary(
                                 ESYS_TR_NONE,
                                 ESYS_TR_NONE,
                                 &primary_sensitive,
-                                &primary_template_ecc,
+                                primary_template,
                                 NULL,
                                 &creation_pcr,
                                 &primary->esys_handle,
@@ -425,6 +616,8 @@ static int tpm2_make_primary(
         }
 
         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,
@@ -432,7 +625,7 @@ static int tpm2_make_primary(
                                 ESYS_TR_NONE,
                                 ESYS_TR_NONE,
                                 &primary_sensitive,
-                                &primary_template_rsa,
+                                primary_template,
                                 NULL,
                                 &creation_pcr,
                                 &primary->esys_handle,
@@ -452,7 +645,17 @@ static int tpm2_make_primary(
                 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);
@@ -1752,10 +1955,13 @@ int tpm2_seal(const char *device,
               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;
@@ -1764,6 +1970,7 @@ int tpm2_seal(const char *device,
         TPM2B_PUBLIC hmac_template;
         usec_t start;
         TSS2_RC rc;
+        size_t srk_buf_size;
         int r;
 
         assert(pubkey || pubkey_size == 0);
@@ -1805,7 +2012,7 @@ int tpm2_seal(const char *device,
                 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;
 
@@ -1914,9 +2121,35 @@ int tpm2_seal(const char *device,
         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);
@@ -1944,6 +2177,8 @@ int tpm2_unseal(const char *device,
                 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) {
 
@@ -1999,18 +2234,39 @@ int tpm2_unseal(const char *device,
         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);
@@ -2439,6 +2695,8 @@ int tpm2_make_luks2_json(
                 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) {
 
@@ -2479,7 +2737,8 @@ int tpm2_make_luks2_json(
                                        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;
 
@@ -2504,10 +2763,12 @@ int tpm2_parse_luks2_json(
                 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 */
@@ -2614,6 +2875,13 @@ int tpm2_parse_luks2_json(
         } 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)
@@ -2642,6 +2910,10 @@ int tpm2_parse_luks2_json(
                 *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;
 }
index c2532c61c23a9916fd099d49b3694369de484fef..6a3aea851963e39b4ca198a5dfafd255cbf53ba6 100644 (file)
@@ -13,6 +13,12 @@ typedef enum TPM2Flags {
 } 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
@@ -65,8 +71,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
 
 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;
@@ -85,6 +91,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref);
 typedef struct {
         Tpm2Context *tpm2_context;
         ESYS_TR esys_handle;
+        bool keep;
 } Tpm2Handle;
 
 #define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), }
@@ -124,6 +131,8 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l);
 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;
@@ -135,8 +144,8 @@ int tpm2_find_device_auto(int log_level, char **ret);
 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)
index f159adb8cc07330e6c655d524e3a0d3b9e8196ff..ede953712cb7919a24f6d47ad47ae4d4cf3ded0f 100644 (file)
@@ -1,3 +1,7 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
 systemd_sysext_sources = files('sysext.c')
+
+meson.add_install_script(meson_make_symlink,
+                         rootbindir / 'systemd-sysext',
+                         rootbindir / 'systemd-confext')
index 539ab5e639db31e141a58ff23bfa539fcdadd07f..d235073743985f5f07a7dda0338305797bd81fbd 100644 (file)
@@ -15,7 +15,7 @@
 #include "dissect-image.h"
 #include "env-util.h"
 #include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
 #include "user-util.h"
 #include "verbs.h"
 
-static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default */
+static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
 static char *arg_root = NULL;
 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
 static bool arg_force = false;
 
+/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
+static ImageClass arg_image_class = IMAGE_SYSEXT;
+
 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+        const char *dot_directory_name;
+        const char *directory_name;
+        const char *short_identifier;
+        const char *short_identifier_plural;
+        const char *level_env;
+        const char *scope_env;
+        const char *name_env;
+} image_class_info[_IMAGE_CLASS_MAX] = {
+        [IMAGE_SYSEXT] = {
+                .dot_directory_name = ".systemd-sysext",
+                .directory_name = "systemd-sysext",
+                .short_identifier = "sysext",
+                .short_identifier_plural = "extensions",
+                .level_env = "SYSEXT_LEVEL",
+                .scope_env = "SYSEXT_SCOPE",
+                .name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
+        },
+        [IMAGE_CONFEXT] = {
+                .dot_directory_name = ".systemd-confext",
+                .directory_name = "systemd-confext",
+                .short_identifier = "confext",
+                .short_identifier_plural = "confexts",
+                .level_env = "CONFEXT_LEVEL",
+                .scope_env = "CONFEXT_SCOPE",
+                .name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
+        }
+};
+
 static int is_our_mount_point(const char *p) {
         _cleanup_free_ char *buf = NULL, *f = NULL;
         struct stat st;
@@ -70,26 +103,26 @@ static int is_our_mount_point(const char *p) {
         /* 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);
@@ -205,7 +238,7 @@ static int verb_status(int argc, char **argv, void *userdata) {
                         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();
 
@@ -272,7 +305,7 @@ static int mount_overlayfs(
         }
 
         /* Now mount the actual overlayfs */
-        r = mount_nofollow_verbose(LOG_ERR, "sysext", where, "overlay", MS_RDONLY, options);
+        r = mount_nofollow_verbose(LOG_ERR, image_class_info[arg_image_class].short_identifier, where, "overlay", MS_RDONLY, options);
         if (r < 0)
                 return r;
 
@@ -315,7 +348,7 @@ static int merge_hierarchy(
         /* 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();
 
@@ -383,12 +416,12 @@ static int merge_hierarchy(
         /* 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();
 
@@ -408,50 +441,9 @@ static int strverscmp_improvedp(char *const* a, char *const* b) {
         return strverscmp_improved(*a, *b);
 }
 
-static int validate_version(
-                const char *root,
-                const Image *img,
-                const char *host_os_release_id,
-                const char *host_os_release_version_id,
-                const char *host_os_release_sysext_level) {
-
-        int r;
-
-        assert(root);
-        assert(img);
-
-        if (arg_force) {
-                log_debug("Force mode enabled, skipping version validation.");
-                return 1;
-        }
-
-        /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
-         * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
-         * merged.) */
-        r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
-        if (r < 0) {
-                if (r != -ENOENT)
-                        return log_error_errno(r, "Failed to determine whether /usr/lib/os-release exists in the extension image: %m");
-        } else
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Extension image contains /usr/lib/os-release file, which is not allowed (it may carry /etc/os-release), refusing.");
-
-        r = extension_release_validate(
-                        img->name,
-                        host_os_release_id,
-                        host_os_release_version_id,
-                        host_os_release_sysext_level,
-                        in_initrd() ? "initrd" : "system",
-                        img->extension_release);
-        if (r < 0)
-                return log_error_errno(r, "Failed to validate extension release information: %m");
-
-        return r;
-}
-
 static int merge_subprocess(Hashmap *images, const char *workspace) {
         _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL,
-                *buf = NULL;
+            *host_os_release_confext_level = NULL, *buf = NULL;
         _cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
         size_t n_extensions = 0;
         unsigned n_ignored = 0;
@@ -478,11 +470,13 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                 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))
@@ -494,7 +488,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         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();
 
@@ -505,6 +499,17 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                 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;
@@ -537,6 +542,9 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         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,
@@ -576,8 +584,12 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                                         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)
@@ -588,17 +600,23 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         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. */
@@ -612,7 +630,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         /* 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;
@@ -637,7 +655,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
 
                 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();
 
@@ -713,7 +731,7 @@ static int merge(Hashmap *images) {
         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) {
@@ -729,7 +747,7 @@ static int merge(Hashmap *images) {
                 _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;
 
@@ -747,9 +765,9 @@ static int image_discover_and_read_metadata(Hashmap **ret_images) {
         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);
@@ -850,9 +868,9 @@ static int verb_list(int argc, char **argv, void *userdata) {
         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.");
@@ -888,11 +906,11 @@ static int verb_help(int argc, char **argv, void *userdata) {
                 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"
@@ -1004,6 +1022,7 @@ static int sysext_main(int argc, char *argv[]) {
 
 static int run(int argc, char *argv[]) {
         int r;
+        arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
 
         log_setup();
 
@@ -1014,9 +1033,9 @@ static int run(int argc, char *argv[]) {
         /* 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);
 }
index 1581642275b71b3d9cdf1c89e3eba751943a9fd0..25c9ab3358c981a452cb2f7e2d880c2e4c6e88f7 100644 (file)
@@ -62,37 +62,40 @@ enum {
 /* 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
index e93851790e09f310c5efcdafc352eac8a18da388..d20c911e2b453430cb6a662be43c05939d7e1312 100644 (file)
@@ -53,6 +53,7 @@ simple_tests += files(
         'test-cgroup-setup.c',
         'test-cgroup-util.c',
         'test-cgroup.c',
+        'test-chase.c',
         'test-clock.c',
         'test-compare-operator.c',
         'test-condition.c',
@@ -263,7 +264,7 @@ tests += [
                 'base' : test_core_base,
         },
         {
-                'sources' : files('test-chase.c'),
+                'sources' : files('test-chase-manual.c'),
                 'type' : 'manual',
         },
         {
diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c
new file mode 100644 (file)
index 0000000..d65fbe1
--- /dev/null
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <getopt.h>
+
+#include "chase.h"
+#include "fd-util.h"
+#include "log.h"
+#include "main-func.h"
+
+static char *arg_root = NULL;
+static int arg_flags = 0;
+static bool arg_open = false;
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_ROOT = 0x1000,
+                ARG_OPEN,
+        };
+
+        static const struct option options[] = {
+                { "help",                no_argument,       NULL, 'h'                     },
+                { "root",                required_argument, NULL, ARG_ROOT                },
+                { "open",                no_argument,       NULL, ARG_OPEN                },
+
+                { "prefix-root",         no_argument,       NULL, CHASE_PREFIX_ROOT       },
+                { "nonexistent",         no_argument,       NULL, CHASE_NONEXISTENT       },
+                { "no_autofs",           no_argument,       NULL, CHASE_NO_AUTOFS         },
+                { "safe",                no_argument,       NULL, CHASE_SAFE              },
+                { "trail-slash",         no_argument,       NULL, CHASE_TRAIL_SLASH       },
+                { "step",                no_argument,       NULL, CHASE_STEP              },
+                { "nofollow",            no_argument,       NULL, CHASE_NOFOLLOW          },
+                { "warn",                no_argument,       NULL, CHASE_WARN              },
+                {}
+        };
+
+        int c;
+
+        assert_se(argc >= 0);
+        assert_se(argv);
+
+        while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
+                switch (c) {
+
+                case 'h':
+                        printf("Syntax:\n"
+                               "  %s [OPTION...] path...\n"
+                               "Options:\n"
+                               , argv[0]);
+                        for (size_t i = 0; i < ELEMENTSOF(options) - 1; i++)
+                                printf("  --%s\n", options[i].name);
+                        return 0;
+
+                case ARG_ROOT:
+                        arg_root = optarg;
+                        break;
+
+                case ARG_OPEN:
+                        arg_open = true;
+                        break;
+
+                case CHASE_PREFIX_ROOT:
+                case CHASE_NONEXISTENT:
+                case CHASE_NO_AUTOFS:
+                case CHASE_SAFE:
+                case CHASE_TRAIL_SLASH:
+                case CHASE_STEP:
+                case CHASE_NOFOLLOW:
+                case CHASE_WARN:
+                        arg_flags |= c;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached();
+                }
+
+        if (optind == argc)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required.");
+
+        return 1;
+}
+
+static int run(int argc, char **argv) {
+        int r;
+
+        log_setup();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        for (int i = optind; i < argc; i++) {
+                _cleanup_free_ char *p = NULL;
+                _cleanup_close_ int fd = -EBADF;
+
+                printf("%s ", argv[i]);
+                fflush(stdout);
+
+                r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
+                if (r < 0)
+                        log_error_errno(r, "failed: %m");
+                else {
+                        log_info("→ %s", p);
+                        if (arg_open)
+                                assert_se(fd >= 0);
+                        else
+                                assert_se(fd == -EBADF);
+                }
+        }
+
+        return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
index d65fbe1d744597da30bb221d9a0e413bbe84761b..dee781929d9ada5e50e4102fa0ffc60c1689a249 100644 (file)
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <getopt.h>
 
+#include <unistd.h>
+
+#include "alloc-util.h"
 #include "chase.h"
+#include "dirent-util.h"
 #include "fd-util.h"
-#include "log.h"
-#include "main-func.h"
+#include "fs-util.h"
+#include "id128-util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "string-util.h"
+#include "tests.h"
+#include "tmpfile-util.h"
 
-static char *arg_root = NULL;
-static int arg_flags = 0;
-static bool arg_open = false;
+static const char *arg_test_dir = NULL;
 
-static int parse_argv(int argc, char *argv[]) {
-        enum {
-                ARG_ROOT = 0x1000,
-                ARG_OPEN,
-        };
+TEST(chase) {
+        _cleanup_free_ char *result = NULL, *pwd = NULL;
+        _cleanup_close_ int pfd = -EBADF;
+        char *temp;
+        const char *top, *p, *pslash, *q, *qslash;
+        struct stat st;
+        int r;
+
+        temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
+        assert_se(mkdtemp(temp));
+
+        top = strjoina(temp, "/top");
+        assert_se(mkdir(top, 0700) >= 0);
 
-        static const struct option options[] = {
-                { "help",                no_argument,       NULL, 'h'                     },
-                { "root",                required_argument, NULL, ARG_ROOT                },
-                { "open",                no_argument,       NULL, ARG_OPEN                },
-
-                { "prefix-root",         no_argument,       NULL, CHASE_PREFIX_ROOT       },
-                { "nonexistent",         no_argument,       NULL, CHASE_NONEXISTENT       },
-                { "no_autofs",           no_argument,       NULL, CHASE_NO_AUTOFS         },
-                { "safe",                no_argument,       NULL, CHASE_SAFE              },
-                { "trail-slash",         no_argument,       NULL, CHASE_TRAIL_SLASH       },
-                { "step",                no_argument,       NULL, CHASE_STEP              },
-                { "nofollow",            no_argument,       NULL, CHASE_NOFOLLOW          },
-                { "warn",                no_argument,       NULL, CHASE_WARN              },
-                {}
+        p = strjoina(top, "/dot");
+        if (symlink(".", p) < 0) {
+                assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
+                log_tests_skipped_errno(errno, "symlink() not possible");
+                goto cleanup;
         };
 
-        int c;
-
-        assert_se(argc >= 0);
-        assert_se(argv);
-
-        while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
-                switch (c) {
-
-                case 'h':
-                        printf("Syntax:\n"
-                               "  %s [OPTION...] path...\n"
-                               "Options:\n"
-                               , argv[0]);
-                        for (size_t i = 0; i < ELEMENTSOF(options) - 1; i++)
-                                printf("  --%s\n", options[i].name);
-                        return 0;
-
-                case ARG_ROOT:
-                        arg_root = optarg;
-                        break;
-
-                case ARG_OPEN:
-                        arg_open = true;
-                        break;
-
-                case CHASE_PREFIX_ROOT:
-                case CHASE_NONEXISTENT:
-                case CHASE_NO_AUTOFS:
-                case CHASE_SAFE:
-                case CHASE_TRAIL_SLASH:
-                case CHASE_STEP:
-                case CHASE_NOFOLLOW:
-                case CHASE_WARN:
-                        arg_flags |= c;
-                        break;
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        assert_not_reached();
-                }
-
-        if (optind == argc)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required.");
-
-        return 1;
-}
+        p = strjoina(top, "/dotdot");
+        assert_se(symlink("..", p) >= 0);
 
-static int run(int argc, char **argv) {
-        int r;
+        p = strjoina(top, "/dotdota");
+        assert_se(symlink("../a", p) >= 0);
+
+        p = strjoina(temp, "/a");
+        assert_se(symlink("b", p) >= 0);
+
+        p = strjoina(temp, "/b");
+        assert_se(symlink("/usr", p) >= 0);
+
+        p = strjoina(temp, "/start");
+        assert_se(symlink("top/dot/dotdota", p) >= 0);
+
+        /* Paths that use symlinks underneath the "root" */
+
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/usr"));
+        result = mfree(result);
+
+        pslash = strjoina(p, "/");
+        r = chase(pslash, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/usr/"));
+        result = mfree(result);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(pslash, temp, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        q = strjoina(temp, "/usr");
+
+        r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, q));
+        result = mfree(result);
+
+        qslash = strjoina(q, "/");
+
+        r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, qslash));
+        result = mfree(result);
+
+        assert_se(mkdir(q, 0700) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, q));
+        result = mfree(result);
+
+        r = chase(pslash, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, qslash));
+        result = mfree(result);
+
+        p = strjoina(temp, "/slash");
+        assert_se(symlink("/", p) >= 0);
+
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/"));
+        result = mfree(result);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, temp));
+        result = mfree(result);
+
+        /* Paths that would "escape" outside of the "root" */
+
+        p = strjoina(temp, "/6dots");
+        assert_se(symlink("../../..", p) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, temp));
+        result = mfree(result);
+
+        p = strjoina(temp, "/6dotsusr");
+        assert_se(symlink("../../../usr", p) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, q));
+        result = mfree(result);
+
+        p = strjoina(temp, "/top/8dotsusr");
+        assert_se(symlink("../../../../usr", p) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, q));
+        result = mfree(result);
+
+        /* Paths that contain repeated slashes */
+
+        p = strjoina(temp, "/slashslash");
+        assert_se(symlink("///usr///", p) >= 0);
+
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/usr"));
+        assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
+        result = mfree(result);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, q));
+        result = mfree(result);
+
+        /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
+
+        if (geteuid() == 0) {
+                p = strjoina(temp, "/user");
+                assert_se(mkdir(p, 0755) >= 0);
+                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
+
+                q = strjoina(temp, "/user/root");
+                assert_se(mkdir(q, 0755) >= 0);
+
+                p = strjoina(q, "/link");
+                assert_se(symlink("/", p) >= 0);
+
+                /* Fail when user-owned directories contain root-owned subdirectories. */
+                r = chase(p, temp, CHASE_SAFE, &result, NULL);
+                assert_se(r == -ENOLINK);
+                result = mfree(result);
+
+                /* Allow this when the user-owned directories are all in the "root". */
+                r = chase(p, q, CHASE_SAFE, &result, NULL);
+                assert_se(r > 0);
+                result = mfree(result);
+        }
+
+        /* Paths using . */
+
+        r = chase("/etc/./.././", NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/"));
+        result = mfree(result);
+
+        r = chase("/etc/./.././", "/etc", 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, "/etc"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../etc", NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(streq(result, "/etc"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(streq(result, "/test-chase.fsldajfl"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
+        assert_se(r > 0);
+        assert_se(streq(result, "/etc"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(streq(result, "/test-chase.fsldajfl"));
+        result = mfree(result);
+
+        r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL);
+        assert_se(IN_SET(r, -ENOTDIR, -ENOENT));
+        result = mfree(result);
+
+        /* Path that loops back to self */
+
+        p = strjoina(temp, "/recursive-symlink");
+        assert_se(symlink("recursive-symlink", p) >= 0);
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ELOOP);
+
+        /* Path which doesn't exist */
+
+        p = strjoina(temp, "/idontexist");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        p = strjoina(temp, "/idontexist/meneither");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        /* Relative paths */
+
+        assert_se(safe_getcwd(&pwd) >= 0);
+
+        assert_se(chdir(temp) >= 0);
+
+        p = "this/is/a/relative/path";
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+
+        p = strjoina(temp, "/", p);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        p = "this/is/a/relative/path";
+        r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+
+        p = strjoina(temp, "/", p);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        assert_se(chdir(pwd) >= 0);
+
+        /* Path which doesn't exist, but contains weird stuff */
+
+        p = strjoina(temp, "/idontexist/..");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        p = strjoina(temp, "/target");
+        q = strjoina(temp, "/top");
+        assert_se(symlink(q, p) >= 0);
+        p = strjoina(temp, "/target/idontexist");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        if (geteuid() == 0) {
+                p = strjoina(temp, "/priv1");
+                assert_se(mkdir(p, 0755) >= 0);
 
-        log_setup();
+                q = strjoina(p, "/priv2");
+                assert_se(mkdir(q, 0755) >= 0);
 
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                return r;
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
 
-        for (int i = optind; i < argc; i++) {
-                _cleanup_free_ char *p = NULL;
+                assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+                assert_se(chown(q, 0, 0) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
+
+                assert_se(rmdir(q) >= 0);
+                assert_se(symlink("/etc/passwd", q) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
+
+                assert_se(chown(p, 0, 0) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+        }
+
+        p = strjoina(temp, "/machine-id-test");
+        assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
+
+        r = chase(p, NULL, 0, NULL, &pfd);
+        if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) {
                 _cleanup_close_ int fd = -EBADF;
+                sd_id128_t a, b;
+
+                assert_se(pfd >= 0);
+
+                fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
+                assert_se(fd >= 0);
+                safe_close(pfd);
 
-                printf("%s ", argv[i]);
-                fflush(stdout);
-
-                r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
-                if (r < 0)
-                        log_error_errno(r, "failed: %m");
-                else {
-                        log_info("→ %s", p);
-                        if (arg_open)
-                                assert_se(fd >= 0);
-                        else
-                                assert_se(fd == -EBADF);
-                }
+                assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0);
+                assert_se(sd_id128_get_machine(&b) >= 0);
+                assert_se(sd_id128_equal(a, b));
         }
 
-        return 0;
+        assert_se(lstat(p, &st) >= 0);
+        r = chase_and_unlink(p, NULL, 0, 0, &result);
+        assert_se(r == 0);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+        assert_se(lstat(p, &st) == -1 && errno == ENOENT);
+
+        /* Test CHASE_NOFOLLOW */
+
+        p = strjoina(temp, "/target");
+        q = strjoina(temp, "/symlink");
+        assert_se(symlink(p, q) >= 0);
+        r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
+        assert_se(r >= 0);
+        assert_se(pfd >= 0);
+        assert_se(path_equal(result, q));
+        assert_se(fstat(pfd, &st) >= 0);
+        assert_se(S_ISLNK(st.st_mode));
+        result = mfree(result);
+        pfd = safe_close(pfd);
+
+        /* s1 -> s2 -> nonexistent */
+        q = strjoina(temp, "/s1");
+        assert_se(symlink("s2", q) >= 0);
+        p = strjoina(temp, "/s2");
+        assert_se(symlink("nonexistent", p) >= 0);
+        r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
+        assert_se(r >= 0);
+        assert_se(pfd >= 0);
+        assert_se(path_equal(result, q));
+        assert_se(fstat(pfd, &st) >= 0);
+        assert_se(S_ISLNK(st.st_mode));
+        result = mfree(result);
+        pfd = safe_close(pfd);
+
+        /* Test CHASE_STEP */
+
+        p = strjoina(temp, "/start");
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/dot/dotdota");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/dotdota");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/../a");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/a");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/b");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        assert_se(streq("/usr", result));
+        result = mfree(result);
+
+        r = chase("/usr", NULL, CHASE_STEP, &result, NULL);
+        assert_se(r > 0);
+        assert_se(streq("/usr", result));
+        result = mfree(result);
+
+        /* Make sure that symlinks in the "root" path are not resolved, but those below are */
+        p = strjoina("/etc/..", temp, "/self");
+        assert_se(symlink(".", p) >= 0);
+        q = strjoina(p, "/top/dot/dotdota");
+        r = chase(q, p, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(path_startswith(result, p), "usr"));
+        result = mfree(result);
+
+        /* Test CHASE_PROHIBIT_SYMLINKS */
+
+        assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+
+ cleanup:
+        assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+}
+
+TEST(chaseat) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+        _cleanup_free_ char *result = NULL;
+        _cleanup_closedir_ DIR *dir = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        struct stat st;
+        const char *p;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+        /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working
+         * directory. */
+
+        assert_se(symlinkat("/usr", tfd, "abc") >= 0);
+
+        p = strjoina(t, "/abc");
+        assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "/usr"));
+        result = mfree(result);
+
+        /* Test that absolute path or not are the same when resolving relative to a directory file
+         * descriptor and that we always get a relative path back. */
+
+        assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0);
+        fd = safe_close(fd);
+        assert_se(symlinkat("/def", tfd, "qed") >= 0);
+        assert_se(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "def"));
+        result = mfree(result);
+        assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "def"));
+        result = mfree(result);
+
+        /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
+         * host's root. */
+        assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
+
+        /* Test CHASE_PARENT */
+
+        assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
+        assert_se(symlinkat("/def", fd, "parent") >= 0);
+        fd = safe_close(fd);
+
+        /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
+         * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
+         * directory of the symlink itself. */
+
+        assert_se(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+        assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
+        assert_se(streq(result, "def"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
+        assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+        assert_se(streq(result, "chase/parent"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+        assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
+        assert_se(streq(result, "chase"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        /* Test CHASE_MKDIR_0755 */
+
+        assert_se(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
+        assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
+        assert_se(streq(result, "m/k/d/i/r"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
+        assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
+        assert_se(streq(result, "q"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT);
+
+        /* Test CHASE_FILENAME */
+
+        assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
+        assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+        assert_se(streq(result, "parent"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
+        assert_se(faccessat(fd, result, F_OK, 0) >= 0);
+        assert_se(streq(result, "chase"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        /* Test chase_and_openat() */
+
+        fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        fd = safe_close(fd);
+
+        fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_directory(fd) >= 0);
+        fd = safe_close(fd);
+
+        /* Test chase_and_openatdir() */
+
+        assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0);
+        FOREACH_DIRENT(de, dir, assert_not_reached())
+                assert_se(streq(de->d_name, "r"));
+        assert_se(streq(result, "o/p/e/n/d/i"));
+        result = mfree(result);
+
+        /* Test chase_and_statat() */
+
+        assert_se(chase_and_statat(tfd, "o/p", 0, &result, &st) >= 0);
+        assert_se(stat_verify_directory(&st) >= 0);
+        assert_se(streq(result, "o/p"));
+        result = mfree(result);
+
+        /* Test chase_and_accessat() */
+
+        assert_se(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result) >= 0);
+        assert_se(streq(result, "o/p/e"));
+        result = mfree(result);
+
+        /* Test chase_and_fopenat_unlocked() */
+
+        assert_se(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0);
+        assert_se(fread(&(char[1]) {}, 1, 1, f) == 0);
+        assert_se(feof(f));
+        f = safe_fclose(f);
+        assert_se(streq(result, "o/p/e/n/f/i/l/e"));
+        result = mfree(result);
+
+        /* Test chase_and_unlinkat() */
+
+        assert_se(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0);
+        assert_se(streq(result, "o/p/e/n/f/i/l/e"));
+        result = mfree(result);
+
+        /* Test chase_and_open_parent_at() */
+
+        assert_se((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)) >= 0);
+        assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+        assert_se(streq(result, "parent"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+        assert_se(faccessat(fd, result, F_OK, 0) >= 0);
+        assert_se(streq(result, "chase"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+        assert_se(streq(result, "."));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+        assert_se(streq(result, "."));
+        fd = safe_close(fd);
+        result = mfree(result);
+}
+
+static int intro(void) {
+        arg_test_dir = saved_argv[1];
+        return EXIT_SUCCESS;
 }
 
-DEFINE_MAIN_FUNCTION(run);
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
index 4aa991ac645d8032e0fba17b692874fa72a26520..3d8f46fc9670d8adcf51bd03b790ed6fc20313e6 100644 (file)
@@ -17,6 +17,7 @@ TEST(parse_compare_operator) {
         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) {
index 1f9d385ec0b8f5f8e0f1b3a5b2f2bf3e80d69d11..bcc98ae88ab14e3cd0eed57383ff85926266764c 100644 (file)
@@ -478,4 +478,13 @@ TEST(getenv_steal_erase) {
         assert_se(r > 0);
 }
 
+TEST(strv_env_name_is_valid) {
+        const char *valid_env_names[] = {"HOME", "USER", "SHELL", "PATH", NULL};
+        const char *invalid_env_names[] = {"", "PATH", "home", "user", "SHELL", NULL};
+        const char *repeated_env_names[] = {"HOME", "USER", "SHELL", "USER", NULL};
+        assert_se(strv_env_name_is_valid((char **) valid_env_names));
+        assert_se(!strv_env_name_is_valid((char **) invalid_env_names));
+        assert_se(!strv_env_name_is_valid((char **) repeated_env_names));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index bc2eed51354400d8ae814f88cdd151ea73405d69..c902f83784b321db4e1e63c352942a234556f2fe 100644 (file)
@@ -9,6 +9,7 @@
 #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"
@@ -596,13 +597,17 @@ TEST(dir_fd_is_root) {
         _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)
@@ -621,16 +626,135 @@ TEST(dir_fd_is_root) {
 
         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);
index ab84a5ba83c4b71aac3ef021f4a513c29d2715db..9e3fbdd28feb57590c7f2302c3e94ecf7ce99444 100644 (file)
@@ -3,19 +3,18 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase.h"
 #include "copy.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
-#include "id128-util.h"
 #include "macro.h"
 #include "mkdir.h"
 #include "path-util.h"
 #include "process-util.h"
 #include "random-util.h"
 #include "rm-rf.h"
+#include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
 
 static const char *arg_test_dir = NULL;
 
-TEST(chase) {
-        _cleanup_free_ char *result = NULL, *pwd = NULL;
-        _cleanup_close_ int pfd = -EBADF;
-        char *temp;
-        const char *top, *p, *pslash, *q, *qslash;
-        struct stat st;
-        int r;
-
-        temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
-        assert_se(mkdtemp(temp));
-
-        top = strjoina(temp, "/top");
-        assert_se(mkdir(top, 0700) >= 0);
-
-        p = strjoina(top, "/dot");
-        if (symlink(".", p) < 0) {
-                assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
-                log_tests_skipped_errno(errno, "symlink() not possible");
-                goto cleanup;
-        };
-
-        p = strjoina(top, "/dotdot");
-        assert_se(symlink("..", p) >= 0);
-
-        p = strjoina(top, "/dotdota");
-        assert_se(symlink("../a", p) >= 0);
-
-        p = strjoina(temp, "/a");
-        assert_se(symlink("b", p) >= 0);
-
-        p = strjoina(temp, "/b");
-        assert_se(symlink("/usr", p) >= 0);
-
-        p = strjoina(temp, "/start");
-        assert_se(symlink("top/dot/dotdota", p) >= 0);
-
-        /* Paths that use symlinks underneath the "root" */
-
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/usr"));
-        result = mfree(result);
-
-        pslash = strjoina(p, "/");
-        r = chase(pslash, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/usr/"));
-        result = mfree(result);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase(pslash, temp, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        q = strjoina(temp, "/usr");
-
-        r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, q));
-        result = mfree(result);
-
-        qslash = strjoina(q, "/");
-
-        r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, qslash));
-        result = mfree(result);
-
-        assert_se(mkdir(q, 0700) >= 0);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, q));
-        result = mfree(result);
-
-        r = chase(pslash, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, qslash));
-        result = mfree(result);
-
-        p = strjoina(temp, "/slash");
-        assert_se(symlink("/", p) >= 0);
-
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/"));
-        result = mfree(result);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, temp));
-        result = mfree(result);
-
-        /* Paths that would "escape" outside of the "root" */
-
-        p = strjoina(temp, "/6dots");
-        assert_se(symlink("../../..", p) >= 0);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, temp));
-        result = mfree(result);
-
-        p = strjoina(temp, "/6dotsusr");
-        assert_se(symlink("../../../usr", p) >= 0);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, q));
-        result = mfree(result);
-
-        p = strjoina(temp, "/top/8dotsusr");
-        assert_se(symlink("../../../../usr", p) >= 0);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, q));
-        result = mfree(result);
-
-        /* Paths that contain repeated slashes */
-
-        p = strjoina(temp, "/slashslash");
-        assert_se(symlink("///usr///", p) >= 0);
-
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/usr"));
-        assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
-        result = mfree(result);
-
-        r = chase(p, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, q));
-        result = mfree(result);
-
-        /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
-
-        if (geteuid() == 0) {
-                p = strjoina(temp, "/user");
-                assert_se(mkdir(p, 0755) >= 0);
-                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
-
-                q = strjoina(temp, "/user/root");
-                assert_se(mkdir(q, 0755) >= 0);
-
-                p = strjoina(q, "/link");
-                assert_se(symlink("/", p) >= 0);
-
-                /* Fail when user-owned directories contain root-owned subdirectories. */
-                r = chase(p, temp, CHASE_SAFE, &result, NULL);
-                assert_se(r == -ENOLINK);
-                result = mfree(result);
-
-                /* Allow this when the user-owned directories are all in the "root". */
-                r = chase(p, q, CHASE_SAFE, &result, NULL);
-                assert_se(r > 0);
-                result = mfree(result);
-        }
-
-        /* Paths using . */
-
-        r = chase("/etc/./.././", NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/"));
-        result = mfree(result);
-
-        r = chase("/etc/./.././", "/etc", 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, "/etc"));
-        result = mfree(result);
-
-        r = chase("/../.././//../../etc", NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(streq(result, "/etc"));
-        result = mfree(result);
-
-        r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(streq(result, "/test-chase.fsldajfl"));
-        result = mfree(result);
-
-        r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
-        assert_se(r > 0);
-        assert_se(streq(result, "/etc"));
-        result = mfree(result);
-
-        r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(streq(result, "/test-chase.fsldajfl"));
-        result = mfree(result);
-
-        r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL);
-        assert_se(IN_SET(r, -ENOTDIR, -ENOENT));
-        result = mfree(result);
-
-        /* Path that loops back to self */
-
-        p = strjoina(temp, "/recursive-symlink");
-        assert_se(symlink("recursive-symlink", p) >= 0);
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r == -ELOOP);
-
-        /* Path which doesn't exist */
-
-        p = strjoina(temp, "/idontexist");
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        p = strjoina(temp, "/idontexist/meneither");
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        /* Relative paths */
-
-        assert_se(safe_getcwd(&pwd) >= 0);
-
-        assert_se(chdir(temp) >= 0);
-
-        p = "this/is/a/relative/path";
-        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-
-        p = strjoina(temp, "/", p);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        p = "this/is/a/relative/path";
-        r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-
-        p = strjoina(temp, "/", p);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        assert_se(chdir(pwd) >= 0);
-
-        /* Path which doesn't exist, but contains weird stuff */
-
-        p = strjoina(temp, "/idontexist/..");
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        p = strjoina(temp, "/target");
-        q = strjoina(temp, "/top");
-        assert_se(symlink(q, p) >= 0);
-        p = strjoina(temp, "/target/idontexist");
-        r = chase(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        if (geteuid() == 0) {
-                p = strjoina(temp, "/priv1");
-                assert_se(mkdir(p, 0755) >= 0);
-
-                q = strjoina(p, "/priv2");
-                assert_se(mkdir(q, 0755) >= 0);
-
-                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
-                assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
-                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
-                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
-                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
-                assert_se(chown(q, 0, 0) >= 0);
-                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
-                assert_se(rmdir(q) >= 0);
-                assert_se(symlink("/etc/passwd", q) >= 0);
-                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
-                assert_se(chown(p, 0, 0) >= 0);
-                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-        }
-
-        p = strjoina(temp, "/machine-id-test");
-        assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
-
-        r = chase(p, NULL, 0, NULL, &pfd);
-        if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) {
-                _cleanup_close_ int fd = -EBADF;
-                sd_id128_t a, b;
-
-                assert_se(pfd >= 0);
-
-                fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
-                assert_se(fd >= 0);
-                safe_close(pfd);
-
-                assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0);
-                assert_se(sd_id128_get_machine(&b) >= 0);
-                assert_se(sd_id128_equal(a, b));
-        }
-
-        assert_se(lstat(p, &st) >= 0);
-        r = chase_and_unlink(p, NULL, 0, 0, &result);
-        assert_se(r == 0);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-        assert_se(lstat(p, &st) == -1 && errno == ENOENT);
-
-        /* Test CHASE_NOFOLLOW */
-
-        p = strjoina(temp, "/target");
-        q = strjoina(temp, "/symlink");
-        assert_se(symlink(p, q) >= 0);
-        r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
-        assert_se(r >= 0);
-        assert_se(pfd >= 0);
-        assert_se(path_equal(result, q));
-        assert_se(fstat(pfd, &st) >= 0);
-        assert_se(S_ISLNK(st.st_mode));
-        result = mfree(result);
-        pfd = safe_close(pfd);
-
-        /* s1 -> s2 -> nonexistent */
-        q = strjoina(temp, "/s1");
-        assert_se(symlink("s2", q) >= 0);
-        p = strjoina(temp, "/s2");
-        assert_se(symlink("nonexistent", p) >= 0);
-        r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
-        assert_se(r >= 0);
-        assert_se(pfd >= 0);
-        assert_se(path_equal(result, q));
-        assert_se(fstat(pfd, &st) >= 0);
-        assert_se(S_ISLNK(st.st_mode));
-        result = mfree(result);
-        pfd = safe_close(pfd);
-
-        /* Test CHASE_STEP */
-
-        p = strjoina(temp, "/start");
-        r = chase(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/top/dot/dotdota");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/top/dotdota");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/top/../a");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/a");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/b");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        assert_se(streq("/usr", result));
-        result = mfree(result);
-
-        r = chase("/usr", NULL, CHASE_STEP, &result, NULL);
-        assert_se(r > 0);
-        assert_se(streq("/usr", result));
-        result = mfree(result);
-
-        /* Make sure that symlinks in the "root" path are not resolved, but those below are */
-        p = strjoina("/etc/..", temp, "/self");
-        assert_se(symlink(".", p) >= 0);
-        q = strjoina(p, "/top/dot/dotdota");
-        r = chase(q, p, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(path_startswith(result, p), "usr"));
-        result = mfree(result);
-
-        /* Test CHASE_PROHIBIT_SYMLINKS */
-
-        assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
-        assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-        assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
-        assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-        assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
-        assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-
- cleanup:
-        assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
-}
-
-TEST(chaseat) {
-        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
-        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
-        _cleanup_free_ char *result = NULL;
-        _cleanup_closedir_ DIR *dir = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        struct stat st;
-        const char *p;
-
-        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
-
-        /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working
-         * directory. */
-
-        assert_se(symlinkat("/usr", tfd, "abc") >= 0);
-
-        p = strjoina(t, "/abc");
-        assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "/usr"));
-        result = mfree(result);
-
-        /* Test that absolute path or not are the same when resolving relative to a directory file
-         * descriptor and that we always get a relative path back. */
-
-        assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0);
-        fd = safe_close(fd);
-        assert_se(symlinkat("/def", tfd, "qed") >= 0);
-        assert_se(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "def"));
-        result = mfree(result);
-        assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "def"));
-        result = mfree(result);
-
-        /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
-         * host's root. */
-        assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
-
-        /* Test CHASE_PARENT */
-
-        assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
-        assert_se(symlinkat("/def", fd, "parent") >= 0);
-        fd = safe_close(fd);
-
-        /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
-         * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
-         * directory of the symlink itself. */
-
-        assert_se(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
-        assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
-        assert_se(streq(result, "def"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
-        assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
-        assert_se(streq(result, "chase/parent"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
-        assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
-        assert_se(streq(result, "chase"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "."));
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "."));
-        result = mfree(result);
-
-        /* Test CHASE_MKDIR_0755 */
-
-        assert_se(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
-        assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
-        assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
-        assert_se(streq(result, "m/k/d/i/r"));
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
-        assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
-        assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
-        assert_se(streq(result, "q"));
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT);
-
-        /* Test CHASE_FILENAME */
-
-        assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
-        assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
-        assert_se(streq(result, "parent"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
-        assert_se(faccessat(fd, result, F_OK, 0) >= 0);
-        assert_se(streq(result, "chase"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
-        assert_se(streq(result, "."));
-        result = mfree(result);
-
-        assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
-        assert_se(streq(result, "."));
-        result = mfree(result);
-
-        /* Test chase_and_openat() */
-
-        fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
-        assert_se(fd >= 0);
-        assert_se(fd_verify_regular(fd) >= 0);
-        fd = safe_close(fd);
-
-        fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
-        assert_se(fd >= 0);
-        assert_se(fd_verify_directory(fd) >= 0);
-        fd = safe_close(fd);
-
-        /* Test chase_and_openatdir() */
-
-        assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0);
-        FOREACH_DIRENT(de, dir, assert_not_reached())
-                assert_se(streq(de->d_name, "r"));
-        assert_se(streq(result, "o/p/e/n/d/i"));
-        result = mfree(result);
-
-        /* Test chase_and_statat() */
-
-        assert_se(chase_and_statat(tfd, "o/p", 0, &result, &st) >= 0);
-        assert_se(stat_verify_directory(&st) >= 0);
-        assert_se(streq(result, "o/p"));
-        result = mfree(result);
-
-        /* Test chase_and_accessat() */
-
-        assert_se(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result) >= 0);
-        assert_se(streq(result, "o/p/e"));
-        result = mfree(result);
-
-        /* Test chase_and_fopenat_unlocked() */
-
-        assert_se(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0);
-        assert_se(fread(&(char[1]) {}, 1, 1, f) == 0);
-        assert_se(feof(f));
-        f = safe_fclose(f);
-        assert_se(streq(result, "o/p/e/n/f/i/l/e"));
-        result = mfree(result);
-
-        /* Test chase_and_unlinkat() */
-
-        assert_se(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0);
-        assert_se(streq(result, "o/p/e/n/f/i/l/e"));
-        result = mfree(result);
-
-        /* Test chase_and_open_parent_at() */
-
-        assert_se((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)) >= 0);
-        assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
-        assert_se(streq(result, "parent"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
-        assert_se(faccessat(fd, result, F_OK, 0) >= 0);
-        assert_se(streq(result, "chase"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
-        assert_se(streq(result, "."));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
-        assert_se(streq(result, "."));
-        fd = safe_close(fd);
-        result = mfree(result);
-}
-
 TEST(readlink_and_make_absolute) {
         const char *tempdir, *name, *name2, *name_alias;
         _cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL;
index 0a97366b6daa37f930b60dd61c41116d8b3c3948..80f762bc8966a28528d57f7a3486b8af04cf7884 100644 (file)
@@ -11,6 +11,8 @@
 #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"
@@ -215,4 +217,106 @@ TEST(benchmark_sd_id128_get_machine_app_specific) {
         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);
index 4fc4bc90874e8c062b1cfc9a3aaaee8f065b857c..e337a3c7df397395f5be9630e463ebeb5405face 100644 (file)
@@ -175,6 +175,32 @@ static void test_log_context(void) {
         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();
 
@@ -188,6 +214,7 @@ int main(int argc, char* argv[]) {
                 test_long_lines();
                 test_log_syntax();
                 test_log_context();
+                test_log_prefix();
         }
 
         return 0;
index defecd3a51182ce83919eabddf04984d50fdac30..2fbcde41d6b489e038c967bee4d7e4ce80bda54b 100644 (file)
@@ -367,7 +367,9 @@ static int make_addresses(struct local_address **addresses) {
                                               .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,
index bc9e3ec91bc0f2aab0ecd6049b5b2de64a2cc558..94680389fd2148e4a058b732cf5451317734cffb 100644 (file)
@@ -1,13 +1,17 @@
 /* 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);
@@ -55,6 +59,48 @@ TEST(parse_os_release) {
         assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
 }
 
+TEST(parse_extension_release) {
+        /* Let's assume that we have a valid extension image */
+        _cleanup_free_ char *id = NULL, *version_id = NULL, *foobar = NULL, *a = NULL, *b = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
+
+        int r = mkdtemp_malloc("/tmp/test-os-util.XXXXXX", &tempdir);
+        if (r < 0)
+                log_error_errno(r, "Failed to setup working directory: %m");
+
+        assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test"));
+        assert_se(mkdir_parents(a, 0777) >= 0);
+
+        r = write_string_file(a, "ID=the-id  \n VERSION_ID=the-version-id", WRITE_STRING_FILE_CREATE);
+        if (r < 0)
+                log_error_errno(r, "Failed to write file: %m");
+
+        assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, false, "test", "ID", &id, "VERSION_ID", &version_id) == 0);
+        log_info("ID: %s VERSION_ID: %s", id, version_id);
+        assert_se(streq(id, "the-id"));
+        assert_se(streq(version_id, "the-version-id"));
+
+        assert_se(b = path_join(tempdir, "/etc/extension-release.d/extension-release.tester"));
+        assert_se(mkdir_parents(b, 0777) >= 0);
+
+        r = write_string_file(b, "ID=\"ignored\" \n ID=\"the-id\" \n VERSION_ID='the-version-id'", WRITE_STRING_FILE_CREATE);
+        if (r < 0)
+                log_error_errno(r, "Failed to write file: %m");
+
+        assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, false, "tester", "ID", &id, "VERSION_ID", &version_id) == 0);
+        log_info("ID: %s VERSION_ID: %s", id, version_id);
+        assert_se(streq(id, "the-id"));
+        assert_se(streq(version_id, "the-version-id"));
+
+        assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, false, "tester", "FOOBAR", &foobar) == 0);
+        log_info("FOOBAR: %s", strnull(foobar));
+        assert_se(foobar == NULL);
+
+        assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, false, "test", "FOOBAR", &foobar) == 0);
+        log_info("FOOBAR: %s", strnull(foobar));
+        assert_se(foobar == NULL);
+}
+
 TEST(load_os_release_pairs) {
         _cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
         assert_se(write_tmpfile(tmpfile,
index 1eb9a49077d261df6f8fd08db609058ee0426edc..6a8b7d823fb3bd11f2b9f1eadd78e66f31cddad0 100644 (file)
 #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);
 }
 
index 62986e8dc2e3186afdea94ca4ed2492c64230682..6d4093ec05d618db6abc15be35ff9f93a5e080bc 100644 (file)
@@ -136,7 +136,7 @@ TEST(specifiers) {
                 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);
 
index 20baa0f26134cc62734ff8db7d5b3710318ea11b..710ce51cd0cea3825683d2f0a45c63a3c589f417 100644 (file)
@@ -409,6 +409,96 @@ TEST(tpml_pcr_selection_add_sub) {
                           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);
index c3741088c34aed46173bd31dc71ee79aeb1b1702..6cd76e8df844af9503b87d8977ef83fa9fd17603 100644 (file)
@@ -754,6 +754,8 @@ static int dir_cleanup(
                                         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) {
@@ -794,6 +796,14 @@ static int dir_cleanup(
                                            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)
index f7a1bb3bb00d6aa431ff0a405ce6fb986b62c3ae..19bbf295c51acded9b10e045d23caf561723b742 100644 (file)
@@ -1168,6 +1168,20 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
         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);
 
@@ -1236,6 +1250,9 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned
                 return 0;
         }
 
+        if (extra_checks)
+                check_tokens_order(rule_line);
+
         sort_tokens(rule_line);
         TAKE_PTR(rule_line);
         return 0;
@@ -1436,8 +1453,12 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che
         assert(filename);
 
         f = fopen(filename, "re");
-        if (!f)
+        if (!f) {
+                if (!extra_checks && errno == ENOENT)
+                        return 0;
+
                 return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
+        }
 
         if (fstat(fileno(f), &st) < 0)
                 return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
index 6d8a5c336f5b58e15974c4686cea49062ebcf1a6..b9f18d1552c50a95545d0eff0e9c07bd494e0769 100644 (file)
@@ -337,11 +337,9 @@ int lock_main(int argc, char *argv[], void *userdata) {
                         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);
                 }
         }
 
index b847f2ed872a55870bb474e53215a2817a640ff0..b319f2dc913509b5150cea70d0d58af4fd38062c 100755 (executable)
@@ -450,6 +450,19 @@ class PeError(Exception):
 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
index 6b7493fd8853a18a5f4ce6cb20084c5ed8e9ffbd..37867ee3ed1d8f975899d1e90f75b40071ef7cba 100644 (file)
@@ -15,7 +15,7 @@
 #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),
@@ -29,14 +29,11 @@ static int run(int argc, char *argv[]) {
         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]);
index 49cf6845969c49717925074f0304e78a23f2b6d2..9070d0c60c9caba2d0d6abe1d2ab33ddf8cbc165 100644 (file)
@@ -262,3 +262,39 @@ More about query suites here: https://codeql.github.com/docs/codeql-cli/creating
 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
index 80ce7926ab14c1f76b83be151642f6163a2aca35..61e8e7d1c42887844b9361f1a89478dd14c4642f 100755 (executable)
@@ -3,8 +3,6 @@
 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"
index 95c8581238f3d6ccf5a0b93fe3609b50996765ab..4f9c896492e1fbf180a8930ad0665dc3f6571ba4 100755 (executable)
@@ -3,8 +3,6 @@
 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"
index b4d2452b7504f804d85a47f356df48a9021df0ed..8ec5b1bc5f00ac1e0aa20afbd032f0a510ce0fed 100755 (executable)
@@ -3,7 +3,6 @@
 set -e
 
 TEST_DESCRIPTION="test NotifyAccess through sd-notify"
-TEST_NO_QEMU=1
 
 # shellcheck source=test/test-functions
 . "${TEST_BASE_DIR:?}/test-functions"
diff --git a/test/TEST-81-GENERATORS/Makefile b/test/TEST-81-GENERATORS/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile
\ No newline at end of file
diff --git a/test/TEST-81-GENERATORS/test.sh b/test/TEST-81-GENERATORS/test.sh
new file mode 100755 (executable)
index 0000000..6c2f0d3
--- /dev/null
@@ -0,0 +1,10 @@
+#!/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 "$@"
index 80a83813cb47fee276bfa693e070fad31625093f..b9992fae475c62eb8fa73ffecd9f15111e26ce7b 100644 (file)
@@ -10,6 +10,7 @@ if install_tests
                 'test-path',
                 'test-path-util',
                 'test-umount',
+                'test-network',
                 'test-network-generator-conversion',
                 'testsuite-03.units',
                 'testsuite-04.units',
diff --git a/test/mkosi.build.networkd-test b/test/mkosi.build.networkd-test
deleted file mode 100755 (executable)
index 25c935e..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/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
diff --git a/test/mkosi.default.networkd-test b/test/mkosi.default.networkd-test
deleted file mode 100644 (file)
index fe15f39..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-# 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
diff --git a/test/mkosi.nspawn.networkd-test b/test/mkosi.nspawn.networkd-test
deleted file mode 100644 (file)
index f624f24..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-[Network]
-Private=yes
index 5a16459aeb8ea4fc490dcfd7ca3f1c480fca8923..35a10868b69e179d1b7b3d333d887d6e0f6797ce 100644 (file)
@@ -214,11 +214,13 @@ BASICTOOLS=(
     seq
     setfattr
     setfont
+    setpriv
     setsid
     sfdisk
     sh
     sleep
     stat
+    stty
     su
     sulogin
     sysctl
@@ -261,7 +263,6 @@ DEBUGTOOLS=(
     route
     sort
     strace
-    stty
     tty
     vi
     /usr/libexec/vi
@@ -1185,7 +1186,7 @@ install_suse_systemd() {
         ddebug "Install files from package $p"
         while read -r f; do
             [ -e "$f" ] || continue
-            [ ! -L "$file" ] && [ -d "$file" ] && continue
+            [ ! -L "$f" ] && [ -d "$f" ] && continue
             inst "$f"
         done < <(rpm -ql "$p")
     done
@@ -1240,11 +1241,14 @@ install_systemd() {
         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
@@ -2651,12 +2655,12 @@ inst_binary() {
     # 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
@@ -3040,7 +3044,6 @@ _test_cleanup() {
         fi
         [[ -n "$TESTDIR" ]] && rm -vfr "$TESTDIR"
         [[ -n "$STATEFILE" ]] && rm -vf "$STATEFILE"
-        [[ -n "$STATEDIR" ]] && rm -vfr "$STATEDIR"
     ) || :
 }
 
@@ -3255,7 +3258,8 @@ do_test() {
                 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]"
index 4b62a30c9c43f02c21832216fc9fd60f2901dd15..f88878435cc62eb53a9532edc85739d70437cfcd 100755 (executable)
@@ -6,7 +6,7 @@
 # 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
index fab37960bc330fff04efd7b450a67ecc42321c3f..3bcc87561fd5b1ac97d20aae798b5de95e55d48d 100755 (executable)
@@ -4,112 +4,116 @@ set -e
 
 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
@@ -128,42 +132,44 @@ SYS_GID_MIN999
 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
index 3ca71d56488d51180c7d20bfb88a872227202579..565ed8d35ac9eddd425d627f37ca271d3f4f65b8 100755 (executable)
@@ -4,23 +4,60 @@
 set -eux
 set -o pipefail
 
-systemd-notify --status="Test starts, waiting for 5 seconds"
-sleep 5
+sync_in() {
+    read -r x < /tmp/syncfifo2
+    test "$x" = "$1"
+}
 
+sync_out() {
+    echo "$1" > /tmp/syncfifo1
+}
+
+export SYSTEMD_LOG_LEVEL=debug
+
+echo "toplevel PID: $BASHPID"
+
+systemd-notify --status="Test starts"
+sync_out a
+sync_in b
 (
-    systemd-notify --pid=auto
+    echo "subshell PID: $BASHPID"
+
+    # Make us main process
+    systemd-notify --pid="$BASHPID"
+
+    # Lock down access to just us
     systemd-notify "NOTIFYACCESS=main"
 
-    systemd-notify --status="Sending READY=1 in an unpriviledged process"
-    (
-        sleep 0.1
-        systemd-notify --ready
-    )
-    sleep 10
+    # This should still work
+    systemd-notify --status="Sending READY=1 in an unprivileged process"
+
+    # Send as subprocess of the subshell, this should not work
+    systemd-notify --ready --pid=self --status "BOGUS1"
 
-    systemd-notify "MAINPID=$$"
+    sync_out c
+    sync_in d
+
+    # Move main process back to toplevel
+    systemd-notify --pid=parent "MAINPID=$$"
+
+    # Should be dropped again
+    systemd-notify --status="BOGUS2" --pid=parent
+
+    # Apparently, bash will automatically invoke the last command in a subshell
+    # via a simple execve() rather than fork()ing first. But we want that the
+    # previous command uses the subshell's PID, hence let's insert a final,
+    # bogus redundant command as last command to run in the subshell, so that
+    # bash can't optimize things like that.
+    echo "bye"
 )
 
+echo "toplevel again: $BASHPID"
+
 systemd-notify --ready --status="OK"
 systemd-notify "NOTIFYACCESS=none"
-sleep infinity
+systemd-notify --status="BOGUS3"
+
+sync_out e
+
+exec sleep infinity
diff --git a/test/units/generator-utils.sh b/test/units/generator-utils.sh
new file mode 100644 (file)
index 0000000..1973efe
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+link_endswith() {
+    [[ -h "${1:?}" && "$(readlink "${1:?}")" =~ ${2:?}$ ]]
+}
+
+link_eq() {
+    [[ -h "${1:?}" && "$(readlink "${1:?}")" == "${2:?}" ]]
+}
+
+# Get the value from a 'key=value' assignment
+opt_get_arg() {
+    local arg
+
+    IFS="=" read -r _ arg <<< "${1:?}"
+    test -n "$arg"
+    echo "$arg"
+}
+
+in_initrd() {
+    [[ "${SYSTEMD_IN_INITRD:-0}" -ne 0 ]]
+}
+
+# Check if we're parsing host's fstab in initrd
+in_initrd_host() {
+    in_initrd && [[ "${SYSTEMD_SYSROOT_FSTAB:-/dev/null}" != /dev/null ]]
+}
+
+in_container() {
+    systemd-detect-virt -qc
+}
+
+# Filter out "unwanted" options, i.e. options that the fstab-generator doesn't
+# propagate to the final mount unit
+opt_filter_consumed() {(
+    set +x
+    local opt split_options filtered_options
+
+    IFS="," read -ra split_options <<< "${1:?}"
+    for opt in "${split_options[@]}"; do
+        if [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+            continue
+        fi
+
+        filtered_options+=("$opt")
+    done
+
+    IFS=","; printf "%s" "${filtered_options[*]}"
+)}
+
+# Run the given generator $1 with target directory $2 - clean the target
+# directory beforehand
+run_and_list() {
+    local generator="${1:?}"
+    local out_dir="${2:?}"
+
+    rm -fr "${out_dir:?}"/*
+    "$generator" "$out_dir"
+    ls -lR "$out_dir"
+}
index 1c81efc1b126a0ce9d2f83d56a92bd9b1fcf66d4..a55cc526a54edf69eebea85c95ffc7f9fafb5ea0 100644 (file)
@@ -1,10 +1,13 @@
 # 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
diff --git a/test/units/testsuite-01.sh b/test/units/testsuite-01.sh
new file mode 100755 (executable)
index 0000000..469c5fe
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+STTY_ORIGINAL="$(stty --file=/dev/console --save)"
+
+at_exit() {
+    set +e
+    stty --file=/dev/console "${STTY_ORIGINAL:?}"
+}
+
+trap at_exit EXIT
+
+# Do one reexec beforehand to get /dev/console into some predictable state
+systemctl daemon-reexec
+
+# Check if we do skip the early setup when doing daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27106
+#
+# Change a couple of console settings, do a reexec, and then check if our
+# changes persisted, since we reset the terminal stuff only on "full" reexec
+#
+# Relevant function: reset_terminal_fd() from terminal-util.cs
+stty --file=/dev/console brkint igncr inlcr istrip iuclc -icrnl -imaxbel -iutf8 \
+     kill ^K quit ^I
+STTY_NEW="$(stty --file=/dev/console --save)"
+systemctl daemon-reexec
+diff <(echo "$STTY_NEW") <(stty --file=/dev/console --save)
+
+if ! systemd-detect-virt -qc; then
+    # We also disable coredumps when doing a "full" reexec, so check for that too
+    sysctl -w kernel.core_pattern=dont-overwrite-me
+    systemctl daemon-reexec
+    diff <(echo dont-overwrite-me) <(sysctl --values kernel.core_pattern)
+fi
+
+# Collect failed units & do one daemon-reload to a basic sanity check
+systemctl --state=failed --no-legend --no-pager | tee /failed
+systemctl daemon-reload
+
+echo OK >/testok
index 3f3d8bc9c89cdf3de5af57f709b725236bc5ebad..b7cbfeb75a32748b09ac695cb8de390f45404fa5 100755 (executable)
@@ -290,4 +290,36 @@ rm -rf "$JTMP"
 
 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
index ba21c9ab87a22ff5b24ca927a8a7a8d5a95a44c4..ce6f58077f2be73d738c8532299f0040f729d571 100755 (executable)
@@ -97,6 +97,8 @@ assert_1 --resolve-names=now
 # 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
 
@@ -298,6 +300,9 @@ 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"
@@ -306,6 +311,7 @@ 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}"
 
index 6ce6d3d42918fea52e37170b018a6dd39f10f638..1e705ea72bf621637febb559b8931dd4c67d9c9b 100755 (executable)
@@ -7,37 +7,37 @@ test_scope_unpriv_delegation() {
     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
index 338769aacc500902d49ab7660e18592d87222bbc..5493b0df9b06f5a24ca2c4aae3a7d99be9325d95 100755 (executable)
@@ -13,8 +13,8 @@ INTERNALPID=$!
 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"
@@ -54,7 +54,7 @@ test "$(systemctl show -P MainPID testsuite-20.service)" -eq "$INTERNALPID"
 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
@@ -73,12 +73,12 @@ disown
 
 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
@@ -98,12 +98,12 @@ disown
 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
@@ -124,11 +124,11 @@ ln -s ../mainpidsh/pid /run/mainpidsh3/pid
 # Quick assertion that the link isn't dead
 test -f /run/mainpidsh3/pid
 EOF
-chmod 755 /dev/shm/test20-mainpid3.sh
+chmod 755 /dev/shm/test-mainpid3.sh
 
 # This has to fail, as we shouldn't accept the dangerous PID file, and then
 # inotify-wait on it to be corrected which we never do.
-systemd-run --unit=test20-mainpidsh3.service \
+systemd-run --unit=test-mainpidsh3.service \
             -p StandardOutput=tty \
             -p StandardError=tty \
             -p Type=forking \
@@ -136,28 +136,27 @@ systemd-run --unit=test20-mainpidsh3.service \
             -p PIDFile=/run/mainpidsh3/pid \
             -p DynamicUser=1 \
             -p TimeoutStartSec=2s \
-            /dev/shm/test20-mainpid3.sh \
+            /dev/shm/test-mainpid3.sh \
     && { echo 'unexpected success'; exit 1; }
 
 # Test that this failed due to timeout, and not some other error
-test "$(systemctl show -P Result test20-mainpidsh3.service)" = timeout
+test "$(systemctl show -P Result test-mainpidsh3.service)" = timeout
 
 # Test that scope units work
-systemd-run --scope --unit test20-true.scope /bin/true
-test "$(systemctl show -P Result test20-true.scope)" = success
+systemd-run --scope --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
 
 # Test that user scope units work as well
 
 runas() {
     declare userid=$1
     shift
-    # shellcheck disable=SC2016
-    su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+    XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
 }
 
 systemctl start user@4711.service
-runas testuser systemd-run --scope --user --unit test20-true.scope /bin/true
-test "$(systemctl show -P Result test20-true.scope)" = success
+runas testuser systemd-run --scope --user --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
 
 systemd-analyze log-level info
 
index b951eef88b7bf95f6e5a17d9634f37abf0138b16..9fb17a50c93e10ac69d44dbb595fbf693dd088af 100755 (executable)
@@ -5,13 +5,13 @@
 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
@@ -19,70 +19,70 @@ EOF
 
 systemctl daemon-reload
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
 
-systemctl start testservice
+systemctl start test-service
 
-test -d /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice && { echo 'unexpected success'; exit 1; }
+systemctl clean test-service && { echo 'unexpected success'; exit 1; }
 
-systemctl stop testservice
+systemctl stop test-service
 
-test -d /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice --what=configuration
+systemctl clean test-service --what=configuration
 
-test ! -e /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test ! -e /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice
+systemctl clean test-service
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test -d /var/lib/testservice
-test ! -e /var/cache/testservice
-test -d /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice --what=logs
+systemctl clean test-service --what=logs
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test -d /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
 
-systemctl clean testservice --what=all
+systemctl clean test-service --what=all
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
 
-cat >/etc/systemd/system/testservice.service <<EOF
+cat >/etc/systemd/system/test-service.service <<EOF
 [Service]
 DynamicUser=yes
-ConfigurationDirectory=testservice
-RuntimeDirectory=testservice
-StateDirectory=testservice
-CacheDirectory=testservice
-LogsDirectory=testservice
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
 RuntimeDirectoryPreserve=yes
 ExecStart=/bin/sleep infinity
 Type=exec
@@ -90,85 +90,85 @@ EOF
 
 systemctl daemon-reload
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
-
-systemctl restart testservice
-
-test -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice && { echo 'unexpected success'; exit 1; }
-
-systemctl stop testservice
-
-test -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice --what=configuration
-
-test ! -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test ! -L /run/testservice
-test -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice --what=logs
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test ! -d /var/log/private/testservice
-test ! -L /run/testservice
-test -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test ! -L /var/log/testservice
-
-systemctl clean testservice --what=all
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test ! -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test ! -d /var/log/private/testservice
-test ! -L /run/testservice
-test ! -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test ! -L /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+systemctl restart test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service && { echo 'unexpected success'; exit 1; }
+
+systemctl stop test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=configuration
+
+test ! -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=logs
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
+
+systemctl clean test-service --what=all
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test ! -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test ! -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
 
 cat >/etc/systemd/system/tmp-hoge.mount <<EOF
 [Mount]
@@ -245,75 +245,75 @@ test ! -d /var/lib/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 && { echo 'unexpected success'; exit 1; }
 
-systemctl stop testservice.socket
+systemctl stop test-service.socket
 
-test -d /etc/testsocket
-test ! -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test -d /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket --what=configuration
+systemctl clean test-service.socket --what=configuration
 
-test ! -e /etc/testsocket
-test ! -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket
+systemctl clean test-service.socket
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test -d /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test -d /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket --what=logs
+systemctl clean test-service.socket --what=logs
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test -d /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
 
-systemctl clean testservice.socket --what=all
+systemctl clean test-service.socket --what=all
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test ! -e /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
 
 echo OK >/testok
 
index 8dcd7563bb4081bfee5401ee769e5a13de29ed00..0b83a6a536b5c080ce3220b166e25f9cca6da384 100755 (executable)
@@ -591,7 +591,7 @@ test_ambient_caps() {
     # 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
index 40b94fdb48635d35aaf8b645d5fef5c814b1408c..fe47de26f007a06608a8d20532470e47432bb793 100755 (executable)
@@ -8,8 +8,7 @@ systemd-analyze log-level debug
 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 \
index af7d3511546a7ae0d9ecc03879ebb5d569ea4a57..3af90a69c611746834d1d061a229fcde9925975a 100755 (executable)
@@ -413,6 +413,17 @@ systemd-sysext merge
 test ! -e /usr/lib/systemd/system/some_file
 systemd-sysext unmerge
 rmdir /etc/extensions/app-nodistro
+
+# Check that extensions cannot contain os-release
+mkdir -p /run/extensions/app-reject/usr/lib/{extension-release.d/,systemd/system}
+echo "ID=_any" >/run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject
+echo "ID=_any" >/run/extensions/app-reject/usr/lib/os-release
+touch /run/extensions/app-reject/usr/lib/systemd/system/other_file
+systemd-sysext merge && { echo 'unexpected success'; exit 1; }
+test ! -e /usr/lib/systemd/system/some_file
+test ! -e /usr/lib/systemd/system/other_file
+systemd-sysext unmerge
+rm -rf /run/extensions/app-reject
 rm /var/lib/extensions/app-nodistro.raw
 
 mkdir -p /run/machines /run/portables /run/extensions
@@ -472,6 +483,17 @@ test ! -e "/dev/loop/by-ref/$name"
 systemd-dissect --detach "${image}.raw"
 (! systemd-dissect --detach "${image}.raw")
 
+# check for confext functionality
+mkdir -p /run/confexts/test/etc/extension-release.d
+echo "ID=_any" >/run/confexts/test/etc/extension-release.d/extension-release.test
+echo "ARCHITECTURE=_any" >>/run/confexts/test/etc/extension-release.d/extension-release.test
+echo "MARKER_CONFEXT_123" >/run/confexts/test/etc/testfile
+systemd-confext merge
+grep -q -F "MARKER_CONFEXT_123" /etc/testfile
+systemd-confext status
+systemd-confext unmerge
+rm -rf /run/confexts/
+
 echo OK >/testok
 
 exit 0
index dd7e28392d24cfa8338a9d665ac36d8f1000eabd..43049dea50842a6b4d094e41efa98c5eaacb8383 100755 (executable)
@@ -10,6 +10,7 @@ systemd-run -p LoadCredential=passwd:/etc/passwd \
             -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
@@ -77,11 +78,13 @@ fi
 # Verify that the creds are immutable
 systemd-run -p LoadCredential=passwd:/etc/passwd \
             -p DynamicUser=1 \
+            --unit=test-54-immutable-touch.service \
             --wait \
             touch '${CREDENTIALS_DIRECTORY}/passwd' \
     && { echo 'unexpected success'; exit 1; }
 systemd-run -p LoadCredential=passwd:/etc/passwd \
             -p DynamicUser=1 \
+            --unit=test-54-immutable-rm.service \
             --wait \
             rm '${CREDENTIALS_DIRECTORY}/passwd' \
     && { echo 'unexpected success'; exit 1; }
@@ -94,6 +97,7 @@ 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 \
+            --unit=test-54-dir.service \
             --wait \
             --pipe \
             cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
index 115b0d56f66c53acd45ea91973e3fb02f5c5ad40..e596392204ef2a562a358c7646024b6483e3c87d 100755 (executable)
@@ -4,7 +4,7 @@ set -ex
 
 if [ -f /tmp/testsuite-57.counter ] ; then
     read -r counter < /tmp/testsuite-57.counter
-    counter=$(("$counter" + 1))
+    counter=$((counter + 1))
 else
     counter=0
 fi
index 20c2c5707366d48508805423d07985172eb14c2a..83d7757a13c35db224673dc98723e9b5cae8da11 100755 (executable)
@@ -6,8 +6,7 @@ set -o pipefail
 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
index db8dc27ab9d9faff2efd5b30c5c4c166c4cce565..aabebb2bb60ba65bd107d345fa8f1032ab26ed1b 100755 (executable)
@@ -116,13 +116,13 @@ LEAVE=0
 
 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
 }
@@ -138,7 +138,7 @@ while [ \$LEAVE = 0 ] ; do
 done
 
 systemd-notify --status="Adding 3 to exit status"
-EXIT_STATUS=\$((\$EXIT_STATUS + 3))
+EXIT_STATUS=\$((EXIT_STATUS + 3))
 exit \$EXIT_STATUS
 EOF
 
index a8d3d2422b9aef522ca36ee974654f1e7cd2736d..3fbcbff8f4e1bd297b949cd426f47cdf9713fbe8 100755 (executable)
@@ -217,6 +217,65 @@ systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:
 systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata
 rm /tmp/testdata
 
+# negative tests for cryptenroll
+
+# Prepare a new disk image
+img_2="/var/tmp/file_enroll.txt"
+truncate -s 20M $img_2
+echo -n password >/tmp/password
+cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img_2 /tmp/password
+
+#boolean_arguments
+systemd-cryptenroll --fido2-with-client-pin=false && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-client-pin=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-client-pin=false $img_2
+
+systemd-cryptenroll --fido2-with-user-presence=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-user-presence=false $img_2
+
+systemd-cryptenroll --fido2-with-user-verification=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --tpm2-with-pin=1234 $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --fido2-with-user-verification=false $img_2
+
+#arg_enroll_type
+systemd-cryptenroll --recovery-key --password $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --recovery-key $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --password --tpm2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+#arg_unlock_type
+systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2 && { echo 'unexpected success'; exit 1; }
+
+#fido2_cred_algorithm
+systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2 && { echo 'unexpected success'; exit 1; }
+
+#tpm2_errors
+systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --tpm2-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; }
+
+#wipe_slots
+systemd-cryptenroll --wipe-slot $img_2 && { echo 'unexpected success'; exit 1; }
+
+systemd-cryptenroll --wipe-slot=10240000 $img_2 && { echo 'unexpected success'; exit 1; }
+
+#fido2_multiple_auto
+systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; }
+
 echo OK >/testok
 
 exit 0
index 2aef7c779a4a55f0923407fe5c41ae787e5fe0a2..191e3f77a6394b65ca8f4aac11acfb4823640f39 100755 (executable)
@@ -650,6 +650,11 @@ EOF
 
 : >/failed
 
+# Make sure the content of kbd-model-map is the one that the tests expect
+# regardless of the version intalled on the distro where the testsuite is
+# running on.
+export SYSTEMD_KBD_MODEL_MAP=/usr/lib/systemd/tests/testdata/test-keymap-util/kbd-model-map
+
 enable_debug
 test_locale
 test_vc_keymap
index 92a607501b7a1f9c9165e66d7f71319f4fd6aeb1..be08575c9e69d49b34bc646ffe96699bc488292e 100755 (executable)
@@ -119,6 +119,15 @@ grep -q "^root:x:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd"
 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"
diff --git a/test/units/testsuite-74.modules-load.sh b/test/units/testsuite-74.modules-load.sh
new file mode 100755 (executable)
index 0000000..3d00e07
--- /dev/null
@@ -0,0 +1,88 @@
+#!/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)
index 4890911fd00f3d6b008a1944357de4dddb7cdf32..bfe472bef7a82d047743b495c4d989b9fcc196b4 100755 (executable)
@@ -114,7 +114,7 @@ systemctl status "$(systemd-escape --path "$WORK_DIR/mnt").automount"
 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"
@@ -122,7 +122,7 @@ 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
index 5f57569b076436444ee4721f12f592a89abc4969..0072133cd4e1ca4adc274aea26fe9c1dca86a92f 100755 (executable)
@@ -9,17 +9,35 @@ set -o pipefail
 
 : >/failed
 
+mkfifo /tmp/syncfifo1 /tmp/syncfifo2
+
+sync_in() {
+    read -r x < /tmp/syncfifo1
+    test "$x" = "$1"
+}
+
+sync_out() {
+    echo "$1" > /tmp/syncfifo2
+}
+
+export SYSTEMD_LOG_LEVEL=debug
+
 systemctl --no-block start notify.service
-sleep 2
 
-assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts, waiting for 5 seconds"
+sync_in a
+
 assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
-sleep 5
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts"
+
+sync_out b
+sync_in c
 
 assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "main"
-assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unpriviledged process"
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unprivileged process"
 assert_rc 3 systemctl --quiet is-active notify.service
-sleep 10
+
+sync_out d
+sync_in e
 
 systemctl --quiet is-active notify.service
 assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK"
@@ -28,5 +46,34 @@ 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
+
+MYSCRIPT="/tmp/myscript$RANDOM.sh"
+cat >> "$MYSCRIPT" <<'EOF'
+#!/usr/bin/env bash
+set -eux
+set -o pipefail
+N="/tmp/$RANDOM"
+echo $RANDOM > "$N"
+systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N"
+rm "$N"
+systemd-notify --ready
+exec sleep infinity
+EOF
+
+chmod +x "$MYSCRIPT"
+
+MYUNIT="myunit$RANDOM.service"
+systemd-run -u "$MYUNIT" -p Type=notify -p StandardOutput=journal+console -p StandardError=journal+console -p FileDescriptorStoreMax=7 "$MYSCRIPT"
+
+test "$(systemd-analyze fdstore "$MYUNIT" | wc -l)" -eq 2
+systemd-analyze fdstore "$MYUNIT" --json=short
+systemd-analyze fdstore "$MYUNIT" --json=short | grep -P -q '\[{"fdname":"quux","type":.*,"devno":\[.*\],"inode":.*,"rdevno":null,"path":"/tmp/.*","flags":"ro"}\]'
+
+systemctl stop "$MYUNIT"
+rm "$MYSCRIPT"
+
 touch /testok
 rm /failed
+
+exit 0
diff --git a/test/units/testsuite-81.debug-generator.sh b/test/units/testsuite-81.debug-generator.sh
new file mode 100755 (executable)
index 0000000..d2b2bf1
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-debug-generator"
+OUT_DIR="$(mktemp -d /tmp/debug-generator.XXX)"
+
+at_exit() {
+    rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+# Potential FIXME:
+#   - debug-generator should gracefully handle duplicated mask/wants
+#   - also, handle gracefully empty mask/wants
+ARGS=(
+    "systemd.mask=masked-no-suffix"
+    "systemd.mask=masked.service"
+    "systemd.mask=masked.socket"
+    "systemd.wants=wanted-no-suffix"
+    "systemd.wants=wanted.service"
+    "systemd.wants=wanted.mount"
+    "rd.systemd.mask=masked-initrd.service"
+    "rd.systemd.wants=wanted-initrd.service"
+)
+
+# Regular (non-initrd) scenario
+#
+: "debug-shell: regular"
+CMDLINE="ro root=/ ${ARGS[*]} rd.systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/masked.service" /dev/null
+link_eq "$OUT_DIR/masked.socket" /dev/null
+link_endswith "$OUT_DIR/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/default.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/default.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+# Following stuff should be ignored, as it's prefixed with rd.
+test ! -h "$OUT_DIR/masked-initrd.service"
+test ! -h "$OUT_DIR/default.target.wants/wants-initrd.service"
+test ! -h "$OUT_DIR/default.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/initrd.target.wants"
+
+# Let's re-run the generator with systemd.debug_shell that should be honored
+: "debug-shell: regular + systemd.debug_shell"
+CMDLINE="$CMDLINE systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Same thing, but with custom tty
+: "debug-shell: regular + systemd.debug_shell=/dev/tty666"
+CMDLINE="$CMDLINE systemd.debug_shell=/dev/tty666"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+grep -F "/dev/tty666" "$OUT_DIR/debug-shell.service.d/50-tty.conf"
+
+# Now override the default target via systemd.unit=
+: "debug-shell: regular + systemd.unit="
+CMDLINE="$CMDLINE systemd.unit=my-fancy.target"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/masked.service" /dev/null
+link_eq "$OUT_DIR/masked.socket" /dev/null
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+link_endswith "$OUT_DIR/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+test ! -d "$OUT_DIR/default.target.wants"
+
+
+# Initrd scenario
+: "debug-shell: initrd"
+CMDLINE="ro root=/ ${ARGS[*]} systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+# The non-initrd stuff (i.e. without the rd. suffix) should be ignored in
+# this case
+test ! -h "$OUT_DIR/masked-no-suffix.service"
+test ! -h "$OUT_DIR/masked.service"
+test ! -h "$OUT_DIR/masked.socket"
+test ! -h "$OUT_DIR/initrd.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/default.target.wants"
+
+# Again, but with rd.systemd.debug_shell
+: "debug-shell: initrd + rd.systemd.debug_shell"
+CMDLINE="$CMDLINE rd.systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/initrd.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Override the default target
+: "debug-shell: initrd + rd.systemd.unit"
+CMDLINE="$CMDLINE rd.systemd.unit=my-fancy-initrd.target"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+test ! -d "$OUT_DIR/initrd.target.wants"
diff --git a/test/units/testsuite-81.environment-d-generator.sh b/test/units/testsuite-81.environment-d-generator.sh
new file mode 100755 (executable)
index 0000000..5bc3978
--- /dev/null
@@ -0,0 +1,80 @@
+#!/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)
diff --git a/test/units/testsuite-81.fstab-generator.sh b/test/units/testsuite-81.fstab-generator.sh
new file mode 100755 (executable)
index 0000000..a9efc0b
--- /dev/null
@@ -0,0 +1,360 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235,SC2233
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+export SYSTEMD_LOG_LEVEL=debug
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-fstab-generator"
+NETWORK_FS_RX="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$"
+OUT_DIR="$(mktemp -d /tmp/fstab-generator.XXX)"
+FSTAB="$(mktemp)"
+
+at_exit() {
+    rm -fr "${OUT_DIR:?}" "${FSTAB:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+FSTAB_GENERAL=(
+    # Valid entries
+    "/dev/test2     /nofail                             ext4        nofail 0 0"
+    "/dev/test3     /regular                            btrfs       defaults 0 0"
+    "/dev/test4     /x-systemd.requires                 xfs         x-systemd.requires=foo.service 0 0"
+    "/dev/test5     /x-systemd.before-after             xfs         x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0"
+    "/dev/test6     /x-systemd.wanted-required-by       xfs         x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0"
+    "/dev/test7     /x-systemd.requires-mounts-for      xfs         x-systemd.requires-mounts-for=/foo/bar/baz 0 0"
+    "/dev/test8     /x-systemd.automount-idle-timeout   vfat        x-systemd.automount,x-systemd.idle-timeout=50s 0 0"
+    "/dev/test9     /x-systemd.makefs                   xfs         x-systemd.makefs 0 0"
+    "/dev/test10    /x-systemd.growfs                   xfs         x-systemd.growfs 0 0"
+    "/dev/test11    /_netdev                            ext4        defaults,_netdev 0 0"
+    "/dev/test12    /_rwonly                            ext4        x-systemd.rw-only 0 0"
+    "/dev/test13    /chaos1                             zfs         x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0"
+    "/dev/test14    /chaos2                             zfs         x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0"
+    "/dev/test15    /fstype/auto                        auto        defaults 0 0"
+    "/dev/test16    /fsck/me                            ext4        defaults 0 1"
+    "/dev/test17    /also/fsck/me                       ext4        defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99"
+    "/dev/test18    /swap                               swap        defaults 0 0"
+    "/dev/test19    /swap/makefs                        swap        defaults,x-systemd.makefs 0 0"
+    "/dev/test20    /var                                xfs         defaults,x-systemd.device-timeout=1h 0 0"
+    "/dev/test21    /usr                                ext4        defaults 0 1"
+    "/dev/test22    /initrd/mount                       ext2        defaults,x-systemd.rw-only,x-initrd.mount 0 1"
+    "/dev/test23    /initrd/mount/nofail                ext3        defaults,nofail,x-initrd.mount 0 1"
+    "/dev/test24    /initrd/mount/deps                  ext4        x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1"
+
+    # Incomplete, but valid entries
+    "/dev/incomplete1 /incomplete1"
+    "/dev/incomplete2 /incomplete2                      ext4"
+    "/dev/incomplete3 /incomplete3                      ext4        defaults"
+    "/dev/incomplete4 /incomplete4                      ext4        defaults 0"
+
+    # Remote filesystems
+    "/dev/remote1   /nfs                                nfs         bg 0 0"
+    "/dev/remote2   /nfs4                               nfs4        bg 0 0"
+    "bar.tld:/store /remote/storage                     nfs         ro,x-systemd.wanted-by=store.service 0 0"
+    "user@host.tld:/remote/dir /remote/top-secret       sshfs       rw,x-systemd.before=naughty.service 0 0"
+    "foo.tld:/hello /hello/world                        ceph        defaults 0 0"
+    "//192.168.0.1/storage /cifs-storage                cifs        automount,nofail 0 0"
+)
+
+FSTAB_GENERAL_ROOT=(
+    # rootfs with bunch of options we should ignore and fsck enabled
+    "/dev/test1     /                                   ext4        noauto,nofail,automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1"
+    "${FSTAB_GENERAL[@]}"
+)
+
+FSTAB_MINIMAL=(
+    "/dev/loop1     /foo/bar                            ext3        defaults 0 0"
+)
+
+FSTAB_DUPLICATE=(
+    "/dev/dup1     /       ext4 defaults 0 1"
+    "/dev/dup2     /       ext4 defaults,x-systemd.requires=foo.mount 0 2"
+)
+
+FSTAB_INVALID=(
+    # Ignored entries
+    "/dev/ignored1  /sys/fs/cgroup/foo                  ext4        defaults    0 0"
+    "/dev/ignored2  /sys/fs/selinux                     ext4        defaults    0 0"
+    "/dev/ignored3  /dev/console                        ext4        defaults    0 0"
+    "/dev/ignored4  /proc/kmsg                          ext4        defaults    0 0"
+    "/dev/ignored5  /proc/sys                           ext4        defaults    0 0"
+    "/dev/ignored6  /proc/sys/kernel/random/boot_id     ext4        defaults    0 0"
+    "/dev/ignored7  /run/host                           ext4        defaults    0 0"
+    "/dev/ignored8  /run/host/foo                       ext4        defaults    0 0"
+    "/dev/ignored9  /autofs                             autofs      defaults    0 0"
+    "/dev/invalid1  not-a-path                          ext4        defaults    0 0"
+    ""
+    "/dev/invalid1"
+    "                  "
+    "\\"
+    "$"
+)
+
+check_fstab_mount_units() {
+    local what where fstype opts passno unit
+    local item opt split_options filtered_options supp service device arg
+    local array_name="${1:?}"
+    local out_dir="${2:?}"
+    # Get a reference to the array from its name
+    local -n fstab_entries="$array_name"
+
+    # Running the checks in a container is pretty much useless, since we don't
+    # generate any mounts, but don't skip the whole test to test the "skip"
+    # paths as well
+    in_container && return 0
+
+    for item in "${fstab_entries[@]}"; do
+        # Don't use a pipe here, as it would make the variables out of scope
+        read -r what where fstype opts _ passno <<< "$item"
+
+        # Skip non-initrd mounts in initrd
+        if in_initrd_host && ! [[ "$opts" =~ x-initrd.mount ]]; then
+            continue
+        fi
+
+        if [[ "$fstype" == swap ]]; then
+            unit="$(systemd-escape --suffix=swap --path "${what:?}")"
+            cat "$out_dir/$unit"
+
+            grep -qE "^What=$what$" "$out_dir/$unit"
+            if [[ "$opts" != defaults ]]; then
+                grep -qE "^Options=$opts$" "$out_dir/$unit"
+            fi
+
+            if [[ "$opts" =~ x-systemd.makefs ]]; then
+                service="$(systemd-escape --template=systemd-mkswap@.service --path "$what")"
+                test -e "$out_dir/$service"
+            fi
+
+            continue
+        fi
+
+        # If we're parsing host's fstab in initrd, prefix all mount targets
+        # with /sysroot
+        in_initrd_host && where="/sysroot${where:?}"
+        unit="$(systemd-escape --suffix=mount --path "${where:?}")"
+        cat "$out_dir/$unit"
+
+        # Check the general stuff
+        grep -qE "^What=$what$" "$out_dir/$unit"
+        grep -qE "^Where=$where$" "$out_dir/$unit"
+        if [[ -n "$fstype" ]] && [[ "$fstype" != auto ]]; then
+            grep -qE "^Type=$fstype$" "$out_dir/$unit"
+        fi
+        if [[ -n "$opts" ]] && [[ "$opts" != defaults ]]; then
+            # Some options are not propagated to the generated unit
+            filtered_options="$(opt_filter_consumed "$opts")"
+            if [[ "${filtered_options[*]}" != defaults ]]; then
+                grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit"
+            fi
+        fi
+
+        if ! [[ "$opts" =~ (noauto|x-systemd.(wanted-by=|required-by=|automount)) ]]; then
+            # We don't create the Requires=/Wants= symlinks for noauto/automount mounts
+            # and for mounts that use x-systemd.wanted-by=/required-by=
+            if in_initrd_host; then
+                if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+                    link_eq "$out_dir/initrd-fs.target.requires/$unit" "../$unit"
+                else
+                    link_eq "$out_dir/initrd-fs.target.wants/$unit" "../$unit"
+                fi
+            elif [[ "$fstype" =~ $NETWORK_FS_RX || "$opts" =~ _netdev ]]; then
+                # Units with network filesystems should have a Requires= dependency
+                # on the remote-fs.target, unless they use nofail or are an nfs "bg"
+                # mounts, in which case the dependency is downgraded to Wants=
+                if [[ "$opts" =~ nofail ]] || [[ "$fstype" =~ ^(nfs|nfs4) && "$opts" =~ bg ]]; then
+                    link_eq "$out_dir/remote-fs.target.wants/$unit" "../$unit"
+                else
+                    link_eq "$out_dir/remote-fs.target.requires/$unit" "../$unit"
+                fi
+            else
+                # Similarly, local filesystems should have a Requires= dependency on
+                # the local-fs.target, unless they use nofail, in which case the
+                # dependency is downgraded to Wants=. Rootfs is a special case,
+                # since we always ignore nofail there
+                if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+                    link_eq "$out_dir/local-fs.target.requires/$unit" "../$unit"
+                else
+                    link_eq "$out_dir/local-fs.target.wants/$unit" "../$unit"
+                fi
+            fi
+        fi
+
+        if [[ "${passno:=0}" -ne 0 ]]; then
+            # Generate systemd-fsck@.service dependencies, if applicable
+            if in_initrd && [[ "$where" == / || "$where" == /usr ]]; then
+                continue
+            fi
+
+            if [[ "$where" == / ]]; then
+                link_endswith "$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service"
+            else
+                service="$(systemd-escape --template=systemd-fsck@.service --path "$what")"
+                grep -qE "^After=$service$" "$out_dir/$unit"
+                if [[ "$where" == /usr ]]; then
+                    grep -qE "^Wants=$service$" "$out_dir/$unit"
+                else
+                    grep -qE "^Requires=$service$" "$out_dir/$unit"
+                fi
+            fi
+        fi
+
+        # Check various x-systemd options
+        #
+        # First, split them into an array to make splitting them even further
+        # easier
+        IFS="," read -ra split_options <<< "$opts"
+        # and process them one by one.
+        #
+        # Note: the "machinery" below might (and probably does) miss some
+        #       combinations of supported options, so tread carefully
+        for opt in "${split_options[@]}"; do
+            if [[ "$opt" =~ ^x-systemd.requires= ]]; then
+                service="$(opt_get_arg "$opt")"
+                grep -qE "^Requires=$service$" "$out_dir/$unit"
+                grep -qE "^After=$service$" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd.before= ]]; then
+                service="$(opt_get_arg "$opt")"
+                grep -qE "^Before=$service$" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd.after= ]]; then
+                service="$(opt_get_arg "$opt")"
+                grep -qE "^After=$service$" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd.wanted-by= ]]; then
+                service="$(opt_get_arg "$opt")"
+                if [[ "$where" == / ]]; then
+                    # This option is ignored for rootfs mounts
+                    (! link_eq "$out_dir/$service.wants/$unit" "../$unit")
+                else
+                    link_eq "$out_dir/$service.wants/$unit" "../$unit"
+                fi
+            elif [[ "$opt" =~ ^x-systemd.required-by= ]]; then
+                service="$(opt_get_arg "$opt")"
+                if [[ "$where" == / ]]; then
+                    # This option is ignored for rootfs mounts
+                    (! link_eq "$out_dir/$service.requires/$unit" "../$unit")
+                else
+                    link_eq "$out_dir/$service.requires/$unit" "../$unit"
+                fi
+            elif [[ "$opt" =~ ^x-systemd.requires-mounts-for= ]]; then
+                arg="$(opt_get_arg "$opt")"
+                grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit"
+            elif [[ "$opt" == x-systemd.device-bound ]]; then
+                # This is implied for fstab mounts
+                :
+            elif [[ "$opt" == x-systemd.automount ]]; then
+                # The $unit should have an accompanying automount unit
+                supp="$(systemd-escape --suffix=automount --path "$where")"
+                test -e "$out_dir/$supp"
+                link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp"
+            elif [[ "$opt" =~ ^x-systemd.idle-timeout= ]]; then
+                # The timeout applies to the automount unit, not the original
+                # mount one
+                arg="$(opt_get_arg "$opt")"
+                supp="$(systemd-escape --suffix=automount --path "$where")"
+                grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp"
+            elif [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+                arg="$(opt_get_arg "$opt")"
+                device="$(systemd-escape --suffix=device --path "$what")"
+                grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf"
+            elif [[ "$opt" == x-systemd.makefs ]]; then
+                service="$(systemd-escape --template=systemd-makefs@.service --path "$what")"
+                test -e "$out_dir/$service"
+                link_eq "$out_dir/${unit}.requires/$service" "../$service"
+            elif [[ "$opt" == x-systemd.rw-only ]]; then
+                grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit"
+            elif [[ "$opt" == x-systemd.growfs ]]; then
+                service="$(systemd-escape --template=systemd-growfs@.service --path "$where")"
+                link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service"
+            elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then
+                # We "convert" nfs bg mounts to fg, so we can do the job-control
+                # ourselves
+                grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit"
+                grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd\. ]]; then
+                echo >&2 "Unhandled mount option: $opt"
+                exit 1
+            fi
+        done
+    done
+}
+
+# TODO
+#   - kernel arguments
+
+: "fstab-generator: regular"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Skip the rest when running in a container, as it makes little sense to check
+# initrd-related stuff there and fstab-generator might have a bit strange
+# behavior during certain tests, like https://github.com/systemd/systemd/issues/27156
+if in_container; then
+    echo "Running in a container, skipping the rest of the fstab-generator tests..."
+    exit 0
+fi
+
+# In this mode we treat the entries as "regular" ones
+: "fstab-generator: initrd - initrd fstab"
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null check_fstab_mount_units FSTAB_GENERAL "$OUT_DIR"
+
+# In this mode we prefix the mount target with /sysroot and ignore all mounts
+# that don't have the x-initrd.mount flag
+: "fstab-generator: initrd - host fstab"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Check the default stuff that we (almost) always create in initrd
+: "fstab-generator: initrd default"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+test -e "$OUT_DIR/sysroot.mount"
+test -e "$OUT_DIR/systemd-fsck-root.service"
+link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+link_eq "$OUT_DIR/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+
+: "fstab-generator: run as systemd-sysroot-fstab-check in initrd"
+ln -svf "$GENERATOR_BIN" /tmp/systemd-sysroot-fstab-check
+(! /tmp/systemd-sysroot-fstab-check foo)
+(! SYSTEMD_IN_INITRD=0 /tmp/systemd-sysroot-fstab-check)
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB="$FSTAB" /tmp/systemd-sysroot-fstab-check
+
+: "fstab-generator: duplicate"
+printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB"
+cat "$FSTAB"
+(! SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR")
+
+: "fstab-generator: invalid"
+printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB"
+cat "$FSTAB"
+# Don't care about the exit code here
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" || :
+# No mounts should get created here
+[[ "$(find "$OUT_DIR" -name "*.mount" | wc -l)" -eq 0 ]]
+
+: "fstab-generator: kernel args - fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+
+: "fstab-generator: kernel args - rd.fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
diff --git a/test/units/testsuite-81.service b/test/units/testsuite-81.service
new file mode 100644 (file)
index 0000000..3b697b3
--- /dev/null
@@ -0,0 +1,8 @@
+# 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
diff --git a/test/units/testsuite-81.sh b/test/units/testsuite-81.sh
new file mode 100755 (executable)
index 0000000..63f4cb6
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+systemctl log-level debug
+
+for script in "${0%.sh}".*.sh; do
+    echo "Running $script"
+    "./$script"
+done
+
+touch /testok
+rm /failed
index 06d68c1d6bcf79d6da3a6cfebbba29be1cba7679..e6eb300661ec89cfdb5c9ecd2c599330d7c20bdb 100644 (file)
@@ -138,6 +138,7 @@ units = [
         ['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',
diff --git a/units/systemd-confext.service b/units/systemd-confext.service
new file mode 100644 (file)
index 0000000..3b46eca
--- /dev/null
@@ -0,0 +1,34 @@
+# 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
index 565374698d76d8b8d67330e08ec6c010866a2fd4..a2d457fc027eaf236322e3aad7dbc679131b8274 100644 (file)
@@ -11,7 +11,7 @@
 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]
index f8c26f5fbfa3eaed9cda32e90a4f2d00221f569e..117591e897ee7ac12fbca2cb97cae75767b11211 100644 (file)
@@ -15,8 +15,6 @@ ConditionCapability=CAP_SYS_ADMIN
 ConditionDirectoryNotEmpty=|/etc/extensions
 ConditionDirectoryNotEmpty=|/run/extensions
 ConditionDirectoryNotEmpty=|/var/lib/extensions
-ConditionDirectoryNotEmpty=|/usr/local/lib/extensions
-ConditionDirectoryNotEmpty=|/usr/lib/extensions
 
 DefaultDependencies=no
 After=local-fs.target
@@ -27,7 +25,8 @@ Before=shutdown.target initrd-switch-root.target
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=systemd-sysext merge
+ExecStart=systemd-sysext refresh
+ExecReload=systemd-sysext refresh
 ExecStop=systemd-sysext unmerge
 
 [Install]