]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #25608 from poettering/dissect-moar
authorLennart Poettering <lennart@poettering.net>
Wed, 12 Apr 2023 11:46:08 +0000 (13:46 +0200)
committerGitHub <noreply@github.com>
Wed, 12 Apr 2023 11:46:08 +0000 (13:46 +0200)
dissect: add dissection policies

165 files changed:
.github/workflows/make_release.yml
.github/workflows/mkosi.yml
TODO
docs/ENVIRONMENT.md
hwdb.d/60-keyboard.hwdb
hwdb.d/60-sensor.hwdb
man/org.freedesktop.systemd1.xml
man/os-release.xml
man/rules/meson.build
man/sd_notify.xml
man/systemd-dissect.xml
man/systemd-mount.xml
man/systemd-sysext.xml
man/systemd.exec.xml
man/systemd.netdev.xml
man/systemd.service.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 78% 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 91% 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.kernel.config
rules.d/60-persistent-storage-tape.rules
rules.d/60-persistent-storage.rules.in
shell-completion/bash/busctl
shell-completion/bash/systemd-confext [new file with mode: 0644]
shell-completion/zsh/_busctl
src/activate/activate.c
src/analyze/analyze-blame.c
src/analyze/analyze-critical-chain.c
src/analyze/analyze-plot.c
src/analyze/analyze-time-data.c
src/analyze/analyze-time-data.h
src/ask-password/ask-password.c
src/basic/chase.c
src/basic/chase.h
src/basic/compress.c
src/basic/compress.h
src/basic/conf-files.c
src/basic/conf-files.h
src/basic/env-file.c
src/basic/env-file.h
src/basic/fd-util.c
src/basic/fileio.c
src/basic/fileio.h
src/basic/getopt-defs.h [new file with mode: 0644]
src/basic/os-util.c
src/basic/os-util.h
src/basic/path-util.c
src/basic/path-util.h
src/basic/proc-cmdline.c
src/basic/proc-cmdline.h
src/basic/user-util.c
src/basic/user-util.h
src/boot/bootctl-install.c
src/boot/bootctl-uki.c
src/boot/efi/meson.build
src/boot/efi/util.h
src/busctl/busctl.c
src/cgls/cgls.c
src/core/dbus-manager.c
src/core/execute.c
src/core/main.c
src/core/namespace.c
src/core/service.c
src/core/unit.c
src/firstboot/firstboot.c
src/fstab-generator/fstab-generator.c
src/fuzz/fuzz-compress.c
src/journal/cat.c
src/journal/journald-server.c
src/libsystemd/sd-bus/sd-bus.c
src/libsystemd/sd-device/device-private.c
src/libsystemd/sd-device/device-private.h
src/libsystemd/sd-device/sd-device.c
src/libsystemd/sd-journal/journal-file.c
src/libsystemd/sd-journal/journal-file.h
src/locale/localed-util.c
src/login/inhibit.c
src/machine/machinectl.c
src/nspawn/nspawn.c
src/partition/repart.c
src/portable/portable.c
src/portable/portablectl.c
src/portable/portabled-bus.c
src/portable/portabled-image-bus.c
src/resolve/resolved-dns-packet.h
src/run/run.c
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/bus-unit-util.c
src/shared/bus-unit-util.h
src/shared/condition.c
src/shared/copy.c
src/shared/device-nodes.c
src/shared/discover-image.c
src/shared/discover-image.h
src/shared/dissect-image.c
src/shared/edit-util.c
src/shared/edit-util.h
src/shared/extension-util.c
src/shared/extension-util.h
src/shared/find-esp.c
src/shared/kernel-image.c
src/shared/kernel-image.h
src/shared/mkfs-util.c
src/shutdown/shutdown.c
src/sysext/meson.build
src/sysext/sysext.c
src/systemctl/systemctl-add-dependency.c
src/systemctl/systemctl-edit.c
src/systemctl/systemctl-enable.c
src/systemctl/systemctl-preset-all.c
src/systemctl/systemctl-set-default.c
src/systemctl/systemctl-show.c
src/systemctl/systemctl-start-unit.c
src/test/test-compress-benchmark.c
src/test/test-compress.c
src/test/test-conf-files.c
src/test/test-copy.c
src/test/test-env-util.c
src/test/test-fileio.c
src/test/test-os-util.c
src/test/test-path-util.c
src/test/test-proc-cmdline.c
src/udev/scsi_id/scsi_id.c
src/udev/scsi_id/scsi_serial.c
src/udev/udev-rules.c
src/udev/udevadm-lock.c
src/udev/udevadm.c
src/userdb/userdbctl.c
test/TEST-64-UDEV-STORAGE/test.sh
test/TEST-81-GENERATORS/Makefile [new symlink]
test/TEST-81-GENERATORS/test.sh [new file with mode: 0755]
test/fuzz/fuzz-udev-rules/60-persistent-storage-tape.rules
test/fuzz/fuzz-udev-rules/60-persistent-storage.rules
test/test-functions
test/test-systemctl-enable.sh
test/udev-test.pl
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-29.sh
test/units/testsuite-50.sh
test/units/testsuite-64.sh
test/units/testsuite-65.sh
test/units/testsuite-73.sh
test/units/testsuite-74.modules-load.sh [new file with mode: 0755]
test/units/testsuite-75.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-sysext.service

index 47dbbea374ae864c5ee87def6e1261a4423d4931..9902a6c6ab0ff28e951c7f0025d25f2c5a557686 100644 (file)
@@ -5,14 +5,20 @@ on:
     tags:
       - "v*"
 
+permissions:
+  contents: read
+
 jobs:
-  build:
+  release:
+    if: github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable'
     runs-on: ubuntu-latest
+
+    permissions:
+      contents: write
+
     steps:
-      - name: Checkout
-        uses: actions/checkout@v3
       - name: Release
-        uses: softprops/action-gh-release@v1
+        uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
         with:
           prerelease: ${{ contains(github.ref_name, '-rc') }}
           draft: ${{ github.repository == 'systemd/systemd' }}
index cec8a432d28d4e2a0e4777faed8ca3744e10e490..829d0c5c45438ffb01e6d4ca6262e4b09e10a9ce 100644 (file)
@@ -73,7 +73,7 @@ jobs:
 
     steps:
     - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
-    - uses: systemd/mkosi@af1fe54805c84bb09e80bb585399121946fec701
+    - uses: systemd/mkosi@ee7f2950a25fdaaa7027937c5d9f3df6761e9fd9
 
     - name: Configure
       run: |
diff --git a/TODO b/TODO
index e9d6c61108d47e47ab4beecec1ff40fd493ec469..29b1ac66011b1eeaac3b0a52bf8a52d2ef7e9d6e 100644 (file)
--- a/TODO
+++ b/TODO
@@ -518,13 +518,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
@@ -535,17 +535,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.
 
@@ -594,7 +594,7 @@ 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?
 
@@ -908,12 +908,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).
index 3ec5573ff9524e0ee4059331c6fc9ae8473a1f95..445131b479b86d937c03596aa6251b56cc5bfa23 100644 (file)
@@ -45,8 +45,7 @@ All tools:
 
 * `$SYSTEMD_OS_RELEASE` â€” if set, use this path instead of `/etc/os-release` or
   `/usr/lib/os-release`. When operating under some root (e.g. `systemctl
-  --root=…`), the path is taken relative to the outside root. Only useful for
-  debugging.
+  --root=…`), the path is prefixed with the root. Only useful for debugging.
 
 * `$SYSTEMD_FSTAB` â€” if set, use this path instead of `/etc/fstab`. Only useful
   for debugging.
@@ -328,7 +327,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 +514,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 7195217bcd7f66bd2188c936b37609469515838c..82f3aaf0513c579788d528a29020e02467fb1e39 100644 (file)
@@ -933,7 +933,7 @@ evdev:input:b0003v17EFp6009*
  KEYBOARD_KEY_090010=f20                                # Microphone mute button; should be micmute
 
 # Lenovo 3000
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:pvr*
  KEYBOARD_KEY_8b=switchvideomode                        # Fn+F7 video
  KEYBOARD_KEY_96=wlan                                   # Fn+F5 wireless
  KEYBOARD_KEY_97=sleep                                  # Fn+F4 suspend
@@ -945,7 +945,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO:pn0769AP2:pvr3000N200:*
  KEYBOARD_KEY_b4=prog1
 
 # Lenovo IdeaPad
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:pvr*
 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pnS10-*:*
  KEYBOARD_KEY_81=rfkill                                 # does nothing in BIOS
  KEYBOARD_KEY_83=display_off                            # BIOS toggles screen state
@@ -960,7 +960,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrIdeaPad5*:*
  KEYBOARD_KEY_81=insert
 
 # Thinkpad X200_Tablet
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:rvn*
  KEYBOARD_KEY_5d=menu
  KEYBOARD_KEY_63=fn
  KEYBOARD_KEY_66=screenlock
@@ -969,7 +969,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X2*T*:*
  KEYBOARD_KEY_6c=direction                              # rotate screen
 
 # ThinkPad X6 Tablet
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X6*Tablet*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*X6*Tablet*:rvn*
  KEYBOARD_KEY_6c=direction                              # rotate
  KEYBOARD_KEY_68=leftmeta                               # toolbox
  KEYBOARD_KEY_6b=esc                                    # escape
@@ -993,20 +993,20 @@ evdev:name:Ideapad extra buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*
  KEYBOARD_KEY_42=f23
  KEYBOARD_KEY_43=f22
 
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*Y550*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*Y550*:pvr*
  KEYBOARD_KEY_95=media
  KEYBOARD_KEY_a3=play
 
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*U300s*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*U300s*:pvr*
  KEYBOARD_KEY_f1=f21
  KEYBOARD_KEY_ce=f20                                    # micmute
 
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:pvr*
  KEYBOARD_KEY_a0=!mute
  KEYBOARD_KEY_ae=!volumedown
  KEYBOARD_KEY_b0=!volumeup
 
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPadFlex5*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPadFlex5*:pvr*
  KEYBOARD_KEY_a0=!mute
  KEYBOARD_KEY_ae=!volumedown
  KEYBOARD_KEY_b0=!volumeup
@@ -1025,11 +1025,11 @@ evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn81Q7*:pvrLenovoYogaS940:*
  KEYBOARD_KEY_b0=!volumeup
 
 # Lenovo Y50-70
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:*
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:pvr*
  KEYBOARD_KEY_f3=f21                                    # Fn+F6 (toggle touchpad)
 
 # V480
-evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:*
+evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:pvr*
  KEYBOARD_KEY_f1=f21
 
 # Lenovo ThinkCentre M800z/M820z/M920z AIO machines
index e142dc55445b2d2ab7bd85fc4c5bd17bb7a890d5..bbc0533c134b5f683b4acd14795aa1698c95845b 100644 (file)
@@ -171,6 +171,9 @@ sensor:modalias:acpi:SMO8500*:dmi:*svn*ASUSTeK*:*pn*TP300LAB:*
 sensor:modalias:acpi:BOSC0200*:dmi:*svn*ASUSTeK*:*pn*TP412UA:*
  ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
 
+sensor:modalias:acpi:BOSC0200*:dmi:*svn*ASUSTeK*:pn*BR1100FKA:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, -1
+
 #########################################
 # Axxo
 #########################################
@@ -808,6 +811,14 @@ sensor:modalias:acpi:BOSC0200*:dmi:bvnAmericanMegatrendsInc.:bvr5.12:bd07/17/201
 sensor:modalias:acpi:BMI0160*:dmi:*:rnONEXPLAYER:rvrV01:*
  ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, -1
 
+#########################################
+# Passion
+#########################################
+
+# Passion P612F
+sensor:modalias:acpi:MXC6655*:dmi:*:svnDefaultstring*:pnP612F:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
 #########################################
 # Peaq
 #########################################
index 05c8b3770e2bede96bf962112b085c7b3ceb3c3a..f39893f647a6ceedcdcf846cc24b6ab366591e49 100644 (file)
@@ -2501,7 +2501,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       condition is a trigger condition, whether the condition is reversed, the right hand side of the
       condition (e.g. the path in case of <varname>ConditionPathExists</varname>), and the status. The status
       can be 0, in which case the condition hasn't been checked yet, a positive value, in which case the
-      condition passed, or a negative value, in which case the condition failed. Currently only 0, +1, and -1
+      condition passed, or a negative value, in which case the condition is not met. Currently only 0, +1, and -1
       are used, but additional values may be used in the future, retaining the meaning of
       zero/positive/negative values.</para>
 
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
index 42c546f18d81fb4b942cf4113f6ff56e0c4202c0..cdf98eaaf04f0f37d56ecf5ca79982d3859c3aa8 100644 (file)
@@ -1043,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 cb07add95eb2f35c1a51b331d711fde17c000030..39bddc9d15ed0bf970dfe747a7a0e05f98d63c6a 100644 (file)
 
   <refsect1>
     <title>Description</title>
-    <para><function>sd_notify()</function> may be called by a service
-    to notify the service manager about state changes. It can be used
-    to send arbitrary information, encoded in an
-    environment-block-like string. Most importantly, it can be used for
-    start-up completion notification.</para>
-
-    <para>If the <parameter>unset_environment</parameter> parameter is
-    non-zero, <function>sd_notify()</function> will unset the
-    <varname>$NOTIFY_SOCKET</varname> environment variable before
-    returning (regardless of whether the function call itself
-    succeeded or not). Further calls to
-    <function>sd_notify()</function> will then fail, but the variable
-    is no longer inherited by child processes.</para>
-
-    <para>The <parameter>state</parameter> parameter should contain a
-    newline-separated list of variable assignments, similar in style
-    to an environment block. A trailing newline is implied if none is
-    specified. The string may contain any kind of variable
-    assignments, but the following shall be considered
+
+    <para><function>sd_notify()</function> may be called by a service to notify the service manager about
+    state changes. It can be used to send arbitrary information, encoded in an environment-block-like
+    string. Most importantly, it can be used for start-up completion notification.</para>
+
+    <para>If the <parameter>unset_environment</parameter> parameter is non-zero,
+    <function>sd_notify()</function> will unset the <varname>$NOTIFY_SOCKET</varname> environment variable
+    before returning (regardless of whether the function call itself succeeded or not). Further calls to
+    <function>sd_notify()</function> will then fail, but the variable is no longer inherited by child
+    processes.</para>
+
+    <para>The <parameter>state</parameter> parameter should contain a newline-separated list of variable
+    assignments, similar in style to an environment block. A trailing newline is implied if none is
+    specified. The string may contain any kind of variable assignments, but the following shall be considered
     well-known:</para>
 
     <variablelist>
       <varlistentry>
         <term>STOPPING=1</term>
 
-        <listitem><para>Tells the service manager that the service is
-        beginning its shutdown. This is useful to allow the service
-        manager to track the service's internal state, and present it
-        to the user.</para></listitem>
+        <listitem><para>Tells the service manager that the service is beginning its shutdown. This is useful
+        to allow the service manager to track the service's internal state, and present it to the
+        user.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>STATUS=…</term>
 
-        <listitem><para>Passes a single-line UTF-8 status string back
-        to the service manager that describes the service state. This
-        is free-form and can be used for various purposes: general
-        state feedback, fsck-like programs could pass completion
-        percentages and failing programs could pass a human-readable
-        error message. Example: <literal>STATUS=Completed 66% of file
-        system check…</literal></para></listitem>
+        <listitem><para>Passes a single-line UTF-8 status string back to the service manager that describes
+        the service state. This is free-form and can be used for various purposes: general state feedback,
+        fsck-like programs could pass completion percentages and failing programs could pass a human-readable
+        error message. Example: <literal>STATUS=Completed 66% of file system
+        check…</literal></para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>NOTIFYACCESS=…</term>
 
-        <listitem><para>Reset the access to the service status notification
-        socket during runtime, overriding <varname>NotifyAccess=</varname> setting
-        in the service unit file. See <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        for details, specifically <literal>NotifyAccess=</literal> for a list of
-        accepted values.</para></listitem>
+        <listitem><para>Reset the access to the service status notification socket during runtime, overriding
+        <varname>NotifyAccess=</varname> setting in the service unit file. See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details, specifically <literal>NotifyAccess=</literal> for a list of accepted
+        values.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>ERRNO=…</term>
 
-        <listitem><para>If a service fails, the errno-style error
-        code, formatted as string. Example: <literal>ERRNO=2</literal>
-        for ENOENT.</para></listitem>
+        <listitem><para>If a service fails, the errno-style error code, formatted as string. Example:
+        <literal>ERRNO=2</literal> for ENOENT.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>BUSERROR=…</term>
 
-        <listitem><para>If a service fails, the D-Bus error-style
-        error code. Example:
+        <listitem><para>If a service fails, the D-Bus error-style error code. Example:
         <literal>BUSERROR=org.freedesktop.DBus.Error.TimedOut</literal></para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>MAINPID=…</term>
 
-        <listitem><para>The main process ID (PID) of the service, in
-        case the service manager did not fork off the process itself.
-        Example: <literal>MAINPID=4711</literal></para></listitem>
+        <listitem><para>The main process ID (PID) of the service, in case the service manager did not fork
+        off the process itself.  Example: <literal>MAINPID=4711</literal></para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>WATCHDOG=1</term>
 
-        <listitem><para>Tells the service manager to update the
-        watchdog timestamp. This is the keep-alive ping that services
-        need to issue in regular intervals if
-        <varname>WatchdogSec=</varname> is enabled for it. See
+        <listitem><para>Tells the service manager to update the watchdog timestamp. This is the keep-alive
+        ping that services need to issue in regular intervals if <varname>WatchdogSec=</varname> is enabled
+        for it. See
         <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
         for information how to enable this functionality and
         <citerefentry><refentrytitle>sd_watchdog_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>
-        for the details of how the service can check whether the
-        watchdog is enabled. </para></listitem>
+        for the details of how the service can check whether the watchdog is enabled. </para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>WATCHDOG=trigger</term>
 
-        <listitem><para>Tells the service manager that the service detected an internal error that should be handled by
-        the configured watchdog options. This will trigger the same behaviour as if <varname>WatchdogSec=</varname> is
-        enabled and the service did not send <literal>WATCHDOG=1</literal> in time. Note that
-        <varname>WatchdogSec=</varname> does not need to be enabled for <literal>WATCHDOG=trigger</literal> to trigger
-        the watchdog action. See
-        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
-        information about the watchdog behavior. </para></listitem>
+        <listitem><para>Tells the service manager that the service detected an internal error that should be
+        handled by the configured watchdog options. This will trigger the same behaviour as if
+        <varname>WatchdogSec=</varname> is enabled and the service did not send <literal>WATCHDOG=1</literal>
+        in time. Note that <varname>WatchdogSec=</varname> does not need to be enabled for
+        <literal>WATCHDOG=trigger</literal> to trigger the watchdog action. See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for information about the watchdog behavior. </para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>WATCHDOG_USEC=…</term>
 
-        <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.
-        Notice that this is not available when using <function>sd_event_set_watchdog()</function>
-        or <function>sd_watchdog_enabled()</function>.
-        Example : <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
+        <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.  Notice that this is not
+        available when using <function>sd_event_set_watchdog()</function> or
+        <function>sd_watchdog_enabled()</function>.  Example :
+        <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>EXTEND_TIMEOUT_USEC=…</term>
 
         <listitem><para>Tells the service manager to extend the startup, runtime or shutdown service timeout
-        corresponding the current state. The value specified is a time in microseconds during which the service must
-        send a new message. A service timeout will occur if the message isn't received, but only if the runtime of the
-        current state is beyond the original maximum times of <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>,
-        and <varname>TimeoutStopSec=</varname>.
-        See <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        corresponding the current state. The value specified is a time in microseconds during which the
+        service must send a new message. A service timeout will occur if the message isn't received, but only
+        if the runtime of the current state is beyond the original maximum times of
+        <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>, and
+        <varname>TimeoutStopSec=</varname>.  See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
         for effects on the service timeouts.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDSTORE=1</term>
 
-        <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this way will
-        be maintained per-service by the service manager and will later be handed back using the usual file descriptor
-        passing logic at the next invocation of the service (e.g. when it is restarted), see
-        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.  This is
-        useful for implementing services that can restart after an explicit request or a crash without losing
-        state. Any open sockets and other file descriptors which should not be closed during the restart may be stored
-        this way. Application state can either be serialized to a file in <filename>/run/</filename>, or better, stored
-        in a <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry> memory
-        file descriptor. Note that the service manager will accept messages for a service only if its
+        <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this
+        way will be maintained per-service by the service manager and will later be handed back using the
+        usual file descriptor passing logic at the next invocation of the service (e.g. when it is
+        restarted), see
+        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+        This is useful for implementing services that can restart after an explicit request or a crash
+        without losing state. Any open sockets and other file descriptors which should not be closed during
+        the restart may be stored this way. Application state can either be serialized to a file in
+        <filename>/run/</filename>, or better, stored in a
+        <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+        memory file descriptor. Note that the service manager will accept messages for a service only if its
         <varname>FileDescriptorStoreMax=</varname> setting is non-zero (defaults to zero, see
         <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>). If
         <varname>FDPOLL=0</varname> is not set and the file descriptors sent are pollable (see
-        <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then any
-        <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in their
-        automatic removal from the store. Multiple arrays of file descriptors may be sent in separate messages, in
-        which case the arrays are combined. Note that the service manager removes duplicate (pointing to the same
-        object) file descriptors before passing them to the service. When a service is stopped, its file descriptor
-        store is discarded and all file descriptors in it are closed. Use <function>sd_pid_notify_with_fds()</function>
-        to send messages with <literal>FDSTORE=1</literal>, see below.</para></listitem>
+        <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then
+        any <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in
+        their automatic removal from the store. Multiple arrays of file descriptors may be sent in separate
+        messages, in which case the arrays are combined. Note that the service manager removes duplicate
+        (pointing to the same object) file descriptors before passing them to the service. When a service is
+        stopped, its file descriptor store is discarded and all file descriptors in it are closed. Use
+        <function>sd_pid_notify_with_fds()</function> to send messages with <literal>FDSTORE=1</literal>, see
+        below. The service manager will set the <varname>$FDSTORE</varname> environment variable for services
+        that have the file descriptor store enabled.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDSTOREREMOVE=1</term>
 
-        <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be combined with
-        <varname>FDNAME=</varname> to specify the name of the file descriptors to remove.</para></listitem>
+        <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be
+        combined with <varname>FDNAME=</varname> to specify the name of the file descriptors to
+        remove.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDNAME=…</term>
 
-        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the submitted
-        file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for the file
-        descriptors to remove. This name is passed to the service during activation, and may be queried using
+        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the
+        submitted file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for
+        the file descriptors to remove. This name is passed to the service during activation, and may be
+        queried using
         <citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
         descriptors submitted without this field set, will implicitly get the name <literal>stored</literal>
-        assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be assigned to
-        all of them. In order to assign different names to submitted file descriptors, submit them in separate
-        invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist of arbitrary ASCII
-        characters except control characters or <literal>:</literal>. It may not be longer than 255 characters. If a
-        submitted name does not follow these restrictions, it is ignored.</para></listitem>
+        assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be
+        assigned to all of them. In order to assign different names to submitted file descriptors, submit
+        them in separate invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist
+        of arbitrary ASCII characters except control characters or <literal>:</literal>. It may not be longer
+        than 255 characters. If a submitted name does not follow these restrictions, it is
+        ignored.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDPOLL=0</term>
 
-        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the stored
-        file descriptors regardless of whether or not they are pollable. As this option disables automatic cleanup
-        of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to ensure proper manual cleanup.
-        Use of this option is not generally recommended except for when automatic cleanup has unwanted behavior such
-        as prematurely discarding file descriptors from the store.</para></listitem>
+        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the
+        stored file descriptors regardless of whether or not they are pollable. As this option disables
+        automatic cleanup of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to
+        ensure proper manual cleanup.  Use of this option is not generally recommended except for when
+        automatic cleanup has unwanted behavior such as prematurely discarding file descriptors from the
+        store.</para></listitem>
       </varlistentry>
 
       <varlistentry>
       </varlistentry>
     </variablelist>
 
-    <para>It is recommended to prefix variable names that are not
-    listed above with <varname>X_</varname> to avoid namespace
-    clashes.</para>
-
-    <para>Note that systemd will accept status data sent from a
-    service only if the <varname>NotifyAccess=</varname> option is
-    correctly set in the service definition file. See
-    <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-    for details.</para>
-
-    <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only if either
-    the sending process is still around at the time PID 1 processes the message, or if the sending process is
-    explicitly runtime-tracked by the service manager. The latter is the case if the service manager originally forked
-    off the process, i.e. on all processes that match <varname>NotifyAccess=</varname><option>main</option> or
-    <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit sends an
-    <function>sd_notify()</function> message and immediately exits, the service manager might not be able to properly
-    attribute the message to the unit, and thus will ignore it, even if
+    <para>It is recommended to prefix variable names that are not listed above with <varname>X_</varname> to
+    avoid namespace clashes.</para>
+
+    <para>Note that systemd will accept status data sent from a service only if the
+    <varname>NotifyAccess=</varname> option is correctly set in the service definition file. See
+    <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+    details.</para>
+
+    <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only
+    if either the sending process is still around at the time PID 1 processes the message, or if the sending
+    process is explicitly runtime-tracked by the service manager. The latter is the case if the service
+    manager originally forked off the process, i.e. on all processes that match
+    <varname>NotifyAccess=</varname><option>main</option> or
+    <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit
+    sends an <function>sd_notify()</function> message and immediately exits, the service manager might not be
+    able to properly attribute the message to the unit, and thus will ignore it, even if
     <varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
 
     <para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
     service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
     unit.</para>
 
-    <para><function>sd_notifyf()</function> is similar to
-    <function>sd_notify()</function> but takes a
-    <function>printf()</function>-like format string plus
-    arguments.</para>
-
-    <para><function>sd_pid_notify()</function> and
-    <function>sd_pid_notifyf()</function> are similar to
-    <function>sd_notify()</function> and
-    <function>sd_notifyf()</function> but take a process ID (PID) to
-    use as originating PID for the message as first argument. This is
-    useful to send notification messages on behalf of other processes,
-    provided the appropriate privileges are available. If the PID
-    argument is specified as 0, the process ID of the calling process
-    is used, in which case the calls are fully equivalent to
-    <function>sd_notify()</function> and
-    <function>sd_notifyf()</function>.</para>
-
-    <para><function>sd_pid_notify_with_fds()</function> is similar to
-    <function>sd_pid_notify()</function> but takes an additional array
-    of file descriptors. These file descriptors are sent along the
-    notification message to the service manager. This is particularly
-    useful for sending <literal>FDSTORE=1</literal> messages, as
-    described above. The additional arguments are a pointer to the
-    file descriptor array plus the number of file descriptors in the
-    array. If the number of file descriptors is passed as 0, the call
-    is fully equivalent to <function>sd_pid_notify()</function>, i.e.
-    no file descriptors are passed. Note that sending file descriptors
-    to the service manager on messages that do not expect them (i.e.
-    without <literal>FDSTORE=1</literal>) they are immediately closed
-    on reception.</para>
-
-    <para><function>sd_notify_barrier()</function> allows the caller to
-    synchronize against reception of previously sent notification messages
-    and uses the <varname>BARRIER=1</varname> command. It takes a relative
-    <varname>timeout</varname> value in microseconds which is passed to
+    <para><function>sd_notifyf()</function> is similar to <function>sd_notify()</function> but takes a
+    <function>printf()</function>-like format string plus arguments.</para>
+
+    <para><function>sd_pid_notify()</function> and <function>sd_pid_notifyf()</function> are similar to
+    <function>sd_notify()</function> and <function>sd_notifyf()</function> but take a process ID (PID) to use
+    as originating PID for the message as first argument. This is useful to send notification messages on
+    behalf of other processes, provided the appropriate privileges are available. If the PID argument is
+    specified as 0, the process ID of the calling process is used, in which case the calls are fully
+    equivalent to <function>sd_notify()</function> and <function>sd_notifyf()</function>.</para>
+
+    <para><function>sd_pid_notify_with_fds()</function> is similar to <function>sd_pid_notify()</function>
+    but takes an additional array of file descriptors. These file descriptors are sent along the notification
+    message to the service manager. This is particularly useful for sending <literal>FDSTORE=1</literal>
+    messages, as described above. The additional arguments are a pointer to the file descriptor array plus
+    the number of file descriptors in the array. If the number of file descriptors is passed as 0, the call
+    is fully equivalent to <function>sd_pid_notify()</function>, i.e. no file descriptors are passed. Note
+    that file descriptors sent to the service manager on a message without <literal>FDSTORE=1</literal> are
+    immediately closed on reception.</para>
+
+    <para><function>sd_notify_barrier()</function> allows the caller to synchronize against reception of
+    previously sent notification messages and uses the <varname>BARRIER=1</varname> command. It takes a
+    relative <varname>timeout</varname> value in microseconds which is passed to
     <citerefentry><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum>
     </citerefentry>. A value of UINT64_MAX is interpreted as infinite timeout.
     </para>
   <refsect1>
     <title>Return Value</title>
 
-    <para>On failure, these calls return a negative errno-style error code. If <varname>$NOTIFY_SOCKET</varname> was
-    not set and hence no status message could be sent, 0 is returned. If the status was sent, these functions return a
-    positive value. In order to support both service managers that implement this scheme and those which do not, it is
-    generally recommended to ignore the return value of this call. Note that the return value simply indicates whether
-    the notification message was enqueued properly, it does not reflect whether the message could be processed
+    <para>On failure, these calls return a negative errno-style error code. If
+    <varname>$NOTIFY_SOCKET</varname> was not set and hence no status message could be sent, 0 is
+    returned. If the status was sent, these functions return a positive value. In order to support both
+    service managers that implement this scheme and those which do not, it is generally recommended to ignore
+    the return value of this call. Note that the return value simply indicates whether the notification
+    message was enqueued properly, it does not reflect whether the message could be processed
     successfully. Specifically, no error is returned when a file descriptor is attempted to be stored using
-    <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file descriptors (see
-    above).</para>
+    <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file
+    descriptors (see above).</para>
   </refsect1>
 
   <refsect1>
     <xi:include href="libsystemd-pkgconfig.xml" xpointer="pkgconfig-text"/>
     <xi:include href="threads-aware.xml" xpointer="getenv"/>
 
-    <para>These functions send a single datagram with the
-    state string as payload to the socket referenced in the
-    <varname>$NOTIFY_SOCKET</varname> environment variable. If the
-    first character of <varname>$NOTIFY_SOCKET</varname> is
-    <literal>/</literal> or <literal>@</literal>, the string is understood
-    as an <constant>AF_UNIX</constant> or Linux abstract namespace socket
-    (respectively), and in both cases the datagram is accompanied by the
-    process credentials of the sending service, using SCM_CREDENTIALS. If
-    the string starts with <literal>vsock:</literal> then the string is
-    understood as an <constant>AF_VSOCK</constant> address, which is useful
-    for hypervisors/VMMs or other processes on the host to receive a
-    notification when a virtual machine has finished booting. Note that in
-    case the hypervisor does not support <constant>SOCK_DGRAM</constant>
-    over <constant>AF_VSOCK</constant>, <constant>SOCK_SEQPACKET</constant>
-    will be used instead. The address should be in the form:
-    <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock,
-    the CID is mandatory and cannot be <literal>VMADDR_CID_ANY</literal>.
-    Note that PID1 will send the VSOCK packets from a privileged port
-    (i.e.: lower than 1024), as an attempt to address concerns that unprivileged
-    processes in the guest might try to send malicious notifications to the
-    host, driving it to make destructive decisions based on them.</para>
+    <para>These functions send a single datagram with the state string as payload to the socket referenced in
+    the <varname>$NOTIFY_SOCKET</varname> environment variable. If the first character of
+    <varname>$NOTIFY_SOCKET</varname> is <literal>/</literal> or <literal>@</literal>, the string is
+    understood as an <constant>AF_UNIX</constant> or Linux abstract namespace socket (respectively), and in
+    both cases the datagram is accompanied by the process credentials of the sending service, using
+    SCM_CREDENTIALS. If the string starts with <literal>vsock:</literal> then the string is understood as an
+    <constant>AF_VSOCK</constant> address, which is useful for hypervisors/VMMs or other processes on the
+    host to receive a notification when a virtual machine has finished booting. Note that in case the
+    hypervisor does not support <constant>SOCK_DGRAM</constant> over <constant>AF_VSOCK</constant>,
+    <constant>SOCK_SEQPACKET</constant> will be used instead. The address should be in the form:
+    <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock, the CID is mandatory and cannot
+    be <literal>VMADDR_CID_ANY</literal>.  Note that PID1 will send the VSOCK packets from a privileged port
+    (i.e.: lower than 1024), as an attempt to address concerns that unprivileged processes in the guest might
+    try to send malicious notifications to the host, driving it to make destructive decisions based on
+    them.</para>
   </refsect1>
 
   <refsect1>
       <varlistentry>
         <term><varname>$NOTIFY_SOCKET</varname></term>
 
-        <listitem><para>Set by the service manager for supervised
-        processes for status and start-up completion notification.
-        This environment variable specifies the socket
-        <function>sd_notify()</function> talks to. See above for
-        details.</para></listitem>
+        <listitem><para>Set by the service manager for supervised processes for status and start-up
+        completion notification.  This environment variable specifies the socket
+        <function>sd_notify()</function> talks to. See above for details.</para></listitem>
       </varlistentry>
     </variablelist>
   </refsect1>
     <example>
       <title>Start-up Notification</title>
 
-      <para>When a service finished starting up, it might issue the
-      following call to notify the service manager:</para>
+      <para>When a service finished starting up, it might issue the following call to notify the service
+      manager:</para>
 
       <programlisting>sd_notify(0, "READY=1");</programlisting>
     </example>
     <example>
       <title>Extended Start-up Notification</title>
 
-      <para>A service could send the following after completing
-      initialization:</para>
+      <para>A service could send the following after completing initialization:</para>
 
       <programlisting>
 sd_notifyf(0, "READY=1\n"
@@ -469,9 +443,8 @@ sd_notifyf(0, "STATUS=Failed to start up: %s\n"
     <example>
       <title>Store a File Descriptor in the Service Manager</title>
 
-      <para>To store an open file descriptor in the service manager,
-      in order to continue operation after a service restart without
-      losing state, use <literal>FDSTORE=1</literal>:</para>
+      <para>To store an open file descriptor in the service manager, in order to continue operation after a
+      service restart without losing state, use <literal>FDSTORE=1</literal>:</para>
 
       <programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &amp;fd, 1);</programlisting>
     </example>
@@ -479,12 +452,10 @@ sd_notifyf(0, "STATUS=Failed to start up: %s\n"
     <example>
       <title>Eliminating race conditions</title>
 
-      <para>When the client sending the notifications is not spawned
-      by the service manager, it may exit too quickly and the service
-      manager may fail to attribute them correctly to the unit. To
-      prevent such races, use <function>sd_notify_barrier()</function>
-      to synchronize against reception of all notifications sent before
-      this call is made.</para>
+      <para>When the client sending the notifications is not spawned by the service manager, it may exit too
+      quickly and the service manager may fail to attribute them correctly to the unit. To prevent such
+      races, use <function>sd_notify_barrier()</function> to synchronize against reception of all
+      notifications sent before this call is made.</para>
 
       <programlisting>
 sd_notify(0, "READY=1");
index 2a83477357aa926d41a831f0a37c1f79740af463..06ee0717f825f07a372f570857df03dfb386f80e 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 1cde3ab00cd6fe0c0d247a722ed1ab91fe5a9ce2..e25d5c435e95a29166bb85692339870c39f68d47 100644 (file)
 
         <listitem><para>This option only has an effect in automount mode,
         and controls whether the automount unit shall be bound to the backing device's lifetime. If set, the
-        automount point will be removed automatically when the backing device vanishes. By default the automount point
+        automount unit will be stopped automatically when the backing device vanishes. By default the automount unit
         stays around, and subsequent accesses will block until backing device is replugged. This option has no effect
         in case of non-device mounts, such as network or virtual file system mounts.</para>
 
index 2b7a87f5105bf8eaa7e3d34075ab59fbdaaa89fc..a257fa73bca298032154899cc033016c51849bea 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>
     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>
 
       <varlistentry>
index 1d99c586011a6e3061dad234654bc7cfd6aa109a..795e26e792a854cb59459ada1a33c665714bd71f 100644 (file)
@@ -3500,8 +3500,7 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
         <varlistentry>
           <term><varname>$NOTIFY_SOCKET</varname></term>
 
-          <listitem><para>The socket
-          <function>sd_notify()</function> talks to. See
+          <listitem><para>The socket <function>sd_notify()</function> talks to. See
           <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
           </para></listitem>
         </varlistentry>
@@ -3823,6 +3822,19 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
           convey.</para></listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>$FDSTORE</varname></term>
+
+          <listitem><para>If the file descriptor store is enabled for a service
+          (i.e. <varname>FileDescriptorStoreMax=</varname> is set to a non-zero value, see
+          <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+          for details), this environment variable will be set to the maximum number of permitted entries, as
+          per the setting. Applications may check this environment variable before sending file descriptors
+          to the service manager via <function>sd_pid_notify_with_fds()</function> (see
+          <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
+          details).</para></listitem>
+        </varlistentry>
+
       </variablelist>
 
       <para>For system services, when <varname>PAMName=</varname> is enabled and <command>pam_systemd</command> is part
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 f64a8e538ff473370f248594f83395ca584b989f..1b116b8372aaae7f0c75c326af3ace2c4d156e04 100644 (file)
         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>
+        descriptors.</para>
+
+        <para>If this option is set to a non-zero value the <varname>$FDSTORE</varname> environment variable
+        will be set for processes invoked for this service. See
+        <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+        details.</para></listitem>
       </varlistentry>
 
       <varlistentry>
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 bbb3544f1f37d646e590b355de66e6ba0d944e3d..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,7 @@ OutputDirectory=mkosi.output
 
 [Content]
 BuildDirectory=mkosi.builddir
-Cache=mkosi.cache
+CacheDirectory=mkosi.cache
 ExtraTrees=src:/root/src
 Packages=
         acl
similarity index 78%
rename from mkosi.conf.d/arch/10-arch.conf
rename to mkosi.conf.d/20-arch.conf
index 30333cff3a967853c7ef89adbe970219906bf593..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.conf in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
+[Match]
 Distribution=arch
 
 [Content]
similarity index 66%
rename from mkosi.conf.d/centos/10-centos.conf
rename to mkosi.conf.d/20-centos.conf
index ff26e3611fdedb86c2b418f4992e0206ce305bc6..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.conf in the project root directory and invoke "mkosi" to build an OS image.
-
-# We use python3[.][9]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,11 +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(psutil)
-        python3[.][9]dist(pytest)
-        python39
+        python3-docutils
         quota
         tpm2-tss
         vim-common
@@ -61,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
@@ -102,7 +91,3 @@ BuildPackages=
         pkgconfig(tss2-rc)
         pkgconfig(valgrind)
         pkgconfig(xkbcommon)
-        python3-docutils
-        python3[.][9]dist(jinja2)
-        python3[.][9]dist(lxml)
-        python3[.][9]dist(pyelftools)
similarity index 90%
rename from mkosi.conf.d/debian/10-debian.conf
rename to mkosi.conf.d/20-debian.conf
index c3e582b8ecf93299e3c04d6e971e68d4c2fb7216..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.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=debian
 
 [Distribution]
-Distribution=debian
 Release=testing
 
 [Content]
similarity index 91%
rename from mkosi.conf.d/fedora/10-fedora.conf
rename to mkosi.conf.d/20-fedora.conf
index b263cce673834887a7ad7ca358896888bc36227f..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.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=fedora
 
 [Distribution]
-Distribution=fedora
 Release=37
 
 [Content]
similarity index 91%
rename from mkosi.conf.d/opensuse/10-opensuse.conf
rename to mkosi.conf.d/20-opensuse.conf
index 1e03c79cff238f728df5f21320e9463c6598352a..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.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=opensuse
 
 [Distribution]
-Distribution=opensuse
 Release=tumbleweed
 
 [Content]
similarity index 90%
rename from mkosi.conf.d/ubuntu/10-ubuntu.conf
rename to mkosi.conf.d/20-ubuntu.conf
index 51898475b0b56f5b9a45bd59d3a5bd3d17f64f1a..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.conf in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=ubuntu
 
 [Distribution]
-Distribution=ubuntu
 Release=jammy
 Repositories=main,universe
 
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 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 607e51ae2a1602d3b939cad6b6db84fbca1d6120..8c00cc76d92005854dacd149c720a713bed45afa 100644 (file)
@@ -6,7 +6,7 @@ ACTION=="remove", GOTO="persistent_storage_tape_end"
 ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
 
 # type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --allowlisted -d $devnode", \
   SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL} tape/by-id/scsi-$env{ID_SERIAL}-changer"
 
 # iSCSI devices from the same host have all the same ID_SERIAL,
@@ -22,7 +22,7 @@ SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end"
 KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
 KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
 KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
-KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --allowlisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
 KERNEL=="st*[0-9]",  ENV{ID_SERIAL}=="?*",      SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}", OPTIONS+="link_priority=10"
 KERNEL=="st*[0-9]",  ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
 KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*",      SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
index 51589686af859e01669afc4cce8de0ae9b954c45..89c064fb6660aa236f07adc05a32b0f3cbc426b7 100644 (file)
@@ -38,6 +38,10 @@ KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{wwid}=="?*", ENV{ID_WWN
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatiblity
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+  ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+  ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
 # obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
   OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
@@ -48,10 +52,14 @@ KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatiblity
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+  ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+  ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
 # obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
   OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
-KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*",  ENV{ID_NSID}=="?*", \
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
   OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
 
 # virtio-blk
@@ -71,8 +79,8 @@ KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=
 KERNEL=="sd*[!0-9]|sr*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
 
 # SCSI devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="cciss"
 KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
 KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
 # Previously, ata_id in the above might not be able to retrieve attributes correctly,
index ee160a79008c96dae0cbc0bd030677ad6877b50d..5464225b15e2643eeb90d4d1ae181d5d66647f42 100644 (file)
@@ -86,7 +86,7 @@ _busctl() {
                       --show-machine --unique --acquired --activatable --list
                       -q --quiet --verbose --expect-reply=no --auto-start=no
                       --allow-interactive-authorization=no --augment-creds=no
-                      --watch-bind=yes -j -l --full'
+                      --watch-bind=yes -j -l --full --xml-interface'
         [ARG]='--address -H --host -M --machine --match --timeout --size --json
                       --destination'
     )
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
index 0cb1c44a43c79655548aadfe7483d6eb6308e923..b0cd4d5db51b9cfe00912b8eba8a4360a7482783 100644 (file)
@@ -276,6 +276,7 @@ _arguments \
     '--list[Do not show tree, but simple object path list]' \
     {-q,--quiet}'[Do not show method call reply]'\
     '--verbose[Show result values in long format]' \
+    '--xml-interface[Dump the XML description in introspect command]' \
     '--json=[Show result values in long format]:format:_busctl_get_json' \
     '-j[Show pretty json in interactive sessions, short json otherwise]' \
     '--expect-reply=[Expect a method call reply]:boolean:(1 0)' \
index 4a639703260764c219c14a590895ecff1c2b9f8f..1caa30d7d474f7058588124f301b4b0dd11007e5 100644 (file)
@@ -347,6 +347,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0)
                 switch (c) {
                 case 'h':
index c9112685f8323d7bf88b65db8873c218382db8f4..81e5c590b9cc248453a61f46a533959c432b73f3 100644 (file)
@@ -16,7 +16,7 @@ int verb_blame(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_connect_error(r, arg_transport);
 
-        n = acquire_time_data(bus, &times);
+        n = acquire_time_data(bus, /* require_finished = */ false, &times);
         if (n <= 0)
                 return n;
 
index f782f95d5f229b9359537d6bff1c4542db514c71..f80f3ddb63c2e2ae7d05f5ebf6c76ab2c8f88687 100644 (file)
@@ -93,7 +93,7 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level,
 
         typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
 
-        r = acquire_boot_times(bus, &boot);
+        r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
         if (r < 0)
                 return r;
 
@@ -178,7 +178,7 @@ static int list_dependencies(sd_bus *bus, const char *name) {
 
         times = hashmap_get(unit_times_hashmap, id);
 
-        r = acquire_boot_times(bus, &boot);
+        r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
         if (r < 0)
                 return r;
 
@@ -205,7 +205,7 @@ int verb_critical_chain(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_connect_error(r, arg_transport);
 
-        n = acquire_time_data(bus, &times);
+        n = acquire_time_data(bus, /* require_finished = */ true, &times);
         if (n <= 0)
                 return n;
 
index e44b9c11f62affded0ce27177e0204ece9853451..ef40e64631d5d268aec0d7ac3b831d0dc8498359 100644 (file)
@@ -439,7 +439,7 @@ int verb_plot(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_connect_error(r, arg_transport);
 
-        n = acquire_boot_times(bus, &boot);
+        n = acquire_boot_times(bus, /* require_finished = */ true, &boot);
         if (n < 0)
                 return n;
 
@@ -453,7 +453,7 @@ int verb_plot(int argc, char *argv[], void *userdata) {
                         return n;
         }
 
-        n = acquire_time_data(bus, &times);
+        n = acquire_time_data(bus, /* require_finished = */ true, &times);
         if (n <= 0)
                 return n;
 
index 07843f74bc21b12865f02771772c2c13dd4fb7a1..baee3cedbb531985f5ee9f9fa31374efccbebb34 100644 (file)
@@ -17,7 +17,16 @@ static void subtract_timestamp(usec_t *a, usec_t b) {
         }
 }
 
-int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
+static int log_not_finished(usec_t finish_time) {
+        return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
+                               "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
+                               "Please try again later.\n"
+                               "Hint: Use 'systemctl%s list-jobs' to see active jobs",
+                               finish_time,
+                               arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user");
+}
+
+int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) {
         static const struct bus_properties_map property_map[] = {
                 { "FirmwareTimestampMonotonic",               "t", NULL, offsetof(BootTimes, firmware_time)                 },
                 { "LoaderTimestampMonotonic",                 "t", NULL, offsetof(BootTimes, loader_time)                   },
@@ -44,8 +53,14 @@ int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
         static bool cached = false;
         int r;
 
-        if (cached)
-                goto finish;
+        if (cached) {
+                if (require_finished && times.finish_time <= 0)
+                        return log_not_finished(times.finish_time);
+
+                if (ret)
+                        *ret = &times;
+                return 0;
+        }
 
         assert_cc(sizeof(usec_t) == sizeof(uint64_t));
 
@@ -61,13 +76,8 @@ int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
         if (r < 0)
                 return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
 
-        if (times.finish_time <= 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
-                                       "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
-                                       "Please try again later.\n"
-                                       "Hint: Use 'systemctl%s list-jobs' to see active jobs",
-                                       times.finish_time,
-                                       arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user");
+        if (require_finished && times.finish_time <= 0)
+                return log_not_finished(times.finish_time);
 
         if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && times.security_start_time > 0) {
                 /* security_start_time is set when systemd is not running under container environment. */
@@ -85,7 +95,8 @@ int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
                 times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
                         times.userspace_time = times.security_start_time = times.security_finish_time = 0;
 
-                subtract_timestamp(&times.finish_time, times.reverse_offset);
+                if (times.finish_time > 0)
+                        subtract_timestamp(&times.finish_time, times.reverse_offset);
 
                 subtract_timestamp(&times.generators_start_time, times.reverse_offset);
                 subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
@@ -96,8 +107,8 @@ int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
 
         cached = true;
 
-finish:
-        *ret = &times;
+        if (ret)
+                *ret = &times;
         return 0;
 }
 
@@ -132,7 +143,7 @@ int pretty_boot_time(sd_bus *bus, char **ret) {
         BootTimes *t;
         int r;
 
-        r = acquire_boot_times(bus, &t);
+        r = acquire_boot_times(bus, /* require_finished = */ true, &t);
         if (r < 0)
                 return r;
 
@@ -214,7 +225,7 @@ UnitTimes* unit_times_free_array(UnitTimes *t) {
         return mfree(t);
 }
 
-int acquire_time_data(sd_bus *bus, UnitTimes **out) {
+int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) {
         static const struct bus_properties_map property_map[] = {
                 { "InactiveExitTimestampMonotonic",  "t", NULL, offsetof(UnitTimes, activating)   },
                 { "ActiveEnterTimestampMonotonic",   "t", NULL, offsetof(UnitTimes, activated)    },
@@ -225,12 +236,12 @@ int acquire_time_data(sd_bus *bus, UnitTimes **out) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
-        BootTimes *boot_times = NULL;
+        BootTimes *boot_times;
         size_t c = 0;
         UnitInfo u;
         int r;
 
-        r = acquire_boot_times(bus, &boot_times);
+        r = acquire_boot_times(bus, require_finished, &boot_times);
         if (r < 0)
                 return r;
 
index 02ee16a7141bbfe69509eb974c1aa7b87a810fae..79240745cb36c2dbfde091915f48a262b142c0f4 100644 (file)
@@ -47,10 +47,10 @@ typedef struct UnitTimes {
         usec_t time;
 } UnitTimes;
 
-int acquire_boot_times(sd_bus *bus, BootTimes **ret);
+int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret);
 int pretty_boot_time(sd_bus *bus, char **ret);
 
 UnitTimes* unit_times_free_array(UnitTimes *t);
 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);
 
-int acquire_time_data(sd_bus *bus, UnitTimes **out);
+int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out);
index c02e3b42febb828b092dfcb7786a83952b3cc0a6..de41d7b641d24cc07f2696c97ec6a65bb81cba9c 100644 (file)
@@ -108,6 +108,10 @@ static int parse_argv(int argc, char *argv[]) {
 
         /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does
          * not. */
+
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0)
 
                 switch (c) {
index 318454ca88eb81dee3796cd870ea149583b86130..e8d38279fdf44f28f47b87d22703af4de31a699c 100644 (file)
@@ -77,7 +77,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
         _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
         unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
         bool exists = true, append_trail_slash = false;
-        struct stat previous_stat;
+        struct stat st; /* stat obtained from fd */
         const char *todo;
         int r;
 
@@ -176,7 +176,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
 
                 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
                  * set and doesn't care about any of the other special features we provide either. */
-                r = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+                r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
                 if (r < 0)
                         return -errno;
 
@@ -184,11 +184,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                 return 0;
         }
 
-        if (!buffer) {
-                buffer = strdup(path);
-                if (!buffer)
-                        return -ENOMEM;
-        }
+        buffer = strdup(path);
+        if (!buffer)
+                return -ENOMEM;
 
         /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
          * a relative path would be interpreted relative to the current working directory. */
@@ -220,7 +218,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
         if (fd < 0)
                 return -errno;
 
-        if (fstat(fd, &previous_stat) < 0)
+        if (fstat(fd, &st) < 0)
                 return -errno;
 
         if (flags & CHASE_TRAIL_SLASH)
@@ -229,7 +227,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
         for (todo = buffer;;) {
                 _cleanup_free_ char *first = NULL;
                 _cleanup_close_ int child = -EBADF;
-                struct stat st;
+                struct stat st_child;
                 const char *e;
 
                 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
@@ -250,6 +248,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                 if (path_equal(first, "..")) {
                         _cleanup_free_ char *parent = NULL;
                         _cleanup_close_ int fd_parent = -EBADF;
+                        struct stat st_parent;
 
                         /* If we already are at the top, then going up will not change anything. This is
                          * in-line with how the kernel handles this. */
@@ -260,13 +259,19 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                         if (fd_parent < 0)
                                 return -errno;
 
-                        if (fstat(fd_parent, &st) < 0)
+                        if (fstat(fd_parent, &st_parent) < 0)
                                 return -errno;
 
-                        /* If we opened the same directory, that means we're at the host root directory, so
+                        /* If we opened the same directory, that _may_ indicate that we're at the host root
+                         * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
                          * going up won't change anything. */
-                        if (st.st_dev == previous_stat.st_dev && st.st_ino == previous_stat.st_ino)
-                                continue;
+                        if (stat_inode_same(&st_parent, &st)) {
+                                r = dir_fd_is_root(fd);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        continue;
+                        }
 
                         r = path_extract_directory(done, &parent);
                         if (r >= 0 || r == -EDESTADDRREQ)
@@ -281,18 +286,16 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                         if (flags & CHASE_STEP)
                                 goto chased_one;
 
-                        if (flags & CHASE_SAFE) {
-                                if (unsafe_transition(&previous_stat, &st))
-                                        return log_unsafe_transition(fd, fd_parent, path, flags);
-
-                                previous_stat = st;
-                        }
+                        if (flags & CHASE_SAFE &&
+                            unsafe_transition(&st, &st_parent))
+                                return log_unsafe_transition(fd, fd_parent, path, flags);
 
                         if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
                                 break;
 
+                        /* update fd and stat */
+                        st = st_parent;
                         close_and_replace(fd, fd_parent);
-
                         continue;
                 }
 
@@ -324,19 +327,18 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                                 return r;
                 }
 
-                if (fstat(child, &st) < 0)
+                if (fstat(child, &st_child) < 0)
                         return -errno;
+
                 if ((flags & CHASE_SAFE) &&
-                    unsafe_transition(&previous_stat, &st))
+                    unsafe_transition(&st, &st_child))
                         return log_unsafe_transition(fd, child, path, flags);
 
-                previous_stat = st;
-
                 if ((flags & CHASE_NO_AUTOFS) &&
                     fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
                         return log_autofs_mount_point(child, path, flags);
 
-                if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
+                if (S_ISLNK(st_child.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
                         _cleanup_free_ char *destination = NULL;
 
                         if (flags & CHASE_PROHIBIT_SYMLINKS)
@@ -363,15 +365,12 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                                 if (fd < 0)
                                         return fd;
 
-                                if (flags & CHASE_SAFE) {
-                                        if (fstat(fd, &st) < 0)
-                                                return -errno;
+                                if (fstat(fd, &st) < 0)
+                                        return -errno;
 
-                                        if (unsafe_transition(&previous_stat, &st))
-                                                return log_unsafe_transition(child, fd, path, flags);
-
-                                        previous_stat = st;
-                                }
+                                if (flags & CHASE_SAFE &&
+                                    unsafe_transition(&st_child, &st))
+                                        return log_unsafe_transition(child, fd, path, flags);
 
                                 r = free_and_strdup(&done, need_absolute ? "/" : NULL);
                                 if (r < 0)
@@ -400,11 +399,12 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                         break;
 
                 /* And iterate again, but go one directory further down. */
+                st = st_child;
                 close_and_replace(fd, child);
         }
 
         if (flags & CHASE_PARENT) {
-                r = fd_verify_directory(fd);
+                r = stat_verify_directory(&st);
                 if (r < 0)
                         return r;
         }
@@ -493,18 +493,19 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char **
                         return r;
 
                 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
-                 * end. While we won't resolve the root path we still simplify it. Note that dropping the
-                 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
-                 * anyway. Moreover at the end of this function after processing everything we'll always turn
-                 * the empty string back to "/". */
-                delete_trailing_chars(root, "/");
+                 * end. While we won't resolve the root path we still simplify it. */
                 path_simplify(root);
 
+                assert(path_is_absolute(root));
+                assert(!empty_or_root(root));
+
                 if (flags & CHASE_PREFIX_ROOT) {
                         absolute = path_join(root, path);
                         if (!absolute)
                                 return -ENOMEM;
                 }
+
+                flags |= CHASE_AT_RESOLVE_IN_ROOT;
         }
 
         if (!absolute) {
@@ -524,9 +525,6 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char **
         if (fd < 0)
                 return -errno;
 
-        if (!empty_or_root(root))
-                flags |= CHASE_AT_RESOLVE_IN_ROOT;
-
         r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
         if (r < 0)
                 return r;
index 1314777cb4fdaaeecb7c2aca6f2384eaa8368db6..7e9ebe0685de92d528d503def3f7a62642d264cd 100644 (file)
@@ -22,7 +22,9 @@ typedef enum ChaseFlags {
         CHASE_PROHIBIT_SYMLINKS  = 1 << 9,  /* Refuse all symlinks */
         CHASE_PARENT             = 1 << 10, /* Chase the parent directory of the given path. Note that the
                                              * full path is still stored in ret_path and only the returned
-                                             * file descriptor will point to the parent directory. */
+                                             * file descriptor will point to the parent directory. Note that
+                                             * the result path is the root or '.', then the file descriptor
+                                             * also points to the result path even if this flag is set. */
         CHASE_MKDIR_0755         = 1 << 11, /* Create any missing parent directories in the given path. */
         CHASE_EXTRACT_FILENAME   = 1 << 12, /* Only return the last component of the resolved path */
 } ChaseFlags;
@@ -51,4 +53,3 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int
 int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
 int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
 int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename);
-
index 0a330ecb55137d1e92b855f3066443ebe45dc0de..10e7aaec59d6227b042de6c74eaf5267171503e6 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
@@ -97,7 +107,7 @@ int compress_blob_xz(const void *src, uint64_t src_size,
                 return -ENOBUFS;
 
         *dst_size = out_pos;
-        return COMPRESSION_XZ;
+        return 0;
 #else
         return -EPROTONOSUPPORT;
 #endif
@@ -127,7 +137,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size,
         unaligned_write_le64(dst, src_size);
         *dst_size = r + 8;
 
-        return COMPRESSION_LZ4;
+        return 0;
 #else
         return -EPROTONOSUPPORT;
 #endif
@@ -150,7 +160,7 @@ int compress_blob_zstd(
                 return zstd_ret_to_errno(k);
 
         *dst_size = k;
-        return COMPRESSION_ZSTD;
+        return 0;
 #else
         return -EPROTONOSUPPORT;
 #endif
@@ -616,7 +626,7 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncom
                                           s.total_in, s.total_out,
                                           (double) s.total_out / s.total_in * 100);
 
-                                return COMPRESSION_XZ;
+                                return 0;
                         }
                 }
         }
@@ -707,7 +717,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco
                   total_in, total_out,
                   (double) total_out / total_in * 100);
 
-        return COMPRESSION_LZ4;
+        return 0;
 #else
         return -EPROTONOSUPPORT;
 #endif
@@ -951,7 +961,7 @@ int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unc
                 log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes)",
                           in_bytes, max_bytes - left);
 
-        return COMPRESSION_ZSTD;
+        return 0;
 #else
         return -EPROTONOSUPPORT;
 #endif
index 583b105c66fdeda841f8f096bcb9c6df0ce2028a..1b5c645e3273ae077ecb6752d32db0ebd78d50ad 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,
@@ -60,7 +63,7 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_size);
 int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size);
 int decompress_stream_zstd(int fdf, int fdt, uint64_t max_size);
 
-static inline int compress_blob_explicit(
+static inline int compress_blob(
                 Compression compression,
                 const void *src, uint64_t src_size,
                 void *dst, size_t dst_alloc_size, size_t *dst_size) {
@@ -77,12 +80,6 @@ static inline int compress_blob_explicit(
         }
 }
 
-#define compress_blob(src, src_size, dst, dst_alloc_size, dst_size) \
-        compress_blob_explicit(                                     \
-                DEFAULT_COMPRESSION,                                \
-                src, src_size,                                      \
-                dst, dst_alloc_size, dst_size)
-
 static inline int compress_stream(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
         switch (DEFAULT_COMPRESSION) {
         case COMPRESSION_ZSTD:
index 215e7951dcc6234174c6d78347a8caf389b001ff..c31fe79ebdaaae922cbdb39e2fba4ad5d3318930 100644 (file)
 #include "terminal-util.h"
 
 static int files_add(
-                Hashmap **h,
+                DIR *dir,
+                const char *dirpath,
+                Hashmap **files,
                 Set **masked,
                 const char *suffix,
-                const char *root,
-                unsigned flags,
-                const char *path) {
+                unsigned flags) {
 
-        _cleanup_free_ char *dirpath = NULL;
-        _cleanup_closedir_ DIR *dir = NULL;
         int r;
 
-        assert(h);
+        assert(dir);
+        assert(dirpath);
+        assert(files);
         assert(masked);
-        assert(path);
-
-        r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &dirpath, &dir);
-        if (r == -ENOENT)
-                return 0;
-        if (r < 0)
-                return log_debug_errno(r, "Failed to open directory '%s/%s': %m", empty_or_root(root) ? "" : root, dirpath);
 
         FOREACH_DIRENT(de, dir, return -errno) {
                 _cleanup_free_ char *n = NULL, *p = NULL;
@@ -53,7 +46,7 @@ static int files_add(
                         continue;
 
                 /* Has this file already been found in an earlier directory? */
-                if (hashmap_contains(*h, de->d_name)) {
+                if (hashmap_contains(*files, de->d_name)) {
                         log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
                         continue;
                 }
@@ -107,13 +100,13 @@ static int files_add(
                         return -ENOMEM;
 
                 if ((flags & CONF_FILES_BASENAME))
-                        r = hashmap_ensure_put(h, &string_hash_ops_free, n, n);
+                        r = hashmap_ensure_put(files, &string_hash_ops_free, n, n);
                 else {
                         p = path_join(dirpath, de->d_name);
                         if (!p)
                                 return -ENOMEM;
 
-                        r = hashmap_ensure_put(h, &string_hash_ops_free_free, n, p);
+                        r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p);
                 }
                 if (r < 0)
                         return r;
@@ -127,49 +120,99 @@ static int files_add(
 }
 
 static int base_cmp(char * const *a, char * const *b) {
-        return strcmp(basename(*a), basename(*b));
+        assert(a);
+        assert(b);
+        return path_compare_filename(*a, *b);
 }
 
-static int conf_files_list_strv_internal(
+static int copy_and_sort_files_from_hashmap(Hashmap *fh, char ***ret) {
+        _cleanup_free_ char **sv = NULL;
+        char **files;
+
+        assert(ret);
+
+        sv = hashmap_get_strv(fh);
+        if (!sv)
+                return -ENOMEM;
+
+        /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
+        files = strv_copy(sv);
+        if (!files)
+                return -ENOMEM;
+
+        typesafe_qsort(files, strv_length(files), base_cmp);
+
+        *ret = files;
+        return 0;
+}
+
+int conf_files_list_strv(
                 char ***ret,
                 const char *suffix,
                 const char *root,
                 unsigned flags,
-                char **dirs) {
+                const char * const *dirs) {
 
         _cleanup_hashmap_free_ Hashmap *fh = NULL;
         _cleanup_set_free_ Set *masked = NULL;
-        _cleanup_strv_free_ char **files = NULL;
-        _cleanup_free_ char **sv = NULL;
         int r;
 
         assert(ret);
 
-        /* This alters the dirs string array */
-        if (!path_strv_resolve_uniq(dirs, root))
-                return -ENOMEM;
-
         STRV_FOREACH(p, dirs) {
-                r = files_add(&fh, &masked, suffix, root, flags, *p);
+                _cleanup_closedir_ DIR *dir = NULL;
+                _cleanup_free_ char *path = NULL;
+
+                r = chase_and_opendir(*p, root, CHASE_PREFIX_ROOT, &path, &dir);
+                if (r < 0) {
+                        if (r != -ENOENT)
+                                log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+                        continue;
+                }
+
+                r = files_add(dir, path, &fh, &masked, suffix, flags);
                 if (r == -ENOMEM)
                         return r;
                 if (r < 0)
-                        log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p);
+                        log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
         }
 
-        sv = hashmap_get_strv(fh);
-        if (!sv)
-                return -ENOMEM;
+        return copy_and_sort_files_from_hashmap(fh, ret);
+}
 
-        /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
-        files = strv_copy(sv);
-        if (!files)
-                return -ENOMEM;
+int conf_files_list_strv_at(
+                char ***ret,
+                const char *suffix,
+                int rfd,
+                unsigned flags,
+                const char * const *dirs) {
 
-        typesafe_qsort(files, strv_length(files), base_cmp);
-        *ret = TAKE_PTR(files);
+        _cleanup_hashmap_free_ Hashmap *fh = NULL;
+        _cleanup_set_free_ Set *masked = NULL;
+        int r;
 
-        return 0;
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(ret);
+
+        STRV_FOREACH(p, dirs) {
+                _cleanup_closedir_ DIR *dir = NULL;
+                _cleanup_free_ char *path = NULL;
+
+                r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
+                if (r < 0) {
+                        if (r != -ENOENT)
+                                log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+                        continue;
+                }
+
+                r = files_add(dir, path, &fh, &masked, suffix, flags);
+                if (r == -ENOMEM)
+                        return r;
+                if (r < 0)
+                        log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
+        }
+
+        return copy_and_sort_files_from_hashmap(fh, ret);
 }
 
 int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) {
@@ -240,31 +283,27 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p
         return r;
 }
 
-int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs) {
-        _cleanup_strv_free_ char **copy = NULL;
-
-        assert(ret);
-
-        copy = strv_copy((char**) dirs);
-        if (!copy)
-                return -ENOMEM;
+int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
+        return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
+}
 
-        return conf_files_list_strv_internal(ret, suffix, root, flags, copy);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir) {
+        return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
 }
 
-int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
-        _cleanup_strv_free_ char **dirs = NULL;
+int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+        _cleanup_strv_free_ char **d = NULL;
 
         assert(ret);
 
-        dirs = strv_new(dir);
-        if (!dirs)
+        d = strv_split_nulstr(dirs);
+        if (!d)
                 return -ENOMEM;
 
-        return conf_files_list_strv_internal(ret, suffix, root, flags, dirs);
+        return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
 }
 
-int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs) {
         _cleanup_strv_free_ char **d = NULL;
 
         assert(ret);
@@ -273,7 +312,7 @@ int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, un
         if (!d)
                 return -ENOMEM;
 
-        return conf_files_list_strv_internal(ret, suffix, root, flags, d);
+        return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
 }
 
 int conf_files_list_with_replacement(
index 7774ed705413aff5b8a0b9505e6880f53b09149c..547c2fc137f0debca34a4d0d48c1c70601d14676 100644 (file)
@@ -12,8 +12,11 @@ enum {
 };
 
 int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir);
 int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs);
+int conf_files_list_strv_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char * const *dirs);
 int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs);
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs);
 int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path);
 int conf_files_list_with_replacement(
                 const char *root,
index 01ed443d5f4d10af91fc5baeca7f5b5063187b02..7b3e209ddcac6aa34dcf2a24ba6ed84287789f98 100644 (file)
@@ -359,6 +359,23 @@ int parse_env_filev(
         return r;
 }
 
+int parse_env_file_fdv(int fd, const char *fname, va_list ap) {
+        _cleanup_fclose_ FILE *f = NULL;
+        va_list aq;
+        int r;
+
+        assert(fd >= 0);
+
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
+
+        va_copy(aq, ap);
+        r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
+        va_end(aq);
+        return r;
+}
+
 int parse_env_file_sentinel(
                 FILE *f,
                 const char *fname,
@@ -381,18 +398,13 @@ int parse_env_file_fd_sentinel(
                 const char *fname, /* only used for logging */
                 ...) {
 
-        _cleanup_fclose_ FILE *f = NULL;
         va_list ap;
         int r;
 
         assert(fd >= 0);
 
-        r = fdopen_independent(fd, "re", &f);
-        if (r < 0)
-                return r;
-
         va_start(ap, fname);
-        r = parse_env_filev(f, fname, ap);
+        r = parse_env_file_fdv(fd, fname, ap);
         va_end(ap);
 
         return r;
index fa22d2209c61c7538189205343e0a5fd9afac66f..2465eeddf4d6e3d7de6ac629783a9967d1f88b48 100644 (file)
@@ -8,6 +8,7 @@
 #include "macro.h"
 
 int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
 int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
 #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
 int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
index 3cc4f44bcd925dc45f22d31ea7dcdd27d8a37110..7125e28e1b096f8ecbc414543604083cb28fa7ef 100644 (file)
@@ -903,6 +903,14 @@ int dir_fd_is_root(int dir_fd) {
         if (r < 0)
                 return r;
 
+        r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
+        if (r < 0)
+                return r;
+
+        /* First, compare inode. If these are different, the fd does not point to the root directory "/". */
+        if (!statx_inode_same(&st.sx, &pst.sx))
+                return false;
+
         if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
                 int mntid;
 
@@ -915,10 +923,6 @@ int dir_fd_is_root(int dir_fd) {
                 st.nsx.stx_mask |= STATX_MNT_ID;
         }
 
-        r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
-        if (r < 0)
-                return r;
-
         if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) {
                 int mntid;
 
@@ -931,14 +935,14 @@ int dir_fd_is_root(int dir_fd) {
                 pst.nsx.stx_mask |= STATX_MNT_ID;
         }
 
-        /* If the parent directory is the same inode, the fd points to the root directory "/". We also check
-         * that the mount ids are the same. Otherwise, a construct like the following could be used to trick
-         * us:
+        /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+         * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+         * following could be used to trick us:
          *
          * $ mkdir /tmp/x /tmp/x/y
          * $ mount --bind /tmp/x /tmp/x/y
          */
-        return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
+        return statx_mount_same(&st.nsx, &pst.nsx);
 }
 
 const char *accmode_to_string(int flags) {
index 340f9b48607bc66833a63d9e4b5897ac0d71c491..48ffb4e5e6e5b47d3ceea1d019ce47069f9bcd6f 100644 (file)
  * can detect EOFs. */
 #define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
 
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret) {
-        int r;
-
-        assert(ret);
-
-        r = xfopenat(dir_fd, path, options, flags, ret);
-        if (r < 0)
-                return r;
-
-        (void) __fsetlocking(*ret, FSETLOCKING_BYCALLER);
-
-        return 0;
-}
-
 int fdopen_unlocked(int fd, const char *options, FILE **ret) {
         assert(ret);
 
@@ -267,7 +253,8 @@ int write_string_file_ts_at(
                 const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
-        int q, r, fd;
+        _cleanup_close_ int fd = -EBADF;
+        int q, r;
 
         assert(fn);
         assert(line);
@@ -304,11 +291,9 @@ int write_string_file_ts_at(
                 goto fail;
         }
 
-        r = fdopen_unlocked(fd, "w", &f);
-        if (r < 0) {
-                safe_close(fd);
+        r = take_fdopen_unlocked(&fd, "w", &f);
+        if (r < 0)
                 goto fail;
-        }
 
         if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
                 setvbuf(f, NULL, _IONBF, 0);
@@ -354,18 +339,19 @@ int write_string_filef(
         return write_string_file(fn, p, flags);
 }
 
-int read_one_line_file(const char *fn, char **line) {
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret) {
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
-        assert(fn);
-        assert(line);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(filename);
+        assert(ret);
 
-        r = fopen_unlocked(fn, "re", &f);
+        r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f);
         if (r < 0)
                 return r;
 
-        return read_line(f, LONG_LINE_MAX, line);
+        return read_line(f, LONG_LINE_MAX, ret);
 }
 
 int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) {
@@ -760,62 +746,19 @@ int read_full_file_full(
                 size_t *ret_size) {
 
         _cleanup_fclose_ FILE *f = NULL;
+        XfopenFlags xflags = XFOPEN_UNLOCKED;
         int r;
 
         assert(filename);
         assert(ret_contents);
 
-        r = xfopenat(dir_fd, filename, "re", 0, &f);
-        if (r < 0) {
-                _cleanup_close_ int sk = -EBADF;
-
-                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
-                if (r != -ENXIO)
-                        return r;
-
-                /* If this is enabled, let's try to connect to it */
-                if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
-                        return -ENXIO;
+        if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+            offset == UINT64_MAX)                              /* Seeking is not supported on AF_UNIX sockets */
+                xflags |= XFOPEN_SOCKET;
 
-                /* Seeking is not supported on AF_UNIX sockets */
-                if (offset != UINT64_MAX)
-                        return -ENXIO;
-
-                sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
-                if (sk < 0)
-                        return -errno;
-
-                if (bind_name) {
-                        /* If the caller specified a socket name to bind to, do so before connecting. This is
-                         * useful to communicate some minor, short meta-information token from the client to
-                         * the server. */
-                        union sockaddr_union bsa;
-
-                        r = sockaddr_un_set_path(&bsa.un, bind_name);
-                        if (r < 0)
-                                return r;
-
-                        if (bind(sk, &bsa.sa, r) < 0)
-                                return -errno;
-                }
-
-                r = connect_unix_path(sk, dir_fd, filename);
-                if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
-                        return -ENXIO;
-                if (r < 0)
-                        return r;
-
-                if (shutdown(sk, SHUT_WR) < 0)
-                        return -errno;
-
-                f = fdopen(sk, "r");
-                if (!f)
-                        return -errno;
-
-                TAKE_FD(sk);
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+        if (r < 0)
+                return r;
 
         return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
 }
@@ -922,8 +865,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
 }
 
 DIR *xopendirat(int fd, const char *name, int flags) {
-        int nfd;
-        DIR *d;
+        _cleanup_close_ int nfd = -EBADF;
 
         assert(!(flags & O_CREAT));
 
@@ -934,13 +876,7 @@ DIR *xopendirat(int fd, const char *name, int flags) {
         if (nfd < 0)
                 return NULL;
 
-        d = fdopendir(nfd);
-        if (!d) {
-                safe_close(nfd);
-                return NULL;
-        }
-
-        return d;
+        return take_fdopendir(&nfd);
 }
 
 int fopen_mode_to_flags(const char *mode) {
@@ -989,33 +925,111 @@ int fopen_mode_to_flags(const char *mode) {
         return flags;
 }
 
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
         FILE *f;
 
         /* A combination of fopen() with openat() */
 
-        if (dir_fd == AT_FDCWD && flags == 0) {
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        if (dir_fd == AT_FDCWD && open_flags == 0)
                 f = fopen(path, mode);
-                if (!f)
-                        return -errno;
-        } else {
-                int fd, mode_flags;
+        else {
+                _cleanup_close_ int fd = -EBADF;
+                int mode_flags;
 
                 mode_flags = fopen_mode_to_flags(mode);
                 if (mode_flags < 0)
                         return mode_flags;
 
-                fd = openat(dir_fd, path, mode_flags | flags);
+                fd = openat(dir_fd, path, mode_flags | open_flags);
                 if (fd < 0)
                         return -errno;
 
-                f = fdopen(fd, mode);
-                if (!f) {
-                        safe_close(fd);
+                f = take_fdopen(&fd, mode);
+        }
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+        _cleanup_close_ int sk = -EBADF;
+        FILE *f;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(ret);
+
+        sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        if (sk < 0)
+                return -errno;
+
+        if (bind_name) {
+                /* If the caller specified a socket name to bind to, do so before connecting. This is
+                 * useful to communicate some minor, short meta-information token from the client to
+                 * the server. */
+                union sockaddr_union bsa;
+
+                r = sockaddr_un_set_path(&bsa.un, bind_name);
+                if (r < 0)
+                        return r;
+
+                if (bind(sk, &bsa.sa, r) < 0)
                         return -errno;
-                }
         }
 
+        r = connect_unix_path(sk, dir_fd, path);
+        if (r < 0)
+                return r;
+
+        if (shutdown(sk, SHUT_WR) < 0)
+                return -errno;
+
+        f = take_fdopen(&sk, "r");
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret) {
+
+        FILE *f = NULL;  /* avoid false maybe-uninitialized warning */
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+        if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+                r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+                if (IN_SET(r, -ENOTSOCK, -EINVAL))
+                        return -ENXIO; /* propagate original error if this is not a socket after all */
+        }
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         *ret = f;
         return 0;
 }
@@ -1040,11 +1054,10 @@ int fdopen_independent(int fd, const char *mode, FILE **ret) {
         if (copy_fd < 0)
                 return copy_fd;
 
-        f = fdopen(copy_fd, mode);
+        f = take_fdopen(&copy_fd, mode);
         if (!f)
                 return -errno;
 
-        TAKE_FD(copy_fd);
         *ret = TAKE_PTR(f);
         return 0;
 }
index 0a88a19146f947ceb3b223a059454e5472497f16..769bf394fdfe66f2ce2c1de727d38bda1f740fc1 100644 (file)
@@ -43,10 +43,6 @@ typedef enum {
         READ_FULL_FILE_FAIL_WHEN_LARGER    = 1 << 5, /* fail loading if file is larger than specified size */
 } ReadFullFileFlags;
 
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret);
-static inline int fopen_unlocked(const char *path, const char *options, FILE **ret) {
-        return fopen_unlocked_at(AT_FDCWD, path, options, 0, ret);
-}
 int fdopen_unlocked(int fd, const char *options, FILE **ret);
 int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
 FILE* take_fdopen(int *fd, const char *options);
@@ -71,7 +67,10 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin
 
 int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
 
-int read_one_line_file(const char *filename, char **line);
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret);
+static inline int read_one_line_file(const char *filename, char **ret) {
+        return read_one_line_file_at(AT_FDCWD, filename, ret);
+}
 int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
 static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) {
         return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
@@ -104,7 +103,29 @@ int executable_is_script(const char *path, char **interpreter);
 int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
 
 DIR *xopendirat(int dirfd, const char *name, int flags);
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
+
+typedef enum XfopenFlags {
+        XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */
+        XFOPEN_SOCKET   = 1 << 1, /* also try to open unix socket */
+} XfopenFlags;
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret);
+static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+        return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret);
+}
+static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+        return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret);
+}
+static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) {
+        return fopen_unlocked_at(AT_FDCWD, path, mode, 0, ret);
+}
 
 int fdopen_independent(int fd, const char *mode, FILE **ret);
 
diff --git a/src/basic/getopt-defs.h b/src/basic/getopt-defs.h
new file mode 100644 (file)
index 0000000..3efeb6d
--- /dev/null
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <getopt.h>
+
+#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:"
+
+#define COMMON_GETOPT_ARGS                      \
+        ARG_LOG_LEVEL = 0x100,                  \
+        ARG_LOG_TARGET,                         \
+        ARG_LOG_COLOR,                          \
+        ARG_LOG_LOCATION,                       \
+        ARG_LOG_TIME
+
+#define SYSTEMD_GETOPT_ARGS                     \
+        ARG_UNIT,                               \
+        ARG_SYSTEM,                             \
+        ARG_USER,                               \
+        ARG_TEST,                               \
+        ARG_NO_PAGER,                           \
+        ARG_VERSION,                            \
+        ARG_DUMP_CONFIGURATION_ITEMS,           \
+        ARG_DUMP_BUS_PROPERTIES,                \
+        ARG_BUS_INTROSPECT,                     \
+        ARG_DUMP_CORE,                          \
+        ARG_CRASH_CHVT,                         \
+        ARG_CRASH_SHELL,                        \
+        ARG_CRASH_REBOOT,                       \
+        ARG_CONFIRM_SPAWN,                      \
+        ARG_SHOW_STATUS,                        \
+        ARG_DESERIALIZE,                        \
+        ARG_SWITCHED_ROOT,                      \
+        ARG_DEFAULT_STD_OUTPUT,                 \
+        ARG_DEFAULT_STD_ERROR,                  \
+        ARG_MACHINE_ID,                         \
+        ARG_SERVICE_WATCHDOGS
+
+#define SHUTDOWN_GETOPT_ARGS                    \
+        ARG_EXIT_CODE,                          \
+        ARG_TIMEOUT
+
+#define COMMON_GETOPT_OPTIONS                                           \
+        { "log-level",                required_argument, NULL, ARG_LOG_LEVEL                }, \
+        { "log-target",               required_argument, NULL, ARG_LOG_TARGET               }, \
+        { "log-color",                optional_argument, NULL, ARG_LOG_COLOR                }, \
+        { "log-location",             optional_argument, NULL, ARG_LOG_LOCATION             }, \
+        { "log-time",                 optional_argument, NULL, ARG_LOG_TIME                 }
+
+#define SYSTEMD_GETOPT_OPTIONS                                          \
+        { "unit",                     required_argument, NULL, ARG_UNIT                     }, \
+        { "system",                   no_argument,       NULL, ARG_SYSTEM                   }, \
+        { "user",                     no_argument,       NULL, ARG_USER                     }, \
+        { "test",                     no_argument,       NULL, ARG_TEST                     }, \
+        { "no-pager",                 no_argument,       NULL, ARG_NO_PAGER                 }, \
+        { "help",                     no_argument,       NULL, 'h'                          }, \
+        { "version",                  no_argument,       NULL, ARG_VERSION                  }, \
+        { "dump-configuration-items", no_argument,       NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \
+        { "dump-bus-properties",      no_argument,       NULL, ARG_DUMP_BUS_PROPERTIES      }, \
+        { "bus-introspect",           required_argument, NULL, ARG_BUS_INTROSPECT           }, \
+        { "dump-core",                optional_argument, NULL, ARG_DUMP_CORE                }, \
+        { "crash-chvt",               required_argument, NULL, ARG_CRASH_CHVT               }, \
+        { "crash-shell",              optional_argument, NULL, ARG_CRASH_SHELL              }, \
+        { "crash-reboot",             optional_argument, NULL, ARG_CRASH_REBOOT             }, \
+        { "confirm-spawn",            optional_argument, NULL, ARG_CONFIRM_SPAWN            }, \
+        { "show-status",              optional_argument, NULL, ARG_SHOW_STATUS              }, \
+        { "deserialize",              required_argument, NULL, ARG_DESERIALIZE              }, \
+        { "switched-root",            no_argument,       NULL, ARG_SWITCHED_ROOT            }, \
+        { "default-standard-output",  required_argument, NULL, ARG_DEFAULT_STD_OUTPUT,      }, \
+        { "default-standard-error",   required_argument, NULL, ARG_DEFAULT_STD_ERROR,       }, \
+        { "machine-id",               required_argument, NULL, ARG_MACHINE_ID               }, \
+        { "service-watchdogs",        required_argument, NULL, ARG_SERVICE_WATCHDOGS        }
+
+#define SHUTDOWN_GETOPT_OPTIONS                                         \
+        { "exit-code",                required_argument, NULL, ARG_EXIT_CODE    }, \
+        { "timeout",                  required_argument, NULL, ARG_TIMEOUT      }
index c8b23b10e471c4d2073376792fbd91c075f1e81a..dd8faf23765ce9f193a5eb72082873752aed0512 100644 (file)
 #include "parse-util.h"
 #include "path-util.h"
 #include "stat-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "utf8.h"
 #include "xattr-util.h"
 
+static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
+        [IMAGE_MACHINE]  = "machine",
+        [IMAGE_PORTABLE] = "portable",
+        [IMAGE_SYSEXT]   = "extension",
+        [IMAGE_CONFEXT]  = "confext",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
+
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+        const char *release_file_directory;
+        const char *release_file_path_prefix;
+} image_class_release_info[_IMAGE_CLASS_MAX] = {
+        [IMAGE_SYSEXT] = {
+                .release_file_directory = "/usr/lib/extension-release.d/",
+                .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
+        },
+        [IMAGE_CONFEXT] = {
+                .release_file_directory = "/etc/extension-release.d/",
+                .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
+        }
+};
+
 bool image_name_is_valid(const char *s) {
         if (!filename_is_valid(s))
                 return false;
@@ -36,7 +61,7 @@ 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);
@@ -48,8 +73,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,199 +122,246 @@ 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) {
-        _cleanup_free_ char *q = NULL;
-        int r, fd;
-
-        if (extension) {
-                const char *extension_full_path;
-
-                if (!image_name_is_valid(extension))
-                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "The extension name %s is invalid.", extension);
-
-                extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
-                r = chase(extension_full_path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
-                log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
-
-                /* Cannot find the expected extension-release file? The image filename might have been
-                 * mangled on deployment, so fallback to checking for any file in the extension-release.d
-                 * directory, and return the first one with a user.extension-release xattr instead.
-                 * The user.extension-release.strict xattr is checked to ensure the author of the image
-                 * considers it OK if names do not match. */
-                if (r == -ENOENT) {
-                        _cleanup_free_ char *extension_release_dir_path = NULL;
-                        _cleanup_closedir_ DIR *extension_release_dir = NULL;
-
-                        r = chase_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
-                                              &extension_release_dir_path, &extension_release_dir);
-                        if (r < 0)
-                                return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
-
-                        r = -ENOENT;
-                        FOREACH_DIRENT(de, extension_release_dir, return -errno) {
-                                int k;
-
-                                if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
-                                        continue;
-
-                                const char *image_name = startswith(de->d_name, "extension-release.");
-                                if (!image_name)
-                                        continue;
-
-                                if (!image_name_is_valid(image_name)) {
-                                        log_debug("%s/%s is not a valid extension-release file name, ignoring.",
-                                                  extension_release_dir_path, de->d_name);
-                                        continue;
-                                }
-
-                                /* We already chased the directory, and checked that
-                                 * this is a real file, so we shouldn't fail to open it. */
-                                _cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir),
-                                                                                  de->d_name,
-                                                                                  O_PATH|O_CLOEXEC|O_NOFOLLOW);
-                                if (extension_release_fd < 0)
-                                        return log_debug_errno(errno,
-                                                               "Failed to open extension-release file %s/%s: %m",
-                                                               extension_release_dir_path,
-                                                               de->d_name);
-
-                                /* Really ensure it is a regular file after we open it. */
-                                if (fd_verify_regular(extension_release_fd) < 0) {
-                                        log_debug("%s/%s is not a regular file, ignoring.", extension_release_dir_path, de->d_name);
-                                        continue;
-                                }
-
-                                if (!relax_extension_release_check) {
-                                        k = extension_release_strict_xattr_value(extension_release_fd,
-                                                                                 extension_release_dir_path,
-                                                                                 de->d_name);
-                                        if (k != 0)
-                                                continue;
-                                }
-
-                                /* We already found what we were looking for, but there's another candidate?
-                                 * We treat this as an error, as we want to enforce that there are no ambiguities
-                                 * in case we are in the fallback path. */
-                                if (r == 0) {
-                                        r = -ENOTUNIQ;
-                                        break;
-                                }
-
-                                r = 0; /* Found it! */
-
-                                if (ret_fd)
-                                        fd = TAKE_FD(extension_release_fd);
-
-                                if (ret_path) {
-                                        q = path_join(extension_release_dir_path, de->d_name);
-                                        if (!q)
-                                                return -ENOMEM;
-                                }
-                        }
-                }
-        } else {
-                const char *var = secure_getenv("SYSTEMD_OS_RELEASE");
-                if (var)
-                        r = chase(var, root, 0, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
-                else
-                        FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
-                                r = chase(path, root, CHASE_PREFIX_ROOT, ret_path ? &q : NULL, ret_fd ? &fd : NULL);
-                                if (r != -ENOENT)
-                                        break;
-                        }
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
+        const char *e;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        e = secure_getenv("SYSTEMD_OS_RELEASE");
+        if (e)
+                return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+
+        FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
+                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+                if (r != -ENOENT)
+                        return r;
         }
+
+        return -ENOENT;
+}
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd) {
+        _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
+
+        r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
         if (r < 0)
                 return r;
 
-        if (ret_fd) {
-                int real_fd;
+        if (ret_path) {
+                r = path_prefix_root_cwd(p, root, ret_path);
+                if (r < 0)
+                        return r;
+        }
+
+        if (ret_fd)
+                *ret_fd = TAKE_FD(fd);
+
+        return 0;
+}
+
+int open_extension_release_at(
+                int rfd,
+                ImageClass image_class,
+                const char *extension,
+                bool relax_extension_release_check,
+                char **ret_path,
+                int *ret_fd) {
+
+        _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
+        _cleanup_close_ int fd_found = -EBADF;
+        _cleanup_closedir_ DIR *dir = NULL;
+        bool found = false;
+        const char *p;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
+
+        if (!extension)
+                return open_os_release_at(rfd, ret_path, ret_fd);
+
+        if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
+                return -EINVAL;
+
+        if (!image_name_is_valid(extension))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
+
+        p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
+        r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+        log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
+        if (r != -ENOENT)
+                return r;
+
+        /* Cannot find the expected extension-release file? The image filename might have been mangled on
+         * deployment, so fallback to checking for any file in the extension-release.d directory, and return
+         * the first one with a user.extension-release xattr instead. The user.extension-release.strict
+         * xattr is checked to ensure the author of the image considers it OK if names do not match. */
+
+        p = image_class_release_info[image_class].release_file_directory;
+        r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
+        if (r < 0)
+                return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
+
+        FOREACH_DIRENT(de, dir, return -errno) {
+                _cleanup_close_ int fd = -EBADF;
+                const char *image_name;
 
-                /* Convert the O_PATH fd into a proper, readable one */
-                real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                safe_close(fd);
-                if (real_fd < 0)
-                        return real_fd;
+                if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
+                        continue;
+
+                image_name = startswith(de->d_name, "extension-release.");
+                if (!image_name)
+                        continue;
 
-                *ret_fd = real_fd;
+                if (!image_name_is_valid(image_name)) {
+                        log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
+                        continue;
+                }
+
+                /* We already chased the directory, and checked that this is a real file, so we shouldn't
+                 * fail to open it. */
+                fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+                if (fd < 0)
+                        return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
+
+                /* Really ensure it is a regular file after we open it. */
+                r = fd_verify_regular(fd);
+                if (r < 0) {
+                        log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
+                        continue;
+                }
+
+                if (!relax_extension_release_check &&
+                    extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0)
+                        continue;
+
+                /* We already found what we were looking for, but there's another candidate? We treat this as
+                 * an error, as we want to enforce that there are no ambiguities in case we are in the
+                 * fallback path. */
+                if (found)
+                        return -ENOTUNIQ;
+
+                found = true;
+
+                if (ret_fd)
+                        fd_found = TAKE_FD(fd);
+
+                if (ret_path) {
+                        path_found = path_join(dir_path, de->d_name);
+                        if (!path_found)
+                                return -ENOMEM;
+                }
         }
+        if (!found)
+                return -ENOENT;
 
+        if (ret_fd)
+                *ret_fd = TAKE_FD(fd_found);
         if (ret_path)
-                *ret_path = TAKE_PTR(q);
+                *ret_path = TAKE_PTR(path_found);
 
         return 0;
 }
 
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+int open_extension_release(
+                const char *root,
+                ImageClass image_class,
+                const char *extension,
+                bool relax_extension_release_check,
+                char **ret_path,
+                int *ret_fd) {
+
+        _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
         _cleanup_free_ char *p = NULL;
-        _cleanup_close_ int fd = -EBADF;
-        FILE *f;
         int r;
 
-        if (!ret_file)
-                return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
 
-        r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
+                                      ret_path ? &p : NULL, ret_fd ? &fd : NULL);
         if (r < 0)
                 return r;
 
-        f = take_fdopen(&fd, "r");
-        if (!f)
-                return -errno;
+        if (ret_path) {
+                r = path_prefix_root_cwd(p, root, ret_path);
+                if (r < 0)
+                        return r;
+        }
 
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
-        *ret_file = f;
+        if (ret_fd)
+                *ret_fd = TAKE_FD(fd);
 
         return 0;
 }
 
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
-        _cleanup_fclose_ FILE *f = NULL;
+static int parse_extension_release_atv(
+                int rfd,
+                ImageClass image_class,
+                const char *extension,
+                bool relax_extension_release_check,
+                va_list ap) {
+
+        _cleanup_close_ int fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
 
-        r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
         if (r < 0)
                 return r;
 
-        return parse_env_filev(f, p, ap);
+        return parse_env_file_fdv(fd, p, ap);
 }
 
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int parse_extension_release_at_sentinel(
+                int rfd,
+                ImageClass image_class,
+                bool relax_extension_release_check,
+                const char *extension,
+                ...) {
+
         va_list ap;
         int r;
 
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
         va_start(ap, extension);
-        r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
         va_end(ap);
-
         return r;
 }
 
-int _parse_os_release(const char *root, ...) {
+int parse_extension_release_sentinel(
+                const char *root,
+                ImageClass image_class,
+                bool relax_extension_release_check,
+                const char *extension,
+                ...) {
+
+        _cleanup_close_ int rfd = -EBADF;
         va_list ap;
         int r;
 
-        va_start(ap, root);
-        r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
-        va_end(ap);
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
 
+        va_start(ap, extension);
+        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
+        va_end(ap);
         return r;
 }
 
-int load_os_release_pairs(const char *root, char ***ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *p = NULL;
-        int r;
-
-        r = fopen_os_release(root, &p, &f);
-        if (r < 0)
-                return r;
-
-        return load_env_file_pairs(f, p, ret);
-}
-
 int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
         _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
         int r;
@@ -318,16 +391,16 @@ 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) {
-        _cleanup_fclose_ FILE *f = NULL;
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
+        _cleanup_close_ int fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
 
-        r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+        r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
         if (r < 0)
                 return r;
 
-        return load_env_file_pairs(f, p, ret);
+        return load_env_file_pairs_fd(fd, p, ret);
 }
 
 int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
index 3bafeaeb92d2031a9c2d599c5de48ecfeed0d308..480f71e614c769eb1506c4b37a62327c3960842a 100644 (file)
@@ -6,33 +6,49 @@
 
 #include "time-util.h"
 
+typedef enum ImageClass {
+        IMAGE_MACHINE,
+        IMAGE_PORTABLE,
+        IMAGE_SYSEXT,
+        IMAGE_CONFEXT,
+        _IMAGE_CLASS_MAX,
+        _IMAGE_CLASS_INVALID = -EINVAL,
+} ImageClass;
+
+const char* image_class_to_string(ImageClass cl) _const_;
+ImageClass image_class_from_string(const char *s) _pure_;
+
 /* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
  * in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
 
 bool image_name_is_valid(const char *s) _pure_;
 
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check);
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
 static inline int path_is_os_tree(const char *path) {
-        return path_is_extension_tree(path, NULL, false);
-}
-
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
-static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
-        return open_extension_release(root, NULL, false, ret_path, ret_fd);
+        return path_is_extension_tree(_IMAGE_CLASS_INVALID, path, NULL, false);
 }
 
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
-static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
-        return fopen_extension_release(root, NULL, false, ret_path, ret_file);
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_extension_release_at(int rfd, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_os_release(const char *root, char **ret_path, int *ret_fd);
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd);
+
+int parse_extension_release_sentinel(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release(root, image_class, extension, relax_extension_release_check, ...) \
+        parse_extension_release_sentinel(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release(root, ...)                                     \
+        parse_extension_release_sentinel(root, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int parse_extension_release_at_sentinel(int rfd, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release_at(rfd, image_class, extension, relax_extension_release_check, ...) \
+        parse_extension_release_at_sentinel(rfd, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release_at(rfd, ...)                                     \
+        parse_extension_release_at_sentinel(rfd, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
+static inline int load_os_release_pairs(const char *root, char ***ret) {
+        return load_extension_release_pairs(root, _IMAGE_CLASS_INVALID, NULL, false, ret);
 }
-
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
-int _parse_os_release(const char *root, ...) _sentinel_;
-#define parse_extension_release(root, relax_extension_release_check, extension, ...) _parse_extension_release(root, relax_extension_release_check, extension, __VA_ARGS__, NULL)
-#define parse_os_release(root, ...) _parse_os_release(root, __VA_ARGS__, NULL)
-
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret);
-int load_os_release_pairs(const char *root, char ***ret);
 int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
 
 int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol);
index ae0b25d1551024ae0415cf476e5b22d71bbf0432..fa2e26789f39cbe1a63701dc094998a9340601e2 100644 (file)
@@ -100,6 +100,34 @@ int path_make_absolute_cwd(const char *p, char **ret) {
         return 0;
 }
 
+int path_prefix_root_cwd(const char *p, const char *root, char **ret) {
+        _cleanup_free_ char *root_abs = NULL;
+        char *c;
+        int r;
+
+        assert(p);
+        assert(ret);
+
+        /* Unlike path_make_absolute(), this always prefixes root path if specified.
+         * The root path is always simplified, but the provided path will not.
+         * This is useful for prefixing the result of chaseat(). */
+
+        if (empty_or_root(root))
+                return path_make_absolute_cwd(p, ret);
+
+        r = path_make_absolute_cwd(root, &root_abs);
+        if (r < 0)
+                return r;
+
+        path_simplify(root_abs);
+        c = path_join(root_abs, p);
+        if (!c)
+                return -ENOMEM;
+
+        *ret = c;
+        return 0;
+}
+
 int path_make_relative(const char *from, const char *to, char **ret) {
         _cleanup_free_ char *result = NULL;
         unsigned n_parents;
@@ -491,25 +519,33 @@ bool path_equal_or_files_same(const char *a, const char *b, int flags) {
         return path_equal(a, b) || files_same(a, b, flags) > 0;
 }
 
-bool path_equal_filename(const char *a, const char *b) {
-        _cleanup_free_ char *a_basename = NULL, *b_basename = NULL;
-        int r;
+int path_compare_filename(const char *a, const char *b) {
+        _cleanup_free_ char *fa = NULL, *fb = NULL;
+        int r, j, k;
 
-        assert(a);
-        assert(b);
+        /* Order NULL before non-NULL */
+        r = CMP(!!a, !!b);
+        if (r != 0)
+                return r;
 
-        r = path_extract_filename(a, &a_basename);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse basename of %s: %m", a);
-                return false;
-        }
-        r = path_extract_filename(b, &b_basename);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse basename of %s: %m", b);
-                return false;
-        }
+        j = path_extract_filename(a, &fa);
+        k = path_extract_filename(b, &fb);
 
-        return path_equal(a_basename, b_basename);
+        /* When one of paths is "." or root, then order it earlier. */
+        r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL);
+        if (r != 0)
+                return r;
+
+        /* When one of paths is invalid (or we get OOM), order invalid path after valid one. */
+        r = CMP(j < 0, k < 0);
+        if (r != 0)
+                return r;
+
+        /* fallback to use strcmp() if both paths are invalid. */
+        if (j < 0)
+                return strcmp(a, b);
+
+        return strcmp(fa, fb);
 }
 
 char* path_extend_internal(char **x, ...) {
@@ -893,6 +929,8 @@ static const char *skip_slash_or_dot_backward(const char *path, const char *q) {
                         continue;
                 if (q > path && strneq(q - 1, "/.", 2))
                         continue;
+                if (q == path && *q == '.')
+                        continue;
                 break;
         }
         return q;
@@ -917,6 +955,12 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char *
         *           ret: "bbbbb/cc//././"
         *           return value: 5 (== strlen("bbbbb"))
         *
+        *   Input:  path: "//.//aaa///bbbbb/cc//././"
+        *           next: "///bbbbb/cc//././"
+        *   Output: next: "//.//aaa///bbbbb/cc//././" (next == path)
+        *           ret: "aaa///bbbbb/cc//././"
+        *           return value: 3 (== strlen("aaa"))
+        *
         *   Input:  path: "/", ".", "", or NULL
         *   Output: next: equivalent to path
         *           ret: NULL
index 56f01f41d8dfc409e16b67907d86faad0f8ba68c..a0af9de6742c5ca3bab7d360a2a81020ec3864c9 100644 (file)
@@ -60,21 +60,25 @@ int path_split_and_make_absolute(const char *p, char ***ret);
 char* path_make_absolute(const char *p, const char *prefix);
 int safe_getcwd(char **ret);
 int path_make_absolute_cwd(const char *p, char **ret);
+int path_prefix_root_cwd(const char *p, const char *root, char **ret);
 int path_make_relative(const char *from, const char *to, char **ret);
 int path_make_relative_parent(const char *from_child, const char *to, char **ret);
 char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
 static inline char* path_startswith(const char *path, const char *prefix) {
         return path_startswith_full(path, prefix, true);
 }
-int path_compare(const char *a, const char *b) _pure_;
 
+int path_compare(const char *a, const char *b) _pure_;
 static inline bool path_equal(const char *a, const char *b) {
         return path_compare(a, b) == 0;
 }
 
+int path_compare_filename(const char *a, const char *b);
+static inline bool path_equal_filename(const char *a, const char *b) {
+        return path_compare_filename(a, b) == 0;
+}
+
 bool path_equal_or_files_same(const char *a, const char *b, int flags);
-/* Compares only the last portion of the input paths, ie: the filenames */
-bool path_equal_filename(const char *a, const char *b);
 
 char* path_extend_internal(char **x, ...);
 #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
index de1f66635a956d89e6a0a878c1aa29a5186a9794..39e9f2c668c938a87f3d33efe77865841756cd24 100644 (file)
@@ -7,15 +7,76 @@
 #include "efivars.h"
 #include "extract-word.h"
 #include "fileio.h"
+#include "getopt-defs.h"
 #include "initrd-util.h"
 #include "macro.h"
 #include "parse-util.h"
 #include "proc-cmdline.h"
 #include "process-util.h"
-#include "special.h"
 #include "string-util.h"
+#include "strv.h"
 #include "virt.h"
 
+int proc_cmdline_filter_pid1_args(
+                char **argv,   /* input, may be reordered by this function. */
+                char ***ret) {
+
+        enum {
+                COMMON_GETOPT_ARGS,
+                SYSTEMD_GETOPT_ARGS,
+                SHUTDOWN_GETOPT_ARGS,
+        };
+
+        static const struct option options[] = {
+                COMMON_GETOPT_OPTIONS,
+                SYSTEMD_GETOPT_OPTIONS,
+                SHUTDOWN_GETOPT_OPTIONS,
+                {}
+        };
+
+        int saved_optind, saved_opterr, saved_optopt, argc;
+        char *saved_optarg;
+        char **filtered;
+        size_t idx;
+
+        assert(argv);
+        assert(ret);
+
+        /* Backup global variables. */
+        saved_optind = optind;
+        saved_opterr = opterr;
+        saved_optopt = optopt;
+        saved_optarg = optarg;
+
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
+         * the GNU extensions, but might be used previously. Hence, we need to always reset it. */
+        optind = 0;
+
+        /* Do not print an error message. */
+        opterr = 0;
+
+        /* Filter out all known options. */
+        argc = strv_length(argv);
+        while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
+                ;
+
+        idx = optind;
+
+        /* Restore global variables. */
+        optind = saved_optind;
+        opterr = saved_opterr;
+        optopt = saved_optopt;
+        optarg = saved_optarg;
+
+        filtered = strv_copy(strv_skip(argv, idx));
+        if (!filtered)
+                return -ENOMEM;
+
+        *ret = filtered;
+        return 0;
+}
+
 int proc_cmdline(char **ret) {
         const char *e;
 
@@ -40,71 +101,86 @@ int proc_cmdline(char **ret) {
                 return read_one_line_file("/proc/cmdline", ret);
 }
 
-static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
-        const char *q = *p;
+static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
+        const char *e;
         int r;
 
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                const char *c;
+        assert(ret);
 
-                r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+        /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
+        e = secure_getenv("SYSTEMD_PROC_CMDLINE");
+        if (e)
+                return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+
+        if (detect_container() > 0) {
+                _cleanup_strv_free_ char **args = NULL;
+
+                r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
                 if (r < 0)
                         return r;
-                if (r == 0)
-                        break;
 
-                /* Filter out arguments that are intended only for the initrd */
-                c = startswith(word, "rd.");
-                if (c) {
-                        if (!in_initrd())
-                                continue;
+                if (filter_pid1_args)
+                        return proc_cmdline_filter_pid1_args(args, ret);
 
-                        if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
-                                r = free_and_strdup(&word, c);
-                                if (r < 0)
-                                        return r;
-                        }
+                *ret = TAKE_PTR(args);
+                return 0;
 
-                } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
-                        continue; /* And optionally filter out arguments that are intended only for the host */
+        } else {
+                _cleanup_free_ char *s = NULL;
+
+                r = read_one_line_file("/proc/cmdline", &s);
+                if (r < 0)
+                        return r;
 
-                *p = q;
-                *ret_word = TAKE_PTR(word);
-                return 1;
+                return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
         }
+}
 
-        *p = q;
-        *ret_word = NULL;
-        return 0;
+int proc_cmdline_strv(char ***ret) {
+        return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
+}
+
+static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
+        char *c;
+
+        c = startswith(word, "rd.");
+        if (c) {
+                /* Filter out arguments that are intended only for the initrd */
+
+                if (!in_initrd())
+                        return NULL;
+
+                if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
+                        return c;
+
+        } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
+                /* And optionally filter out arguments that are intended only for the host */
+                return NULL;
+
+        return (char*) word;
 }
 
-static int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
-        const char *p;
+static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
         int r;
 
         assert(parse_item);
 
-        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
-         * clear. */
+        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
+         * make this clear. */
         assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
 
-        p = line;
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                char *value;
+        STRV_FOREACH(word, args) {
+                char *key, *value;
 
-                r = proc_cmdline_extract_first(&p, &word, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
+                key = mangle_word(*word, flags);
+                if (!key)
+                        continue;
 
-                value = strchr(word, '=');
+                value = strchr(key, '=');
                 if (value)
-                        *(value++) = 0;
+                        *(value++) = '\0';
 
-                r = parse_item(word, value, data);
+                r = parse_item(key, value, data);
                 if (r < 0)
                         return r;
         }
@@ -113,7 +189,7 @@ static int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse
 }
 
 int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
-        _cleanup_free_ char *line = NULL;
+        _cleanup_strv_free_ char **args = NULL;
         int r;
 
         assert(parse_item);
@@ -121,24 +197,30 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineF
         /* We parse the EFI variable first, because later settings have higher priority. */
 
         if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+                _cleanup_free_ char *line = NULL;
+
                 r = systemd_efi_options_variable(&line);
                 if (r < 0) {
                         if (r != -ENODATA)
                                 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
                 } else {
-                        r = proc_cmdline_parse_given(line, parse_item, data, flags);
+                        r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
                         if (r < 0)
                                 return r;
 
-                        line = mfree(line);
+                        r = proc_cmdline_parse_strv(args, parse_item, data, flags);
+                        if (r < 0)
+                                return r;
+
+                        args = strv_free(args);
                 }
         }
 
-        r = proc_cmdline(&line);
+        r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
         if (r < 0)
                 return r;
 
-        return proc_cmdline_parse_given(line, parse_item, data, flags);
+        return proc_cmdline_parse_strv(args, parse_item, data, flags);
 }
 
 static bool relaxed_equal_char(char a, char b) {
@@ -173,24 +255,19 @@ bool proc_cmdline_key_streq(const char *x, const char *y) {
         return true;
 }
 
-static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
+static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
         _cleanup_free_ char *v = NULL;
         bool found = false;
-        const char *p;
         int r;
 
-        assert(line);
         assert(key);
 
-        p = line;
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
+        STRV_FOREACH(p, args) {
+                const char *word;
 
-                r = proc_cmdline_extract_first(&p, &word, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
+                word = mangle_word(*p, flags);
+                if (!word)
+                        continue;
 
                 if (ret_value) {
                         const char *e;
@@ -224,6 +301,7 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
 }
 
 int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
+        _cleanup_strv_free_ char **args = NULL;
         _cleanup_free_ char *line = NULL, *v = NULL;
         int r;
 
@@ -247,14 +325,14 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
         if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
                 return -EINVAL;
 
-        r = proc_cmdline(&line);
+        r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
         if (r < 0)
                 return r;
 
         if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
-                return cmdline_get_key(line, key, flags, ret_value);
+                return cmdline_get_key(args, key, flags, ret_value);
 
-        r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
+        r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL);
         if (r < 0)
                 return r;
         if (r > 0) {
@@ -264,7 +342,6 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
                 return r;
         }
 
-        line = mfree(line);
         r = systemd_efi_options_variable(&line);
         if (r == -ENODATA) {
                 if (ret_value)
@@ -275,7 +352,12 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
         if (r < 0)
                 return r;
 
-        return cmdline_get_key(line, key, flags, ret_value);
+        args = strv_free(args);
+        r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+        if (r < 0)
+                return r;
+
+        return cmdline_get_key(args, key, flags, ret_value);
 }
 
 int proc_cmdline_get_bool(const char *key, bool *ret) {
@@ -303,75 +385,82 @@ int proc_cmdline_get_bool(const char *key, bool *ret) {
         return 1;
 }
 
-int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
-        _cleanup_free_ char *line = NULL;
-        bool processing_efi = true;
-        const char *p;
-        va_list ap;
+static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
         int r, ret = 0;
 
-        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
-         * this clear. */
-        assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
+        for (;;) {
+                char **v;
+                const char *k, *e;
 
-        /* This call may clobber arguments on failure! */
+                k = va_arg(ap, const char*);
+                if (!k)
+                        break;
 
-        if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
-                r = systemd_efi_options_variable(&line);
-                if (r < 0 && r != -ENODATA)
-                        log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
-        }
+                assert_se(v = va_arg(ap, char**));
 
-        p = line;
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
+                STRV_FOREACH(p, args) {
+                        const char *word;
 
-                r = proc_cmdline_extract_first(&p, &word, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        /* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */
-                        if (processing_efi) {
-                                processing_efi = false;
+                        word = mangle_word(*p, flags);
+                        if (!word)
+                                continue;
 
-                                line = mfree(line);
-                                r = proc_cmdline(&line);
+                        e = proc_cmdline_key_startswith(word, k);
+                        if (e && *e == '=') {
+                                r = free_and_strdup(v, e + 1);
                                 if (r < 0)
                                         return r;
 
-                                p = line;
-                                continue;
+                                ret++;
                         }
-
-                        break;
                 }
+        }
+
+        return ret;
+}
+
+int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
+        _cleanup_strv_free_ char **args = NULL;
+        int r, ret = 0;
+        va_list ap;
 
-                va_start(ap, flags);
+        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
+         * this clear. */
+        assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
 
-                for (;;) {
-                        char **v;
-                        const char *k, *e;
+        /* This call may clobber arguments on failure! */
 
-                        k = va_arg(ap, const char*);
-                        if (!k)
-                                break;
+        if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+                _cleanup_free_ char *line = NULL;
 
-                        assert_se(v = va_arg(ap, char**));
+                r = systemd_efi_options_variable(&line);
+                if (r < 0 && r != -ENODATA)
+                        log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
+                if (r >= 0) {
+                        r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+                        if (r < 0)
+                                return r;
 
-                        e = proc_cmdline_key_startswith(word, k);
-                        if (e && *e == '=') {
-                                r = free_and_strdup(v, e + 1);
-                                if (r < 0) {
-                                        va_end(ap);
-                                        return r;
-                                }
+                        va_start(ap, flags);
+                        r = cmdline_get_key_ap(flags, args, ap);
+                        va_end(ap);
+                        if (r < 0)
+                                return r;
 
-                                ret++;
-                        }
+                        ret = r;
+                        args = strv_free(args);
                 }
-
-                va_end(ap);
         }
 
-        return ret;
+        r = proc_cmdline_strv(&args);
+        if (r < 0)
+                return r;
+
+        va_start(ap, flags);
+        r = cmdline_get_key_ap(flags, args, ap);
+        va_end(ap);
+        if (r < 0)
+                return r;
+
+        return ret + r;
 }
index 8650e293ced540d673066568b73c0907b2881440..a64d7757bcc8d1176450d2f1dc2ec538b1c83d05 100644 (file)
@@ -14,7 +14,10 @@ typedef enum ProcCmdlineFlags {
 
 typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data);
 
+int proc_cmdline_filter_pid1_args(char **argv, char ***ret);
+
 int proc_cmdline(char **ret);
+int proc_cmdline_strv(char ***ret);
 
 int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, ProcCmdlineFlags flags);
 
index 551e98b4272dfacd0fc8a74f9b9ea0beb560f81c..cbf00a173a43cb46050280ea254cd143e9999f0f 100644 (file)
@@ -156,21 +156,32 @@ bool is_nologin_shell(const char *shell) {
                            "/usr/bin/true");
 }
 
-const char* default_root_shell(const char *root) {
+const char* default_root_shell_at(int rfd) {
         /* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
          * will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
          * or any access errors. */
 
-        int r = chase(DEFAULT_USER_SHELL, root, CHASE_PREFIX_ROOT, NULL, NULL);
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
         if (r < 0 && r != -ENOENT)
-                log_debug_errno(r, "Failed to look up shell '%s%s%s': %m",
-                                strempty(root), root ? "/" : "", DEFAULT_USER_SHELL);
+                log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
         if (r > 0)
                 return DEFAULT_USER_SHELL;
 
         return "/bin/sh";
 }
 
+const char *default_root_shell(const char *root) {
+        _cleanup_close_ int rfd = -EBADF;
+
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return "/bin/sh";
+
+        return default_root_shell_at(rfd);
+}
+
 static int synthesize_user_creds(
                 const char **username,
                 uid_t *uid, gid_t *gid,
index f7ff35d0e07041e9c31a7a96b26f188c1991d38a..5aca6307e05bff7567efbc3cce20dad0a98ca42e 100644 (file)
@@ -131,6 +131,7 @@ int putsgent_sane(const struct sgrp *sg, FILE *stream);
 #endif
 
 bool is_nologin_shell(const char *shell);
+const char* default_root_shell_at(int rfd);
 const char* default_root_shell(const char *root);
 
 int is_this_me(const char *username);
index dc8596fa05aa4c7843a75782044cb5bd767016b0..2dd50360ecd471a21a1600fcf566a6a39e252f7e 100644 (file)
@@ -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);
 
index 718bac5ab24afdaaf658720333ef936532892fd2..8808c30569aef1bccb2ca43756f9b08f9a393086 100644 (file)
@@ -1,5 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <fcntl.h>
+
 #include "alloc-util.h"
 #include "bootctl-uki.h"
 #include "kernel-image.h"
@@ -8,7 +10,7 @@ int verb_kernel_identify(int argc, char *argv[], void *userdata) {
         KernelImageType t;
         int r;
 
-        r = inspect_kernel(argv[1], &t, NULL, NULL, NULL);
+        r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL);
         if (r < 0)
                 return r;
 
@@ -21,7 +23,7 @@ int verb_kernel_inspect(int argc, char *argv[], void *userdata) {
         KernelImageType t;
         int r;
 
-        r = inspect_kernel(argv[1], &t, &cmdline, &uname, &pname);
+        r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname);
         if (r < 0)
                 return r;
 
index e6e7eed3bcac6a8f661c386428862e41cba3014d..67ce00838d4deaec604b63bf4555a0ef42c49a29 100644 (file)
@@ -213,15 +213,16 @@ efi_c_args_alt = [efi_c_args, '-DEFI_MACHINE_TYPE_NAME="' + efi_arch_alt + '"']
 efi_c_ld_args_primary = efi_c_ld_args
 efi_c_ld_args_alt = efi_c_ld_args
 
-efi_c_args_primary += {
+efi_arch_c_args = {
         'aarch64' : ['-mgeneral-regs-only'],
         'arm'     : ['-mgeneral-regs-only'],
         'x86_64'  : ['-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'],
-        'x86'     : ['-march=i686', '-mgeneral-regs-only'],
-}.get(host_machine.cpu_family(), [])
+        'x86'     : ['-march=i686', '-mgeneral-regs-only', '-malign-double'],
+}
+efi_c_args_primary += efi_arch_c_args.get(host_machine.cpu_family(), [])
 
 if efi_arch_alt == 'ia32'
-        efi_c_args_alt += ['-m32', '-march=i686', '-mgeneral-regs-only']
+        efi_c_args_alt += ['-m32', efi_arch_c_args['x86']]
         efi_c_ld_args_alt += '-m32'
 endif
 
index c321062996c5268658badcddbbdd684af4705253..9df7b4a2d4e833e1e252707c5c140027c93524b0 100644 (file)
@@ -168,10 +168,22 @@ void hexdump(const char16_t *prefix, const void *data, size_t size);
 #  define notify_debugger(i, w)
 #endif
 
+/* On x86 the compiler assumes a different incoming stack alignment than what we get.
+ * This will cause long long variables to be misaligned when building with
+ * '-mlong-double' (for correct struct layouts). Normally, the compiler realigns the
+ * stack itself on entry, but we have to do this ourselves here as the compiler does
+ * not know that this is our entry point. */
+#ifdef __i386__
+#  define _realign_stack_ __attribute__((force_align_arg_pointer))
+#else
+#  define _realign_stack_
+#endif
+
 #define DEFINE_EFI_MAIN_FUNCTION(func, identity, wait_for_debugger)                    \
         EFI_SYSTEM_TABLE *ST;                                                          \
         EFI_BOOT_SERVICES *BS;                                                         \
         EFI_RUNTIME_SERVICES *RT;                                                      \
+        _realign_stack_                                                                \
         EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table);  \
         EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) { \
                 ST = system_table;                                                     \
index 37ba05680f58a1214e99d99b9c186abed3d2d094..90f20c05a16d7bf788afed85c60c65abe13507b8 100644 (file)
@@ -2307,6 +2307,7 @@ static int help(void) {
                "     --verbose             Show result values in long format\n"
                "     --json=MODE           Output as JSON\n"
                "  -j                       Same as --json=pretty on tty, --json=short otherwise\n"
+               "     --xml-interface       Dump the XML description in introspect command\n"
                "     --expect-reply=BOOL   Expect a method call reply\n"
                "     --auto-start=BOOL     Auto-start destination service\n"
                "     --allow-interactive-authorization=BOOL\n"
index 32780c606a8bf7506968d5d8707b3610ecc4f301..a34b0aa604b0da2d2d8dbc873afefa744e4a1feb 100644 (file)
@@ -93,6 +93,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 1);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "-hkalM:u::xc", options, NULL)) >= 0)
 
                 switch (c) {
index 2cb9efc14e18d5b5526b460c231b0a9998cbd09b..8fdca2da22b3633b863ac0521393eb47eaccea68 100644 (file)
@@ -2234,7 +2234,7 @@ static int install_error(
                 InstallChange *changes,
                 size_t n_changes) {
 
-        int r;
+        CLEANUP_ARRAY(changes, n_changes, install_changes_free);
 
         for (size_t i = 0; i < n_changes; i++)
 
@@ -2246,83 +2246,65 @@ static int install_error(
 
                 case -EEXIST:
                         if (changes[i].source)
-                                r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
-                                                      "File %s already exists and is a symlink to %s.",
-                                                      changes[i].path, changes[i].source);
-                        else
-                                r = sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
-                                                      "File %s already exists.",
-                                                      changes[i].path);
-                        goto found;
+                                return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
+                                                         "File %s already exists and is a symlink to %s.",
+                                                         changes[i].path, changes[i].source);
+                        return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS,
+                                                 "File %s already exists.",
+                                                 changes[i].path);
 
                 case -ERFKILL:
-                        r = sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED,
-                                              "Unit file %s is masked.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED,
+                                                 "Unit file %s is masked.", changes[i].path);
 
                 case -EADDRNOTAVAIL:
-                        r = sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED,
-                                              "Unit %s is transient or generated.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED,
+                                                 "Unit %s is transient or generated.", changes[i].path);
 
                 case -ETXTBSY:
-                        r = sd_bus_error_setf(error, BUS_ERROR_UNIT_BAD_PATH,
-                                              "File %s is under the systemd unit hierarchy already.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_UNIT_BAD_PATH,
+                                                 "File %s is under the systemd unit hierarchy already.", changes[i].path);
 
                 case -EBADSLT:
-                        r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
-                                              "Invalid specifier in %s.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+                                                 "Invalid specifier in %s.", changes[i].path);
 
                 case -EIDRM:
-                        r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
-                                              "Destination unit %s is a non-template unit.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+                                                 "Destination unit %s is a non-template unit.", changes[i].path);
 
                 case -EUCLEAN:
-                        r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
-                                              "\"%s\" is not a valid unit name.",
-                                              changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+                                                 "\"%s\" is not a valid unit name.",
+                                                 changes[i].path);
 
                 case -ELOOP:
-                        r = sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED,
-                                              "Refusing to operate on alias name or linked unit file: %s",
-                                              changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_UNIT_LINKED,
+                                                 "Refusing to operate on alias name or linked unit file: %s",
+                                                 changes[i].path);
 
                 case -EXDEV:
                         if (changes[i].source)
-                                r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
-                                                      "Cannot alias %s as %s.",
-                                                      changes[i].source, changes[i].path);
-                        else
-                                r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
-                                                      "Invalid unit reference %s.", changes[i].path);
-                        goto found;
+                                return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+                                                         "Cannot alias %s as %s.",
+                                                         changes[i].source, changes[i].path);
+                        return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+                                                 "Invalid unit reference %s.", changes[i].path);
 
                 case -ENOENT:
-                        r = sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
-                                              "Unit file %s does not exist.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
+                                                 "Unit file %s does not exist.", changes[i].path);
 
                 case -EUNATCH:
-                        r = sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
-                                              "Cannot resolve specifiers in %s.", changes[i].path);
-                        goto found;
+                        return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING,
+                                                 "Cannot resolve specifiers in %s.", changes[i].path);
 
                 default:
                         assert(changes[i].type < 0); /* other errors */
-                        r = sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path);
-                        goto found;
+                        return sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path);
                 }
 
-        r = c < 0 ? c : -EINVAL;
-
- found:
-        install_changes_free(changes, n_changes);
-        return r;
+        return c < 0 ? c : -EINVAL;
 }
 
 static int reply_install_changes_and_free(
@@ -2726,6 +2708,8 @@ static int method_get_unit_file_links(sd_bus_message *message, void *userdata, s
         const char *name;
         int runtime, r;
 
+        CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
         r = sd_bus_message_read(message, "sb", &name, &runtime);
         if (r < 0)
                 return r;
@@ -2741,27 +2725,21 @@ static int method_get_unit_file_links(sd_bus_message *message, void *userdata, s
         r = unit_file_disable(m->runtime_scope,
                               UNIT_FILE_DRY_RUN | (runtime ? UNIT_FILE_RUNTIME : 0),
                               NULL, STRV_MAKE(name), &changes, &n_changes);
-        if (r < 0) {
-                log_error_errno(r, "Failed to get file links for %s: %m", name);
-                goto finish;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to get file links for %s: %m", name);
 
         for (i = 0; i < n_changes; i++)
                 if (changes[i].type == INSTALL_CHANGE_UNLINK) {
                         r = sd_bus_message_append(reply, "s", changes[i].path);
                         if (r < 0)
-                                goto finish;
+                                return r;
                 }
 
         r = sd_bus_message_close_container(reply);
         if (r < 0)
-                goto finish;
-
-        r = sd_bus_send(NULL, reply, NULL);
+                return r;
 
-finish:
-        install_changes_free(changes, n_changes);
-        return r;
+        return sd_bus_send(NULL, reply, NULL);
 }
 
 static int method_get_job_waiting(sd_bus_message *message, void *userdata, sd_bus_error *error) {
index cd11683407edbfe890dfc689d12941ed0667da61..8b09794089ad9d10d597dff69b8203ef85bf1f7b 100644 (file)
@@ -3446,7 +3446,7 @@ static int compile_bind_mounts(
                 char ***ret_empty_directories) {
 
         _cleanup_strv_free_ char **empty_directories = NULL;
-        BindMount *bind_mounts;
+        BindMount *bind_mounts = NULL;
         size_t n, h = 0;
         int r;
 
@@ -3456,6 +3456,8 @@ static int compile_bind_mounts(
         assert(ret_n_bind_mounts);
         assert(ret_empty_directories);
 
+        CLEANUP_ARRAY(bind_mounts, h, bind_mount_free_many);
+
         n = context->n_bind_mounts;
         for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
                 if (!params->prefix[t])
@@ -3478,24 +3480,19 @@ static int compile_bind_mounts(
 
         for (size_t i = 0; i < context->n_bind_mounts; i++) {
                 BindMount *item = context->bind_mounts + i;
-                char *s, *d;
+                _cleanup_free_ char *s = NULL, *d = NULL;
 
                 s = strdup(item->source);
-                if (!s) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                if (!s)
+                        return -ENOMEM;
 
                 d = strdup(item->destination);
-                if (!d) {
-                        free(s);
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                if (!d)
+                        return -ENOMEM;
 
                 bind_mounts[h++] = (BindMount) {
-                        .source = s,
-                        .destination = d,
+                        .source = TAKE_PTR(s),
+                        .destination = TAKE_PTR(d),
                         .read_only = item->read_only,
                         .recursive = item->recursive,
                         .ignore_enoent = item->ignore_enoent,
@@ -3518,18 +3515,16 @@ static int compile_bind_mounts(
                          * tmpfs that makes it accessible and is empty except for the submounts we do this for. */
 
                         private_root = path_join(params->prefix[t], "private");
-                        if (!private_root) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
+                        if (!private_root)
+                                return -ENOMEM;
 
                         r = strv_consume(&empty_directories, private_root);
                         if (r < 0)
-                                goto finish;
+                                return r;
                 }
 
                 for (size_t i = 0; i < context->directories[t].n_items; i++) {
-                        char *s, *d;
+                        _cleanup_free_ char *s = NULL, *d = NULL;
 
                         /* When one of the parent directories is in the list, we cannot create the symlink
                          * for the child directory. See also the comments in setup_exec_directory(). */
@@ -3540,10 +3535,8 @@ static int compile_bind_mounts(
                                 s = path_join(params->prefix[t], "private", context->directories[t].items[i].path);
                         else
                                 s = path_join(params->prefix[t], context->directories[t].items[i].path);
-                        if (!s) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
+                        if (!s)
+                                return -ENOMEM;
 
                         if (exec_directory_is_private(context, t) &&
                             exec_context_with_rootfs(context))
@@ -3553,15 +3546,12 @@ static int compile_bind_mounts(
                                 d = path_join(params->prefix[t], context->directories[t].items[i].path);
                         else
                                 d = strdup(s);
-                        if (!d) {
-                                free(s);
-                                r = -ENOMEM;
-                                goto finish;
-                        }
+                        if (!d)
+                                return -ENOMEM;
 
                         bind_mounts[h++] = (BindMount) {
-                                .source = s,
-                                .destination = d,
+                                .source = TAKE_PTR(s),
+                                .destination = TAKE_PTR(d),
                                 .read_only = false,
                                 .nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */
                                 .recursive = true,
@@ -3572,15 +3562,11 @@ static int compile_bind_mounts(
 
         assert(h == n);
 
-        *ret_bind_mounts = bind_mounts;
+        *ret_bind_mounts = TAKE_PTR(bind_mounts);
         *ret_n_bind_mounts = n;
         *ret_empty_directories = TAKE_PTR(empty_directories);
 
         return (int) n;
-
-finish:
-        bind_mount_free_many(bind_mounts, h);
-        return r;
 }
 
 /* ret_symlinks will contain a list of pairs src:dest that describes
@@ -3704,6 +3690,8 @@ static int apply_mount_namespace(
 
         assert(context);
 
+        CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many);
+
         if (params->flags & EXEC_APPLY_CHROOT) {
                 root_image = context->root_image;
 
@@ -3718,20 +3706,18 @@ static int apply_mount_namespace(
         /* Symlinks for exec dirs are set up after other mounts, before they are made read-only. */
         r = compile_symlinks(context, params, &symlinks);
         if (r < 0)
-                goto finalize;
+                return r;
 
         /* We need to make the pressure path writable even if /sys/fs/cgroups is made read-only, as the
          * service will need to write to it in order to start the notifications. */
         if (context->protect_control_groups && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) {
                 read_write_paths_cleanup = strv_copy(context->read_write_paths);
-                if (!read_write_paths_cleanup) {
-                        r = -ENOMEM;
-                        goto finalize;
-                }
+                if (!read_write_paths_cleanup)
+                        return -ENOMEM;
 
                 r = strv_extend(&read_write_paths_cleanup, memory_pressure_path);
                 if (r < 0)
-                        goto finalize;
+                        return r;
 
                 read_write_paths = read_write_paths_cleanup;
         } else
@@ -3793,35 +3779,25 @@ static int apply_mount_namespace(
             params->prefix[EXEC_DIRECTORY_RUNTIME] &&
             FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
                 creds_path = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id);
-                if (!creds_path) {
-                        r = -ENOMEM;
-                        goto finalize;
-                }
+                if (!creds_path)
+                        return -ENOMEM;
         }
 
         if (MANAGER_IS_SYSTEM(u->manager)) {
                 propagate_dir = path_join("/run/systemd/propagate/", u->id);
-                if (!propagate_dir) {
-                        r = -ENOMEM;
-                        goto finalize;
-                }
+                if (!propagate_dir)
+                        return -ENOMEM;
 
                 incoming_dir = strdup("/run/systemd/incoming");
-                if (!incoming_dir) {
-                        r = -ENOMEM;
-                        goto finalize;
-                }
+                if (!incoming_dir)
+                        return -ENOMEM;
 
                 extension_dir = strdup("/run/systemd/unit-extensions");
-                if (!extension_dir) {
-                        r = -ENOMEM;
-                        goto finalize;
-                }
+                if (!extension_dir)
+                        return -ENOMEM;
         } else
-                if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0) {
-                        r = -ENOMEM;
-                        goto finalize;
-                }
+                if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0)
+                        return -ENOMEM;
 
         r = setup_namespace(
                         root_dir,
@@ -3870,20 +3846,22 @@ static int apply_mount_namespace(
                                     context,
                                     root_dir, root_image,
                                     bind_mounts,
-                                    n_bind_mounts)) {
-                        log_unit_debug(u, "Failed to set up namespace, and refusing to continue since the selected namespacing options alter mount environment non-trivially.\n"
-                                       "Bind mounts: %zu, temporary filesystems: %zu, root directory: %s, root image: %s, dynamic user: %s",
-                                       n_bind_mounts, context->n_temporary_filesystems, yes_no(root_dir), yes_no(root_image), yes_no(context->dynamic_user));
-
-                        r = -EOPNOTSUPP;
-                } else {
-                        log_unit_debug(u, "Failed to set up namespace, assuming containerized execution and ignoring.");
-                        r = 0;
-                }
+                                    n_bind_mounts))
+                        return log_unit_debug_errno(u,
+                                                    SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                                    "Failed to set up namespace, and refusing to continue since "
+                                                    "the selected namespacing options alter mount environment non-trivially.\n"
+                                                    "Bind mounts: %zu, temporary filesystems: %zu, root directory: %s, root image: %s, dynamic user: %s",
+                                                    n_bind_mounts,
+                                                    context->n_temporary_filesystems,
+                                                    yes_no(root_dir),
+                                                    yes_no(root_image),
+                                                    yes_no(context->dynamic_user));
+
+                log_unit_debug(u, "Failed to set up namespace, assuming containerized execution and ignoring.");
+                return 0;
         }
 
-finalize:
-        bind_mount_free_many(bind_mounts, n_bind_mounts);
         return r;
 }
 
index 2cd2da7284853d39e8abab79f6836fd780e6d7ef..9588c625b28dfeddb6feb9489cb4c359ce3f97fc 100644 (file)
@@ -49,6 +49,7 @@
 #include "fileio.h"
 #include "format-util.h"
 #include "fs-util.h"
+#include "getopt-defs.h"
 #include "hexdecoct.h"
 #include "hostname-setup.h"
 #include "ima-setup.h"
@@ -818,62 +819,13 @@ static void set_manager_settings(Manager *m) {
 
 static int parse_argv(int argc, char *argv[]) {
         enum {
-                ARG_LOG_LEVEL = 0x100,
-                ARG_LOG_TARGET,
-                ARG_LOG_COLOR,
-                ARG_LOG_LOCATION,
-                ARG_LOG_TIME,
-                ARG_UNIT,
-                ARG_SYSTEM,
-                ARG_USER,
-                ARG_TEST,
-                ARG_NO_PAGER,
-                ARG_VERSION,
-                ARG_DUMP_CONFIGURATION_ITEMS,
-                ARG_DUMP_BUS_PROPERTIES,
-                ARG_BUS_INTROSPECT,
-                ARG_DUMP_CORE,
-                ARG_CRASH_CHVT,
-                ARG_CRASH_SHELL,
-                ARG_CRASH_REBOOT,
-                ARG_CONFIRM_SPAWN,
-                ARG_SHOW_STATUS,
-                ARG_DESERIALIZE,
-                ARG_SWITCHED_ROOT,
-                ARG_DEFAULT_STD_OUTPUT,
-                ARG_DEFAULT_STD_ERROR,
-                ARG_MACHINE_ID,
-                ARG_SERVICE_WATCHDOGS,
+                COMMON_GETOPT_ARGS,
+                SYSTEMD_GETOPT_ARGS,
         };
 
         static const struct option options[] = {
-                { "log-level",                required_argument, NULL, ARG_LOG_LEVEL                },
-                { "log-target",               required_argument, NULL, ARG_LOG_TARGET               },
-                { "log-color",                optional_argument, NULL, ARG_LOG_COLOR                },
-                { "log-location",             optional_argument, NULL, ARG_LOG_LOCATION             },
-                { "log-time",                 optional_argument, NULL, ARG_LOG_TIME                 },
-                { "unit",                     required_argument, NULL, ARG_UNIT                     },
-                { "system",                   no_argument,       NULL, ARG_SYSTEM                   },
-                { "user",                     no_argument,       NULL, ARG_USER                     },
-                { "test",                     no_argument,       NULL, ARG_TEST                     },
-                { "no-pager",                 no_argument,       NULL, ARG_NO_PAGER                 },
-                { "help",                     no_argument,       NULL, 'h'                          },
-                { "version",                  no_argument,       NULL, ARG_VERSION                  },
-                { "dump-configuration-items", no_argument,       NULL, ARG_DUMP_CONFIGURATION_ITEMS },
-                { "dump-bus-properties",      no_argument,       NULL, ARG_DUMP_BUS_PROPERTIES      },
-                { "bus-introspect",           required_argument, NULL, ARG_BUS_INTROSPECT           },
-                { "dump-core",                optional_argument, NULL, ARG_DUMP_CORE                },
-                { "crash-chvt",               required_argument, NULL, ARG_CRASH_CHVT               },
-                { "crash-shell",              optional_argument, NULL, ARG_CRASH_SHELL              },
-                { "crash-reboot",             optional_argument, NULL, ARG_CRASH_REBOOT             },
-                { "confirm-spawn",            optional_argument, NULL, ARG_CONFIRM_SPAWN            },
-                { "show-status",              optional_argument, NULL, ARG_SHOW_STATUS              },
-                { "deserialize",              required_argument, NULL, ARG_DESERIALIZE              },
-                { "switched-root",            no_argument,       NULL, ARG_SWITCHED_ROOT            },
-                { "default-standard-output",  required_argument, NULL, ARG_DEFAULT_STD_OUTPUT,      },
-                { "default-standard-error",   required_argument, NULL, ARG_DEFAULT_STD_ERROR,       },
-                { "machine-id",               required_argument, NULL, ARG_MACHINE_ID               },
-                { "service-watchdogs",        required_argument, NULL, ARG_SERVICE_WATCHDOGS        },
+                COMMON_GETOPT_OPTIONS,
+                SYSTEMD_GETOPT_OPTIONS,
                 {}
         };
 
@@ -886,7 +838,7 @@ static int parse_argv(int argc, char *argv[]) {
         if (getpid_cached() == 1)
                 opterr = 0;
 
-        while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL)) >= 0)
 
                 switch (c) {
 
index 2668bca1bb1d60a3c6dc51e005ec2db52ed72872..1d19685d2eaeb154576da13c5847e9054cce8a2e 100644 (file)
@@ -1435,7 +1435,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)
@@ -1447,7 +1447,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)
@@ -2173,7 +2174,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 48229f96ee3da629f4c57a0eb8399b0cec3d6927..650741cc7ce18f57fbc60bec0e3f6915b6a4093e 100644 (file)
@@ -1677,7 +1677,7 @@ static int service_spawn_internal(
         if (r < 0)
                 return r;
 
-        our_env = new0(char*, 12);
+        our_env = new0(char*, 13);
         if (!our_env)
                 return -ENOMEM;
 
@@ -1686,6 +1686,10 @@ static int service_spawn_internal(
                         return -ENOMEM;
 
                 exec_params.notify_socket = UNIT(s)->manager->notify_socket;
+
+                if (s->n_fd_store_max > 0)
+                        if (asprintf(our_env + n_env++, "FDSTORE=%u", s->n_fd_store_max) < 0)
+                                return -ENOMEM;
         }
 
         if (s->main_pid > 0)
index 846d15b4151d064ad596b0b41ccc398e69f8e951..409801aed2888828cc2ab11b629efbf4736d38fd 100644 (file)
@@ -1890,12 +1890,12 @@ int unit_start(Unit *u, ActivationDetails *details) {
         if (UNIT_VTABLE(u)->once_only && dual_timestamp_is_set(&u->inactive_enter_timestamp))
                 return -ESTALE;
 
-        /* If the conditions failed, don't do anything at all. If we already are activating this call might
+        /* If the conditions were unmet, don't do anything at all. If we already are activating this call might
          * still be useful to speed up activation in case there is some hold-off time, but we don't want to
          * recheck the condition in that case. */
         if (state != UNIT_ACTIVATING &&
             !unit_test_condition(u))
-                return log_unit_debug_errno(u, SYNTHETIC_ERRNO(ECOMM), "Starting requested but condition failed. Not starting unit.");
+                return log_unit_debug_errno(u, SYNTHETIC_ERRNO(ECOMM), "Starting requested but condition not met. Not starting unit.");
 
         /* If the asserts failed, fail the entire job */
         if (state != UNIT_ACTIVATING &&
index 54c72eceaf5b4edb672e2c7e3ccfb71ac1afb350..7cd13a51e904daae34fc70337d54af98681dfb45 100644 (file)
@@ -100,23 +100,24 @@ static bool press_any_key(void) {
         return k != 'q';
 }
 
-static void print_welcome(void) {
+static void print_welcome(int rfd) {
         _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL;
         static bool done = false;
         const char *pn, *ac;
         int r;
 
+        assert(rfd >= 0);
+
         if (!arg_welcome)
                 return;
 
         if (done)
                 return;
 
-        r = parse_os_release(
-                        arg_root,
-                        "PRETTY_NAME", &pretty_name,
-                        "NAME", &os_name,
-                        "ANSI_COLOR", &ansi_color);
+        r = parse_os_release_at(rfd,
+                                "PRETTY_NAME", &pretty_name,
+                                "NAME", &os_name,
+                                "ANSI_COLOR", &ansi_color);
         if (r < 0)
                 log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
                                "Failed to read os-release file, ignoring: %m");
@@ -246,19 +247,23 @@ static int should_configure(int dir_fd, const char *filename) {
         return arg_force; /* exists, but if --force was given we should still configure the file. */
 }
 
-static bool locale_is_ok(const char *name) {
+static bool locale_is_installed_bool(const char *name) {
+        return locale_is_installed(name) > 0;
+}
 
-        if (arg_root)
-                return locale_is_valid(name);
+static bool locale_is_ok(int rfd, const char *name) {
+        assert(rfd >= 0);
 
-        return locale_is_installed(name) > 0;
+        return dir_fd_is_root(rfd) ? locale_is_installed_bool(name) : locale_is_valid(name);
 }
 
-static int prompt_locale(void) {
+static int prompt_locale(int rfd) {
         _cleanup_strv_free_ char **locales = NULL;
         bool acquired_from_creds = false;
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_locale || arg_locale_messages)
                 return 0;
 
@@ -304,10 +309,13 @@ static int prompt_locale(void) {
                         /* Not setting arg_locale_message here, since it defaults to LANG anyway */
                 }
         } else {
-                print_welcome();
+                bool (*is_valid)(const char *name) = dir_fd_is_root(rfd) ? locale_is_installed_bool
+                                                                         : locale_is_valid;
+
+                print_welcome(rfd);
 
                 r = prompt_loop("Please enter system locale name or number",
-                                locales, 60, locale_is_ok, &arg_locale);
+                                locales, 60, is_valid, &arg_locale);
                 if (r < 0)
                         return r;
 
@@ -315,7 +323,7 @@ static int prompt_locale(void) {
                         return 0;
 
                 r = prompt_loop("Please enter system message locale name or number",
-                                locales, 60, locale_is_ok, &arg_locale_messages);
+                                locales, 60, is_valid, &arg_locale_messages);
                 if (r < 0)
                         return r;
 
@@ -363,7 +371,7 @@ static int process_locale(int rfd) {
                 }
         }
 
-        r = prompt_locale();
+        r = prompt_locale(rfd);
         if (r < 0)
                 return r;
 
@@ -385,10 +393,12 @@ static int process_locale(int rfd) {
         return 0;
 }
 
-static int prompt_keymap(void) {
+static int prompt_keymap(int rfd) {
         _cleanup_strv_free_ char **kmaps = NULL;
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_keymap)
                 return 0;
 
@@ -411,7 +421,7 @@ static int prompt_keymap(void) {
         if (r < 0)
                 return log_error_errno(r, "Failed to read keymaps: %m");
 
-        print_welcome();
+        print_welcome(rfd);
 
         return prompt_loop("Please enter system keymap name or number",
                            kmaps, 60, keymap_is_valid, &arg_keymap);
@@ -452,7 +462,7 @@ static int process_keymap(int rfd) {
                 }
         }
 
-        r = prompt_keymap();
+        r = prompt_keymap(rfd);
         if (r == -ENOENT)
                 return 0; /* don't fail if no keymaps are installed */
         if (r < 0)
@@ -475,10 +485,12 @@ static bool timezone_is_valid_log_error(const char *name) {
         return timezone_is_valid(name, LOG_ERR);
 }
 
-static int prompt_timezone(void) {
+static int prompt_timezone(int rfd) {
         _cleanup_strv_free_ char **zones = NULL;
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_timezone)
                 return 0;
 
@@ -499,7 +511,7 @@ static int prompt_timezone(void) {
         if (r < 0)
                 return log_error_errno(r, "Cannot query timezone list: %m");
 
-        print_welcome();
+        print_welcome(rfd);
 
         r = prompt_loop("Please enter timezone name or number",
                         zones, 30, timezone_is_valid_log_error, &arg_timezone);
@@ -550,7 +562,7 @@ static int process_timezone(int rfd) {
                 }
         }
 
-        r = prompt_timezone();
+        r = prompt_timezone(rfd);
         if (r < 0)
                 return r;
 
@@ -567,9 +579,11 @@ static int process_timezone(int rfd) {
         return 0;
 }
 
-static int prompt_hostname(void) {
+static int prompt_hostname(int rfd) {
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_hostname)
                 return 0;
 
@@ -578,7 +592,7 @@ static int prompt_hostname(void) {
                 return 0;
         }
 
-        print_welcome();
+        print_welcome(rfd);
         putchar('\n');
 
         for (;;) {
@@ -626,7 +640,7 @@ static int process_hostname(int rfd) {
         if (r <= 0)
                 return r;
 
-        r = prompt_hostname();
+        r = prompt_hostname(rfd);
         if (r < 0)
                 return r;
 
@@ -675,10 +689,12 @@ static int process_machine_id(int rfd) {
         return 0;
 }
 
-static int prompt_root_password(void) {
+static int prompt_root_password(int rfd) {
         const char *msg1, *msg2;
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_root_password)
                 return 0;
 
@@ -690,7 +706,7 @@ static int prompt_root_password(void) {
                 return 0;
         }
 
-        print_welcome();
+        print_welcome(rfd);
         putchar('\n');
 
         msg1 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip):");
@@ -757,6 +773,8 @@ static int find_shell(int rfd, const char *path) {
 static int prompt_root_shell(int rfd) {
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_root_shell)
                 return 0;
 
@@ -773,7 +791,7 @@ static int prompt_root_shell(int rfd) {
                 return 0;
         }
 
-        print_welcome();
+        print_welcome(rfd);
         putchar('\n');
 
         for (;;) {
@@ -799,7 +817,7 @@ static int prompt_root_shell(int rfd) {
         return 0;
 }
 
-static int write_root_passwd(int etc_fd, const char *password, const char *shell) {
+static int write_root_passwd(int rfd, int etc_fd, const char *password, const char *shell) {
         _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
         _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
         int r;
@@ -844,7 +862,7 @@ static int write_root_passwd(int etc_fd, const char *password, const char *shell
                         .pw_gid = 0,
                         .pw_gecos = (char *) "Super User",
                         .pw_dir = (char *) "/root",
-                        .pw_shell = (char *) (shell ?: default_root_shell(arg_root)),
+                        .pw_shell = (char *) (shell ?: default_root_shell_at(rfd)),
                 };
 
                 if (errno != ENOENT)
@@ -1025,7 +1043,7 @@ static int process_root_account(int rfd) {
                 arg_root_password_is_hashed = true;
         }
 
-        r = prompt_root_password();
+        r = prompt_root_password(rfd);
         if (r < 0)
                 return r;
 
@@ -1045,7 +1063,7 @@ static int process_root_account(int rfd) {
         else
                 password = hashed_password = PASSWORD_LOCKED_AND_INVALID;
 
-        r = write_root_passwd(pfd, password, arg_root_shell);
+        r = write_root_passwd(rfd, pfd, password, arg_root_shell);
         if (r < 0)
                 return log_error_errno(r, "Failed to write /etc/passwd: %m");
 
@@ -1481,14 +1499,6 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached();
                 }
 
-        /* We check if the specified locale strings are valid down here, so that we can take --root= into
-         * account when looking for the locale files. */
-
-        if (arg_locale && !locale_is_ok(arg_locale))
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
-        if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
-
         if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "--delete-root-password cannot be combined with other root password options");
@@ -1558,6 +1568,14 @@ static int run(int argc, char *argv[]) {
 
         LOG_SET_PREFIX(arg_image ?: arg_root);
 
+        /* We check these conditions here instead of in parse_argv() so that we can take the root directory
+         * into account. */
+
+        if (arg_locale && !locale_is_ok(rfd, arg_locale))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
+        if (arg_locale_messages && !locale_is_ok(rfd, arg_locale_messages))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
+
         if (arg_root_shell) {
                 r = find_shell(rfd, arg_root_shell);
                 if (r < 0)
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 10956cc5482b4ecd4d5251db50347039e09b4ab0..9cd571dfb896d177797a72fe046dbea9e5c2f003 100644 (file)
@@ -45,7 +45,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         }
 
         size_t csize;
-        r = compress_blob_explicit(alg, h->data, data_len, buf, size, &csize);
+        r = compress_blob(alg, h->data, data_len, buf, size, &csize);
         if (r < 0) {
                 log_error_errno(r, "Compression failed: %m");
                 return 0;
index 5908758a8f03ade9be2b4fb0b3ca188aa8b9f991..d3f7785ad3409fbe9ef0e4b62683c5878f5b3a69 100644 (file)
@@ -75,6 +75,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0)
 
                 switch (c) {
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 6ee8bb7a7ffeec8c8fa443c84088ada8d8bb2f26..40dadf67a88f26a770a13fb7e50797515b6c8c52 100644 (file)
@@ -3512,23 +3512,23 @@ static int bus_add_match_full(
 
         struct bus_match_component *components = NULL;
         size_t n_components = 0;
-        sd_bus_slot *s = NULL;
-        int r = 0;
+        _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL;
+        int r;
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(match, -EINVAL);
         assert_return(!bus_pid_changed(bus), -ECHILD);
 
+        CLEANUP_ARRAY(components, n_components, bus_match_parse_free);
+
         r = bus_match_parse(match, &components, &n_components);
         if (r < 0)
-                goto finish;
+                return r;
 
         s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata);
-        if (!s) {
-                r = -ENOMEM;
-                goto finish;
-        }
+        if (!s)
+                return -ENOMEM;
 
         s->match_callback.callback = callback;
         s->match_callback.install_callback = install_callback;
@@ -3544,10 +3544,8 @@ static int bus_add_match_full(
                         /* We store the original match string, so that we can use it to remove the match again. */
 
                         s->match_callback.match_string = strdup(match);
-                        if (!s->match_callback.match_string) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
+                        if (!s->match_callback.match_string)
+                                return -ENOMEM;
 
                         if (asynchronous) {
                                 r = bus_add_match_internal_async(bus,
@@ -3557,7 +3555,7 @@ static int bus_add_match_full(
                                                                  s);
 
                                 if (r < 0)
-                                        goto finish;
+                                        return r;
 
                                 /* Make the slot of the match call floating now. We need the reference, but we don't
                                  * want that this match pins the bus object, hence we first create it non-floating, but
@@ -3566,7 +3564,7 @@ static int bus_add_match_full(
                         } else
                                 r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
                         if (r < 0)
-                                goto finish;
+                                return r;
 
                         s->match_added = true;
                 }
@@ -3575,17 +3573,13 @@ static int bus_add_match_full(
         bus->match_callbacks_modified = true;
         r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback);
         if (r < 0)
-                goto finish;
+                return r;
 
         if (slot)
                 *slot = s;
         s = NULL;
 
-finish:
-        bus_match_parse_free(components, n_components);
-        sd_bus_slot_unref(s);
-
-        return r;
+        return 0;
 }
 
 _public_ int sd_bus_add_match(
index e9d54f514e65d837d4fcc814c99a73460c7c5fcf..9ded8c17a178f3563a2bff6d1c7706423e8ab96e 100644 (file)
@@ -351,6 +351,8 @@ static int device_amend(sd_device *device, const char *key, const char *value) {
                                 return r;
                         if (r == 0)
                                 break;
+                        if (isempty(word))
+                                continue;
 
                         r = device_add_tag(device, word, streq(key, "CURRENT_TAGS"));
                         if (r < 0)
index 740c58438cfcf672ff85ce6293a3a767880204e2..b903d1afd6468803f06695802b499b457090a07c 100644 (file)
@@ -38,7 +38,7 @@ void device_set_db_persist(sd_device *device);
 void device_set_devlink_priority(sd_device *device, int priority);
 int device_ensure_usec_initialized(sd_device *device, sd_device *device_old);
 int device_add_devlink(sd_device *device, const char *devlink);
-void device_remove_devlink(sd_device *device, const char *devlink);
+int device_remove_devlink(sd_device *device, const char *devlink);
 bool device_has_devlink(sd_device *device, const char *devlink);
 int device_add_property(sd_device *device, const char *property, const char *value);
 int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
index e86ec3e75b1c07ef0e2f8a68f7b99a4dfcbf6499..e2eac651bd27fb3911fee97a7e454a6a7ded51ef 100644 (file)
@@ -569,6 +569,32 @@ int device_set_ifindex(sd_device *device, const char *name) {
         return 0;
 }
 
+static int mangle_devname(const char *p, char **ret) {
+        char *q;
+
+        assert(p);
+        assert(ret);
+
+        if (!path_is_safe(p))
+                return -EINVAL;
+
+        /* When the path is absolute, it must start with "/dev/", but ignore "/dev/" itself. */
+        if (path_is_absolute(p)) {
+                if (isempty(path_startswith(p, "/dev/")))
+                        return -EINVAL;
+
+                q = strdup(p);
+        } else
+                q = path_join("/dev/", p);
+        if (!q)
+                return -ENOMEM;
+
+        path_simplify(q);
+
+        *ret = q;
+        return 0;
+}
+
 int device_set_devname(sd_device *device, const char *devname) {
         _cleanup_free_ char *t = NULL;
         int r;
@@ -576,12 +602,9 @@ int device_set_devname(sd_device *device, const char *devname) {
         assert(device);
         assert(devname);
 
-        if (devname[0] != '/')
-                t = strjoin("/dev/", devname);
-        else
-                t = strdup(devname);
-        if (!t)
-                return -ENOMEM;
+        r = mangle_devname(devname, &t);
+        if (r < 0)
+                return r;
 
         r = device_add_property_internal(device, "DEVNAME", t);
         if (r < 0)
@@ -1326,7 +1349,7 @@ _public_ int sd_device_get_devname(sd_device *device, const char **devname) {
         if (!device->devname)
                 return -ENOENT;
 
-        assert(path_startswith(device->devname, "/dev/"));
+        assert(!isempty(path_startswith(device->devname, "/dev/")));
 
         if (devname)
                 *devname = device->devname;
@@ -1439,7 +1462,7 @@ _public_ int sd_device_get_diskseq(sd_device *device, uint64_t *ret) {
 static bool is_valid_tag(const char *tag) {
         assert(tag);
 
-        return !strchr(tag, ':') && !strchr(tag, ' ');
+        return in_charset(tag, ALPHANUMERICAL "-_") && filename_is_valid(tag);
 }
 
 int device_add_tag(sd_device *device, const char *tag, bool both) {
@@ -1474,33 +1497,44 @@ int device_add_tag(sd_device *device, const char *tag, bool both) {
 }
 
 int device_add_devlink(sd_device *device, const char *devlink) {
+        char *p;
         int r;
 
         assert(device);
         assert(devlink);
 
-        r = set_put_strdup(&device->devlinks, devlink);
+        r = mangle_devname(devlink, &p);
+        if (r < 0)
+                return r;
+
+        r = set_ensure_consume(&device->devlinks, &path_hash_ops_free, p);
         if (r < 0)
                 return r;
 
         device->devlinks_generation++;
         device->property_devlinks_outdated = true;
 
-        return 0;
+        return r; /* return 1 when newly added, 0 when already exists */
 }
 
-void device_remove_devlink(sd_device *device, const char *devlink) {
-        _cleanup_free_ char *s = NULL;
+int device_remove_devlink(sd_device *device, const char *devlink) {
+        _cleanup_free_ char *p = NULL, *s = NULL;
+        int r;
 
         assert(device);
         assert(devlink);
 
-        s = set_remove(device->devlinks, devlink);
+        r = mangle_devname(devlink, &p);
+        if (r < 0)
+                return r;
+
+        s = set_remove(device->devlinks, p);
         if (!s)
-                return;
+                return 0; /* does not exist */
 
         device->devlinks_generation++;
         device->property_devlinks_outdated = true;
+        return 1; /* removed */
 }
 
 bool device_has_devlink(sd_device *device, const char *devlink) {
@@ -2270,7 +2304,7 @@ int device_cache_sysattr_value(sd_device *device, const char *key, char *value)
                         return -ENOMEM;
         }
 
-        r = hashmap_ensure_put(&device->sysattr_values, &string_hash_ops_free_free, new_key, value);
+        r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value);
         if (r < 0)
                 return r;
 
index 8da4ca9cadcd3e0d08e600dc149d4c723732f355..3753e29d0d313c83b9054d8e73b7b81fcd2611ff 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(
@@ -1618,25 +1671,28 @@ static int journal_file_append_field(
         return 0;
 }
 
-static Compression maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
-        Compression compression = COMPRESSION_NONE;
-
+static int maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, uint64_t size, size_t *rsize) {
         assert(f);
         assert(f->header);
 
 #if HAVE_COMPRESSION
-        if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) {
-                compression = compress_blob(src, size, dst, size - 1, rsize);
-                if (compression > 0)
-                        log_debug("Compressed data object %"PRIu64" -> %zu using %s",
-                                  size, *rsize, compression_to_string(compression));
-                else
-                        /* Compression didn't work, we don't really care why, let's continue without compression */
-                        compression = COMPRESSION_NONE;
-        }
-#endif
+        Compression c;
+        int r;
+
+        c = JOURNAL_FILE_COMPRESSION(f);
+        if (c == COMPRESSION_NONE || size < f->compress_threshold_bytes)
+                return 0;
+
+        r = compress_blob(c, src, size, dst, size - 1, rsize);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to compress data object using %s, ignoring: %m", compression_to_string(c));
 
-        return compression;
+        log_debug("Compressed data object %"PRIu64" -> %zu using %s", size, *rsize, compression_to_string(c));
+
+        return 1; /* compressed */
+#else
+        return 0;
+#endif
 }
 
 static int journal_file_append_data(
@@ -1649,7 +1705,6 @@ static int journal_file_append_data(
         uint64_t hash, p, osize;
         Object *o, *fo;
         size_t rsize = 0;
-        Compression c;
         const void *eq;
         int r;
 
@@ -1677,13 +1732,18 @@ static int journal_file_append_data(
 
         o->data.hash = htole64(hash);
 
-        c = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize);
+        r = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize);
+        if (r <= 0)
+                /* We don't really care failures, let's continue without compression */
+                memcpy_safe(journal_file_data_payload_field(f, o), data, size);
+        else {
+                Compression c = JOURNAL_FILE_COMPRESSION(f);
+
+                assert(c >= 0 && c < _COMPRESSION_MAX && c != COMPRESSION_NONE);
 
-        if (c != COMPRESSION_NONE) {
                 o->object.size = htole64(journal_file_data_payload_offset(f) + rsize);
                 o->object.flags |= COMPRESSION_TO_OBJECT_FLAG(c);
-        } else
-                memcpy_safe(journal_file_data_payload_field(f, o), data, size);
+        }
 
         r = journal_file_link_data(f, o, p, hash);
         if (r < 0)
@@ -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 8ad5256ad9d472fb38acc966e67b35adb22e8858..5247d139eb7ea9d64766bbad68a7b697ecbecc1a 100644 (file)
@@ -948,6 +948,7 @@ static int locale_gen_locale_supported(const char *locale_entry) {
 
         for (;;) {
                 _cleanup_free_ char *line = NULL;
+                char *l;
 
                 r = read_line(f, LONG_LINE_MAX, &line);
                 if (r < 0)
@@ -955,8 +956,8 @@ static int locale_gen_locale_supported(const char *locale_entry) {
                 if (r == 0)
                         return 0;
 
-                line = strstrip(line);
-                if (strcaseeq_ptr(line, locale_entry))
+                l = strstrip(line);
+                if (strcaseeq_ptr(l, locale_entry))
                         return 1;
         }
 }
@@ -1034,14 +1035,13 @@ int locale_gen_enable_locale(const char *locale) {
                                 continue;
                         }
 
-                        line = strstrip(line);
-                        if (isempty(line)) {
+                        line_locale = strstrip(line);
+                        if (isempty(line_locale)) {
                                 fputc('\n', fw);
                                 first_line = false;
                                 continue;
                         }
 
-                        line_locale = line;
                         if (line_locale[0] == '#')
                                 line_locale = strstrip(line_locale + 1);
                         else if (strcaseeq_ptr(line_locale, locale_entry))
index 7cd2fd3e6688b9cdac378140c9cc9628d02bc10a..25ba848492dee9527de92a1c2cd460d160632b6a 100644 (file)
@@ -210,6 +210,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0)
 
                 switch (c) {
index 6a29a32bcd22bf97a069bb04e561e2d1bfbdcdef..353b3394948ae98fb7b8e44eebc080a415fe3b41 100644 (file)
@@ -1454,9 +1454,7 @@ static int get_settings_path(const char *name, char **ret_path) {
 }
 
 static int edit_settings(int argc, char *argv[], void *userdata) {
-        _cleanup_(edit_file_context_done) EditFileContext context = {
-                .remove_parent = false,
-        };
+        _cleanup_(edit_file_context_done) EditFileContext context = {};
         int r;
 
         if (!on_tty())
@@ -1757,15 +1755,15 @@ static int start_machine(int argc, char *argv[], void *userdata) {
 static int enable_machine(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        InstallChange *changes = NULL;
-        size_t n_changes = 0;
         const char *method;
         sd_bus *bus = ASSERT_PTR(userdata);
         int r;
+        bool enable;
 
         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles";
+        enable = streq(argv[0], "enable");
+        method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
 
         r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
         if (r < 0)
@@ -1775,7 +1773,7 @@ static int enable_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_create_error(r);
 
-        if (streq(argv[0], "enable")) {
+        if (enable) {
                 r = sd_bus_message_append(m, "s", "machines.target");
                 if (r < 0)
                         return bus_log_create_error(r);
@@ -1805,7 +1803,7 @@ static int enable_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_create_error(r);
 
-        if (streq(argv[0], "enable"))
+        if (enable)
                 r = sd_bus_message_append(m, "bb", false, false);
         else
                 r = sd_bus_message_append(m, "b", false);
@@ -1816,47 +1814,38 @@ static int enable_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
 
-        if (streq(argv[0], "enable")) {
+        if (enable) {
                 r = sd_bus_message_read(reply, "b", NULL);
                 if (r < 0)
                         return bus_log_parse_error(r);
         }
 
-        r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+        r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
         if (r < 0)
-                goto finish;
+                return r;
 
         r = bus_call_method(bus, bus_systemd_mgr, "Reload", &error, NULL, NULL);
-        if (r < 0) {
-                log_error("Failed to reload daemon: %s", bus_error_message(&error, r));
-                goto finish;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
 
         if (arg_now) {
                 _cleanup_strv_free_ char **new_args = NULL;
 
-                new_args = strv_new(streq(argv[0], "enable") ? "start" : "poweroff");
-                if (!new_args) {
-                        r = log_oom();
-                        goto finish;
-                }
+                new_args = strv_new(enable ? "start" : "poweroff");
+                if (!new_args)
+                        return log_oom();
 
                 r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates = */ false);
-                if (r < 0) {
-                        log_oom();
-                        goto finish;
-                }
+                if (r < 0)
+                        return log_oom();
 
-                if (streq(argv[0], "enable"))
-                        r = start_machine(strv_length(new_args), new_args, userdata);
-                else
-                        r = poweroff_machine(strv_length(new_args), new_args, userdata);
-        }
+                if (enable)
+                        return start_machine(strv_length(new_args), new_args, userdata);
 
-finish:
-        install_changes_free(changes, n_changes);
+                return poweroff_machine(strv_length(new_args), new_args, userdata);
+        }
 
-        return r;
+        return 0;
 }
 
 static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) {
@@ -2720,6 +2709,10 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
+
         for (;;) {
                 static const char option_string[] = "-hp:als:H:M:qn:o:E:";
 
index 6b74a3df1314aefb60980d0fad9cf48c096361a5..3882676216a525c0b0c7693432b330cab11a10c1 100644 (file)
@@ -820,6 +820,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nUE:P", options, NULL)) >= 0)
                 switch (c) {
 
index bc3bfa15fb69d383fa1bb103e464cea7580be0ff..725cad1f4a5035ba91453cd5e6ddc8866a6a8dc8 100644 (file)
@@ -3965,6 +3965,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");
@@ -3985,6 +3987,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;
 }
@@ -3995,7 +3999,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
@@ -4032,7 +4036,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;
 }
 
@@ -5438,6 +5442,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;
 
@@ -5458,6 +5463,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;
@@ -5515,6 +5525,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;
@@ -5548,6 +5566,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 23420abab25da81d232f695164a24c328824c989..e5f2f38a211a8bc797b3da1aaaafc5632989e3ed 100644 (file)
@@ -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);
@@ -627,7 +627,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)
@@ -970,17 +970,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);
@@ -1128,7 +1128,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 d463eb41207e1989db6f01cc723fc6c88c083ee3..eec9c63d0b8ca0f1026c5b4ed6c2265ae7544784 100644 (file)
@@ -556,9 +556,7 @@ static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_strv_free_ char **names = NULL;
-        InstallChange *changes = NULL;
         const uint64_t flags = UNIT_FILE_PORTABLE | (arg_runtime ? UNIT_FILE_RUNTIME : 0);
-        size_t n_changes = 0;
         int r;
 
         if (!arg_enable)
@@ -597,8 +595,7 @@ static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) {
                         return bus_log_parse_error(r);
         }
 
-        (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
-        install_changes_free(changes, n_changes);
+        (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
 
         return 0;
 }
index 768964231a16e99494b40fff2cca6fd1ac1265a8..0d5518060eb9a9276ac2243b247d06cf63bc9216 100644 (file)
@@ -281,6 +281,8 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
 
         assert(message);
 
+        CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
         /* Note that we do not redirect detaching to the image object here, because we want to allow that users can
          * detach already deleted images too, in case the user already deleted an image before properly detaching
          * it. */
@@ -339,13 +341,9 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e
                         &n_changes,
                         error);
         if (r < 0)
-                goto finish;
-
-        r = reply_portable_changes(message, changes, n_changes);
+                return r;
 
-finish:
-        portable_changes_free(changes, n_changes);
-        return r;
+        return reply_portable_changes(message, changes, n_changes);
 }
 
 static int method_reattach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
index 262518d15cc38e5f3c0f1209a160bdf2b2ac13d6..5e46d86b08c902a45c8ee1877e42d7fea2042122 100644 (file)
@@ -83,9 +83,9 @@ static int append_fd(sd_bus_message *m, PortableMetadata *d) {
         if (d) {
                 assert(d->fd >= 0);
 
-                f = take_fdopen(&d->fd, "r");
-                if (!f)
-                        return -errno;
+                r = fdopen_independent(d->fd, "r", &f);
+                if (r < 0)
+                        return r;
 
                 r = read_full_stream(f, &buf, &n);
                 if (r < 0)
@@ -317,6 +317,8 @@ int bus_image_common_attach(
         assert(message);
         assert(name_or_path || image);
 
+        CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
         if (!m) {
                 assert(image);
                 m = image->userdata;
@@ -392,13 +394,9 @@ int bus_image_common_attach(
                         &n_changes,
                         error);
         if (r < 0)
-                goto finish;
-
-        r = reply_portable_changes(message, changes, n_changes);
+                return r;
 
-finish:
-        portable_changes_free(changes, n_changes);
-        return r;
+        return reply_portable_changes(message, changes, n_changes);
 }
 
 static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -420,6 +418,8 @@ static int bus_image_method_detach(
 
         assert(message);
 
+        CLEANUP_ARRAY(changes, n_changes, portable_changes_free);
+
         if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
                 r = sd_bus_message_read_strv(message, &extension_images);
                 if (r < 0)
@@ -472,13 +472,9 @@ static int bus_image_method_detach(
                         &n_changes,
                         error);
         if (r < 0)
-                goto finish;
-
-        r = reply_portable_changes(message, changes, n_changes);
+                return r;
 
-finish:
-        portable_changes_free(changes, n_changes);
-        return r;
+        return reply_portable_changes(message, changes, n_changes);
 }
 
 int bus_image_common_remove(
@@ -650,6 +646,10 @@ int bus_image_common_reattach(
         assert(message);
         assert(name_or_path || image);
 
+        CLEANUP_ARRAY(changes_detached, n_changes_detached, portable_changes_free);
+        CLEANUP_ARRAY(changes_attached, n_changes_attached, portable_changes_free);
+        CLEANUP_ARRAY(changes_gone, n_changes_gone, portable_changes_free);
+
         if (!m) {
                 assert(image);
                 m = image->userdata;
@@ -723,7 +723,7 @@ int bus_image_common_reattach(
                         &n_changes_detached,
                         error);
         if (r < 0)
-                goto finish;
+                return r;
 
         r = portable_attach(
                         sd_bus_message_get_bus(message),
@@ -737,7 +737,7 @@ int bus_image_common_reattach(
                         &n_changes_attached,
                         error);
         if (r < 0)
-                goto finish;
+                return r;
 
         /* We want to return the list of units really removed by the detach,
          * and not added again by the attach */
@@ -745,22 +745,14 @@ int bus_image_common_reattach(
                                        changes_detached, n_changes_detached,
                                        &changes_gone, &n_changes_gone);
         if (r < 0)
-                goto finish;
+                return r;
 
         /* First, return the units that are gone (so that the caller can stop them)
          * Then, return the units that are changed/added (so that the caller can
          * start/restart/enable them) */
-        r = reply_portable_changes_pair(message,
-                                        changes_gone, n_changes_gone,
-                                        changes_attached, n_changes_attached);
-        if (r < 0)
-                goto finish;
-
-finish:
-        portable_changes_free(changes_detached, n_changes_detached);
-        portable_changes_free(changes_attached, n_changes_attached);
-        portable_changes_free(changes_gone, n_changes_gone);
-        return r;
+        return reply_portable_changes_pair(message,
+                                           changes_gone, n_changes_gone,
+                                           changes_attached, n_changes_attached);
 }
 
 static int bus_image_method_reattach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
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 8377c2e8cdd0a4bbc60670b9ebab3d17b62e6f19..ee4a151a4abc468705b61334f189ed28c8a86f9d 100644 (file)
@@ -242,6 +242,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tPqGdSu:", options, NULL)) >= 0)
 
                 switch (c) {
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 81eef8b058362cabd43c3fd3549c15647b72015d..1c991ae54f2e609797457207ce5fa2c88fab3fe8 100644 (file)
@@ -2711,14 +2711,13 @@ int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char
         return 0;
 }
 
-int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, InstallChange **changes, size_t *n_changes) {
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet) {
         const char *type, *path, *source;
+        InstallChange *changes = NULL;
+        size_t n_changes = 0;
         int r;
 
-        /* changes is dereferenced when calling install_changes_dump() later,
-         * so we have to make sure this is not NULL. */
-        assert(changes);
-        assert(n_changes);
+        CLEANUP_ARRAY(changes, n_changes, install_changes_free);
 
         r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
         if (r < 0)
@@ -2736,7 +2735,7 @@ int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, In
                         continue;
                 }
 
-                r = install_changes_add(changes, n_changes, t, path, source);
+                r = install_changes_add(&changes, &n_changes, t, path, source);
                 if (r < 0)
                         return r;
         }
@@ -2747,7 +2746,8 @@ int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, In
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        install_changes_dump(0, NULL, *changes, *n_changes, quiet);
+        install_changes_dump(0, NULL, changes, n_changes, quiet);
+
         return 0;
 }
 
index 789a142e1d85f2371a10d2f17ae4cd072a649d83..97d84708b429d68056716adfab2c9f12cfe69edd 100644 (file)
@@ -25,7 +25,7 @@ int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u);
 int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const char *assignment);
 int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char **l);
 
-int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, InstallChange **changes, size_t *n_changes);
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet);
 
 int unit_load_state(sd_bus *bus, const char *name, char **load_state);
 
index d5fdbbf9e07d5c57916112ed21b79388a0059245..e5a80757e095352adcb8027efc571d5ee31f399f 100644 (file)
@@ -106,35 +106,28 @@ Condition* condition_free_list_type(Condition *head, ConditionType type) {
 }
 
 static int condition_test_kernel_command_line(Condition *c, char **env) {
-        _cleanup_free_ char *line = NULL;
+        _cleanup_strv_free_ char **args = NULL;
         int r;
 
         assert(c);
         assert(c->parameter);
         assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
 
-        r = proc_cmdline(&line);
+        r = proc_cmdline_strv(&args);
         if (r < 0)
                 return r;
 
         bool equal = strchr(c->parameter, '=');
 
-        for (const char *p = line;;) {
-                _cleanup_free_ char *word = NULL;
+        STRV_FOREACH(word, args) {
                 bool found;
 
-                r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
                 if (equal)
-                        found = streq(word, c->parameter);
+                        found = streq(*word, c->parameter);
                 else {
                         const char *f;
 
-                        f = startswith(word, c->parameter);
+                        f = startswith(*word, c->parameter);
                         found = f && IN_SET(*f, 0, '=');
                 }
 
index 23d72ad1ca8527eea1fb9c175fccf180386c91ff..f283394545b85dc5632a03f007dfbcc071b26ecc 100644 (file)
@@ -157,6 +157,7 @@ int copy_bytes_full(
                 copy_progress_bytes_t progress,
                 void *userdata) {
 
+        _cleanup_close_ int fdf_opened = -EBADF, fdt_opened = -EBADF;
         bool try_cfr = true, try_sendfile = true, try_splice = true, copied_something = false;
         int r, nonblock_pipe = -1;
         size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */
@@ -177,6 +178,13 @@ int copy_bytes_full(
         if (ret_remains_size)
                 *ret_remains_size = 0;
 
+        fdf = fd_reopen_condition(fdf, O_CLOEXEC | O_NOCTTY | O_RDONLY, O_PATH, &fdf_opened);
+        if (fdf < 0)
+                return fdf;
+        fdt = fd_reopen_condition(fdt, O_CLOEXEC | O_NOCTTY | O_RDWR, O_PATH, &fdt_opened);
+        if (fdt < 0)
+                return fdt;
+
         /* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of
          * source and destination first. */
         if ((copy_flags & COPY_REFLINK)) {
index 40e469379f5d0ba226f808ab33201a7ee2f0cdc3..bdbbb98c2c82e4ce406bceb9cbf2ad610e9d93d8 100644 (file)
@@ -8,12 +8,12 @@
 #include "string-util.h"
 #include "utf8.h"
 
-int allow_listed_char_for_devnode(char c, const char *white) {
+int allow_listed_char_for_devnode(char c, const char *additional) {
         return
                 ascii_isdigit(c) ||
                 ascii_isalpha(c) ||
                 strchr("#+-.:=@_", c) ||
-                (white && strchr(white, c));
+                (additional && strchr(additional, c));
 }
 
 int encode_devnode_name(const char *str, char *str_enc, size_t len) {
index df3d5b77656d77b98a1308d353109064cf5772d5..ac6a8033ddb4928ec19320eb532afc2af90e94ed 100644 (file)
@@ -64,9 +64,14 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
          * 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_EXTENSION] = "/etc/extensions\0"             /* only place symlinks here */
-                            "/run/extensions\0"             /* and here too */
-                            "/var/lib/extensions\0",        /* the main place for images */
+        [IMAGE_SYSEXT] =    "/etc/extensions\0"            /* only place symlinks here */
+                            "/run/extensions\0"            /* and here too */
+                            "/var/lib/extensions\0",       /* the main place for images */
+
+        [IMAGE_CONFEXT] =   "/run/confexts\0"              /* only place symlinks here */
+                            "/var/lib/confexts\0"          /* the main place for images */
+                            "/usr/local/lib/confexts\0"
+                            "/usr/lib/confexts\0",
 };
 
 /* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
@@ -1174,7 +1179,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
                 _cleanup_free_ char *hostname = NULL;
                 _cleanup_free_ char *path = NULL;
 
-                if (i->class == IMAGE_EXTENSION) {
+                if (i->class == IMAGE_SYSEXT) {
                         r = extension_has_forbidden_content(i->path);
                         if (r < 0)
                                 return r;
@@ -1212,7 +1217,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
                 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");
 
@@ -1345,11 +1350,3 @@ static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
 };
 
 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
-
-static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
-        [IMAGE_MACHINE] = "machine",
-        [IMAGE_PORTABLE] = "portable",
-        [IMAGE_EXTENSION] = "extension",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
index c423132a62b3855cc17b89f2f2bfa5d855b9bfb7..edfb1412a446d61d940f641f54a69889db210d56 100644 (file)
 #include "image-policy.h"
 #include "lock-util.h"
 #include "macro.h"
+#include "os-util.h"
 #include "path-util.h"
 #include "string-util.h"
 #include "time-util.h"
 
-typedef enum ImageClass {
-        IMAGE_MACHINE,
-        IMAGE_PORTABLE,
-        IMAGE_EXTENSION,
-        _IMAGE_CLASS_MAX,
-        _IMAGE_CLASS_INVALID = -EINVAL,
-} ImageClass;
-
 typedef enum ImageType {
         IMAGE_DIRECTORY,
         IMAGE_SUBVOLUME,
@@ -78,9 +71,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 5ec4fbf2e1360e3518f9e2d2c9a5377ebff2884d..46c42cfc953bc554648d56001d505115f7f93c0d 100644 (file)
@@ -2060,7 +2060,7 @@ int dissected_image_mount(
                                 if (r < 0)
                                         return r;
                                 if (r == 0) {
-                                        r = path_is_extension_tree(where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
+                                        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)
@@ -3323,7 +3323,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;
@@ -3848,7 +3848,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);
 
@@ -3858,7 +3858,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 f1eb7987f48c52f54386971be8338e35eca6c887..9fd74973aa2d62b95e015e861dd3bec27882c38f 100644 (file)
 #include "mkdir-label.h"
 #include "path-util.h"
 #include "process-util.h"
-#include "selinux-util.h"
-#include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
-#include "tmpfile-util.h"
+#include "tmpfile-util-label.h"
 
 void edit_file_context_done(EditFileContext *context) {
         int r;
@@ -103,6 +101,9 @@ int edit_files_add(
 
 static int create_edit_temp_file(EditFile *e) {
         _cleanup_(unlink_and_freep) char *temp = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        const char *source;
+        bool has_original, has_target;
         unsigned line = 1;
         int r;
 
@@ -114,62 +115,37 @@ static int create_edit_temp_file(EditFile *e) {
         if (e->temp)
                 return 0;
 
-        r = tempfn_random(e->path, NULL, &temp);
+        r = mkdir_parents_label(e->path, 0755);
         if (r < 0)
-                return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", e->path);
+                return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
 
-        r = mkdir_parents_label(e->path, 0755);
+        r = fopen_temporary_label(e->path, e->path, &f, &temp);
         if (r < 0)
-                return log_error_errno(r, "Failed to create parent directories for \"%s\": %m", e->path);
+                return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
 
-        if (!e->original_path && !e->comment_paths) {
-                r = mac_selinux_create_file_prepare(e->path, S_IFREG);
-                if (r < 0)
-                        return r;
+        if (fchmod(fileno(f), 0644) < 0)
+                return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
 
-                r = touch(temp);
-                mac_selinux_create_file_clear();
-                if (r < 0)
-                        return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
-        }
+        has_original = e->original_path && access(e->original_path, F_OK) >= 0;
+        has_target = access(e->path, F_OK) >= 0;
 
-        if (e->original_path) {
-                r = mac_selinux_create_file_prepare(e->path, S_IFREG);
-                if (r < 0)
-                        return r;
-
-                r = copy_file(e->original_path, temp, 0, 0644, COPY_REFLINK);
-                if (r == -ENOENT) {
-                        r = touch(temp);
-                        mac_selinux_create_file_clear();
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
-                } else {
-                        mac_selinux_create_file_clear();
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", e->path);
-                }
-        }
+        if (has_original && (!has_target || e->context->overwrite_with_origin))
+                /* We are asked to overwrite target with original_path or target doesn't exist. */
+                source = e->original_path;
+        else if (has_target)
+                /* Target exists and shouldn't be overwritten. */
+                source = e->path;
+        else
+                source = NULL;
 
         if (e->comment_paths) {
-                _cleanup_free_ char *target_contents = NULL;
-                _cleanup_fclose_ FILE *f = NULL;
+                _cleanup_free_ char *source_contents = NULL;
 
-                r = mac_selinux_create_file_prepare(e->path, S_IFREG);
-                if (r < 0)
-                        return r;
-
-                f = fopen(temp, "we");
-                mac_selinux_create_file_clear();
-                if (!f)
-                        return log_error_errno(errno, "Failed to open temporary file \"%s\": %m", temp);
-
-                if (fchmod(fileno(f), 0644) < 0)
-                        return log_error_errno(errno, "Failed to change mode of temporary file \"%s\": %m", temp);
-
-                r = read_full_file(e->path, &target_contents, NULL);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read target file \"%s\": %m", e->path);
+                if (source) {
+                        r = read_full_file(source, &source_contents, NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to read source file '%s': %m", source);
+                }
 
                 fprintf(f,
                         "### Editing %s\n"
@@ -180,41 +156,47 @@ static int create_edit_temp_file(EditFile *e) {
                         "%s\n",
                         e->path,
                         e->context->marker_start,
-                        strempty(target_contents),
-                        target_contents && endswith(target_contents, "\n") ? "" : "\n",
+                        strempty(source_contents),
+                        source_contents && endswith(source_contents, "\n") ? "" : "\n",
                         e->context->marker_end);
 
                 line = 4; /* Start editing at the contents area */
 
-                /* Add a comment with the contents of the original files */
                 STRV_FOREACH(path, e->comment_paths) {
-                        _cleanup_free_ char *contents = NULL;
+                        _cleanup_free_ char *comment = NULL;
 
-                        /* Skip the file that's being edited, already processed in above */
-                        if (path_equal(*path, e->path))
+                        /* Skip the file which is being edited and the source file (can be the same) */
+                        if (PATH_IN_SET(*path, e->path, source))
                                 continue;
 
-                        r = read_full_file(*path, &contents, NULL);
+                        r = read_full_file(*path, &comment, NULL);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to read original file \"%s\": %m", *path);
+                                return log_error_errno(r, "Failed to read comment file '%s': %m", *path);
 
                         fprintf(f, "\n\n### %s", *path);
-                        if (!isempty(contents)) {
-                                _cleanup_free_ char *commented_contents = NULL;
 
-                                commented_contents = strreplace(strstrip(contents), "\n", "\n# ");
-                                if (!commented_contents)
+                        if (!isempty(comment)) {
+                                _cleanup_free_ char *c = NULL;
+
+                                c = strreplace(strstrip(comment), "\n", "\n# ");
+                                if (!c)
                                         return log_oom();
 
-                                fprintf(f, "\n# %s", commented_contents);
+                                fprintf(f, "\n# %s", c);
                         }
                 }
-
-                r = fflush_and_check(f);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
+        } else if (source) {
+                r = copy_file_fd(source, fileno(f), COPY_REFLINK);
+                if (r < 0) {
+                        assert(r != -ENOENT);
+                        return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", source, temp);
+                }
         }
 
+        r = fflush_and_check(f);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp);
+
         e->temp = TAKE_PTR(temp);
         e->line = line;
 
@@ -310,7 +292,7 @@ static int strip_edit_temp_file(EditFile *e) {
 
         r = read_full_file(e->temp, &old_contents, NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to read temporary file \"%s\": %m", e->temp);
+                return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp);
 
         if (e->context->marker_start) {
                 /* Trim out the lines between the two markers */
@@ -344,7 +326,7 @@ static int strip_edit_temp_file(EditFile *e) {
 
         r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
         if (r < 0)
-                return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", e->temp);
+                return log_error_errno(r, "Failed to strip temporary file '%s': %m", e->temp);
 
         return 1; /* Contents have real changes and are changed after stripping */
 }
@@ -377,7 +359,10 @@ int do_edit_files_and_install(EditFileContext *context) {
 
                 r = RET_NERRNO(rename(i->temp, i->path));
                 if (r < 0)
-                        return log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", i->temp, i->path);
+                        return log_error_errno(r,
+                                               "Failed to rename temporary file '%s' to target file '%s': %m",
+                                               i->temp,
+                                               i->path);
                 i->temp = mfree(i->temp);
 
                 log_info("Successfully installed edited file '%s'.", i->path);
index 63c6190ef84607015530ab9dc86503b998276bf8..83b3df86839f018af81e757d7212cc9f1364321c 100644 (file)
@@ -3,6 +3,9 @@
 
 #include <stdbool.h>
 
+#define DROPIN_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file"
+#define DROPIN_MARKER_END "### Edits below this comment will be discarded"
+
 typedef struct EditFile EditFile;
 typedef struct EditFileContext EditFileContext;
 
@@ -21,6 +24,7 @@ struct EditFileContext {
         const char *marker_start;
         const char *marker_end;
         bool remove_parent;
+        bool overwrite_with_origin; /* whether to always overwrite target with original file */
 };
 
 void edit_file_context_done(EditFileContext *context);
index fa83f6b6fdc7549d6fba1e932f29e12eb187bf1f..43a19bf2625d21d6eeb327a6afb4adef61c4a8a1 100644 (file)
@@ -13,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;
                 }
         }
@@ -54,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;
@@ -81,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)) {
@@ -100,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;
                 }
@@ -110,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;
@@ -120,16 +123,21 @@ 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;
 
index fba8acaf199c74490b9009ed5acb2c9d68824aef..3cad219d51beb87f9a1d652e7ae59c9b01882b1e 100644 (file)
@@ -1,6 +1,8 @@
 /* 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. */
@@ -8,12 +10,13 @@ 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);
 
-/* Parse SYSTEMD_SYSEXT_HIERARCHIES and if not set, return "/usr /opt" */
-int parse_env_extension_hierarchies(char ***ret_hierarchies);
+/* 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.) */
index 0d45249d6327ce2369b5b77c00235663420fbd97..6a0002a2bdfa07f8b990f43b70db915cc02cb6f3 100644 (file)
@@ -540,11 +540,9 @@ int find_esp_and_warn(
                 return r;
 
         if (ret_path) {
-                char *q = path_join(empty_to_root(root), p);
-                if (!q)
-                        return -ENOMEM;
-
-                *ret_path = TAKE_PTR(q);
+                r = path_prefix_root_cwd(p, root, ret_path);
+                if (r < 0)
+                        return r;
         }
         if (ret_part)
                 *ret_part = part;
@@ -861,11 +859,9 @@ int find_xbootldr_and_warn(
                 return r;
 
         if (ret_path) {
-                char *q = path_join(empty_to_root(root), p);
-                if (!q)
-                        return -ENOMEM;
-
-                *ret_path = TAKE_PTR(q);
+                r = path_prefix_root_cwd(p, root, ret_path);
+                if (r < 0)
+                        return r;
         }
         if (ret_uuid)
                 *ret_uuid = uuid;
index b5ea1bb2a0bcac3ce1162fafe23886d6c4f20ecd..3d2ec820d96efa3b1c03204453a09fbed4c2124d 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "fd-util.h"
+#include "fileio.h"
 #include "env-file.h"
 #include "kernel-image.h"
 #include "os-util.h"
@@ -255,6 +256,7 @@ static int inspect_uki(
 }
 
 int inspect_kernel(
+                int dir_fd,
                 const char *filename,
                 KernelImageType *ret_type,
                 char **ret_cmdline,
@@ -267,11 +269,12 @@ int inspect_kernel(
         KernelImageType t;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(filename);
 
-        f = fopen(filename, "re");
-        if (!f)
-                return log_error_errno(errno, "Failed to open kernel image file '%s': %m", filename);
+        r = xfopenat(dir_fd, filename, "re", 0, &f);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open kernel image file '%s': %m", filename);
 
         r = pe_sections(f, &sections, &scount);
         if (r < 0)
index d1875920cc7ef7f73cca027d9e01135387cfe0b1..41b2c08f9a6ea410f78f515ca9b4ebcfcf49db66 100644 (file)
@@ -16,6 +16,7 @@ typedef enum KernelImageType {
 const char* kernel_image_type_to_string(KernelImageType t) _const_;
 
 int inspect_kernel(
+                int dir_fd,
                 const char *filename,
                 KernelImageType *ret_type,
                 char **ret_cmdline,
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 42111d27728892762baee4365de031988a004bc7..a8248901ce6c9c41fc25fee55c855966973fc70d 100644 (file)
@@ -25,6 +25,7 @@
 #include "exec-util.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "getopt-defs.h"
 #include "initrd-util.h"
 #include "killall.h"
 #include "log.h"
@@ -50,23 +51,13 @@ static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
 
 static int parse_argv(int argc, char *argv[]) {
         enum {
-                ARG_LOG_LEVEL = 0x100,
-                ARG_LOG_TARGET,
-                ARG_LOG_COLOR,
-                ARG_LOG_LOCATION,
-                ARG_LOG_TIME,
-                ARG_EXIT_CODE,
-                ARG_TIMEOUT,
+                COMMON_GETOPT_ARGS,
+                SHUTDOWN_GETOPT_ARGS,
         };
 
         static const struct option options[] = {
-                { "log-level",     required_argument, NULL, ARG_LOG_LEVEL    },
-                { "log-target",    required_argument, NULL, ARG_LOG_TARGET   },
-                { "log-color",     optional_argument, NULL, ARG_LOG_COLOR    },
-                { "log-location",  optional_argument, NULL, ARG_LOG_LOCATION },
-                { "log-time",      optional_argument, NULL, ARG_LOG_TIME     },
-                { "exit-code",     required_argument, NULL, ARG_EXIT_CODE    },
-                { "timeout",       required_argument, NULL, ARG_TIMEOUT      },
+                COMMON_GETOPT_OPTIONS,
+                SHUTDOWN_GETOPT_OPTIONS,
                 {}
         };
 
@@ -75,6 +66,10 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 1);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
+
         /* "-" prevents getopt from permuting argv[] and moving the verb away
          * from argv[1]. Our interface to initrd promises it'll be there. */
         while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0)
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 f784627e82036860b5bdc794817fa562531c6c3a..3fc6b910c4e1ff97be9492b25317825a3be8af54 100644 (file)
@@ -39,7 +39,7 @@
 #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;
@@ -47,10 +47,43 @@ static bool arg_legend = true;
 static bool arg_force = false;
 static ImagePolicy *arg_image_policy = NULL;
 
+/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
+static ImageClass arg_image_class = IMAGE_SYSEXT;
+
 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+        const char *dot_directory_name;
+        const char *directory_name;
+        const char *short_identifier;
+        const char *short_identifier_plural;
+        const char *level_env;
+        const char *scope_env;
+        const char *name_env;
+} 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;
@@ -72,26 +105,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);
@@ -207,7 +240,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();
 
@@ -274,7 +307,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;
 
@@ -317,7 +350,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();
 
@@ -385,12 +418,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();
 
@@ -430,7 +463,7 @@ static const ImagePolicy *pick_image_policy(const Image *img) {
 
 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;
@@ -457,11 +490,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))
@@ -473,7 +508,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();
 
@@ -595,7 +630,8 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                                         host_os_release_version_id,
                                         host_os_release_sysext_level,
                                         in_initrd() ? "initrd" : "system",
-                                        img->extension_release);
+                                        img->extension_release,
+                                        arg_image_class);
                         if (r < 0)
                                 return r;
                         if (r == 0) {
@@ -640,7 +676,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();
 
@@ -716,7 +752,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) {
@@ -732,7 +768,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;
 
@@ -750,9 +786,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, &image_policy_sysext);
@@ -853,9 +889,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.");
@@ -891,11 +927,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"
@@ -1022,6 +1058,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();
 
@@ -1032,9 +1069,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 4bbcd7a13ba8a520cc37d7e4c9ac28190a9cd3f2..8df25b515de3f8189696ffcf6319da68d86b7cfd 100644 (file)
@@ -11,8 +11,6 @@ int verb_add_dependency(int argc, char *argv[], void *userdata) {
         _cleanup_strv_free_ char **names = NULL;
         _cleanup_free_ char *target = NULL;
         const char *verb = argv[0];
-        InstallChange *changes = NULL;
-        size_t n_changes = 0;
         UnitDependency dep;
         int r;
 
@@ -37,11 +35,15 @@ int verb_add_dependency(int argc, char *argv[], void *userdata) {
                 assert_not_reached();
 
         if (install_client_side()) {
+                InstallChange *changes = NULL;
+                size_t n_changes = 0;
+
+                CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
                 r = unit_file_add_dependency(arg_runtime_scope, unit_file_flags_from_args(), arg_root, names, target, dep, &changes, &n_changes);
                 install_changes_dump(r, "add dependency on", changes, n_changes, arg_quiet);
-
-                if (r > 0)
-                        r = 0;
+                if (r < 0)
+                        return r;
         } else {
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -69,22 +71,16 @@ int verb_add_dependency(int argc, char *argv[], void *userdata) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add dependency: %s", bus_error_message(&error, r));
 
-                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
-                if (arg_no_reload) {
-                        r = 0;
-                        goto finish;
+                if (!arg_no_reload) {
+                        r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
+                        if (r < 0)
+                                return r;
                 }
-
-                r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
-                if (r > 0)
-                        r = 0;
         }
 
-finish:
-        install_changes_free(changes, n_changes);
-
-        return r;
+        return 0;
 }
index 5f42dc239ff958e93a4108c55d670bce97c949ae..561b01a67a46730cdeaa8d23f42be12ab05da7c6 100644 (file)
@@ -13,9 +13,6 @@
 #include "systemctl.h"
 #include "terminal-util.h"
 
-#define EDIT_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file"
-#define EDIT_MARKER_END "### Edits below this comment will be discarded"
-
 int verb_cat(int argc, char *argv[], void *userdata) {
         _cleanup_(hashmap_freep) Hashmap *cached_name_map = NULL, *cached_id_map = NULL;
         _cleanup_(lookup_paths_free) LookupPaths lp = {};
@@ -316,9 +313,10 @@ static int find_paths_to_edit(
 
 int verb_edit(int argc, char *argv[], void *userdata) {
         _cleanup_(edit_file_context_done) EditFileContext context = {
-                .marker_start = EDIT_MARKER_START,
-                .marker_end = EDIT_MARKER_END,
+                .marker_start = DROPIN_MARKER_START,
+                .marker_end = DROPIN_MARKER_END,
                 .remove_parent = !arg_full,
+                .overwrite_with_origin = true,
         };
         _cleanup_(lookup_paths_free) LookupPaths lp = {};
         _cleanup_strv_free_ char **names = NULL;
index 0cc8cbb6b83f4ae19ad217748d8ac8db31759702..6d3709705e036fc2f12822b49f88d586d2d863a3 100644 (file)
@@ -64,8 +64,6 @@ static int normalize_names(char **names) {
 int verb_enable(int argc, char *argv[], void *userdata) {
         _cleanup_strv_free_ char **names = NULL;
         const char *verb = argv[0];
-        InstallChange *changes = NULL;
-        size_t n_changes = 0;
         int carries_install_info = -1;
         bool ignore_carries_install_info = arg_quiet || arg_no_warn;
         int r;
@@ -104,6 +102,10 @@ int verb_enable(int argc, char *argv[], void *userdata) {
 
         if (install_client_side()) {
                 UnitFileFlags flags;
+                InstallChange *changes = NULL;
+                size_t n_changes = 0;
+
+                CLEANUP_ARRAY(changes, n_changes, install_changes_free);
 
                 flags = unit_file_flags_from_args();
                 if (streq(verb, "enable")) {
@@ -130,8 +132,7 @@ int verb_enable(int argc, char *argv[], void *userdata) {
 
                 install_changes_dump(r, verb, changes, n_changes, arg_quiet);
                 if (r < 0)
-                        goto finish;
-                r = 0;
+                        return r;
         } else {
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -234,17 +235,16 @@ int verb_enable(int argc, char *argv[], void *userdata) {
                                 return bus_log_parse_error(r);
                 }
 
-                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
                 /* Try to reload if enabled */
                 if (!arg_no_reload) {
                         r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
-                        if (r > 0)
-                                r = 0;
-                } else
-                        r = 0;
+                        if (r < 0)
+                                return r;
+                }
         }
 
         if (carries_install_info == 0 && !ignore_carries_install_info)
@@ -306,7 +306,7 @@ int verb_enable(int argc, char *argv[], void *userdata) {
 
                 r = acquire_bus(BUS_MANAGER, &bus);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
                 len = strv_length(names);
                 {
@@ -321,8 +321,5 @@ int verb_enable(int argc, char *argv[], void *userdata) {
                 }
         }
 
-finish:
-        install_changes_free(changes, n_changes);
-
-        return r;
+        return 0;
 }
index ed117e077c713d644df6f6d3e7406466d071a9d8..b55f8e35ca74474dcf0d38a6693857ef11be670e 100644 (file)
@@ -8,11 +8,14 @@
 #include "systemctl.h"
 
 int verb_preset_all(int argc, char *argv[], void *userdata) {
-        InstallChange *changes = NULL;
-        size_t n_changes = 0;
         int r;
 
         if (install_client_side()) {
+                InstallChange *changes = NULL;
+                size_t n_changes = 0;
+
+                CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
                 r = unit_file_preset_all(arg_runtime_scope, unit_file_flags_from_args(), arg_root, arg_preset_mode, &changes, &n_changes);
                 install_changes_dump(r, "preset", changes, n_changes, arg_quiet);
 
@@ -42,22 +45,16 @@ int verb_preset_all(int argc, char *argv[], void *userdata) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to preset all units: %s", bus_error_message(&error, r));
 
-                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
-                if (arg_no_reload) {
-                        r = 0;
-                        goto finish;
+                if (!arg_no_reload) {
+                        r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
+                        if (r < 0)
+                                return r;
                 }
-
-                r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
-                if (r > 0)
-                        r = 0;
         }
 
-finish:
-        install_changes_free(changes, n_changes);
-
-        return r;
+        return 0;
 }
index 28886c83560b475952c32a4e4dac269e0fe4a5f5..136a4f3b6679b9066ece6ff754bec54f4e40288f 100644 (file)
@@ -95,8 +95,6 @@ int verb_get_default(int argc, char *argv[], void *userdata) {
 
 int verb_set_default(int argc, char *argv[], void *userdata) {
         _cleanup_free_ char *unit = NULL;
-        InstallChange *changes = NULL;
-        size_t n_changes = 0;
         int r;
 
         assert(argc >= 2);
@@ -109,10 +107,15 @@ int verb_set_default(int argc, char *argv[], void *userdata) {
                 return log_error_errno(r, "Failed to mangle unit name: %m");
 
         if (install_client_side()) {
+                InstallChange *changes = NULL;
+                size_t n_changes = 0;
+
+                CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
                 r = unit_file_set_default(arg_runtime_scope, UNIT_FILE_FORCE, arg_root, unit, &changes, &n_changes);
                 install_changes_dump(r, "set default", changes, n_changes, arg_quiet);
                 if (r < 0)
-                        goto finish;
+                        return r;
         } else {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
@@ -128,15 +131,15 @@ int verb_set_default(int argc, char *argv[], void *userdata) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r));
 
-                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes);
+                r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
                 /* Try to reload if enabled */
                 if (!arg_no_reload) {
                         r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
                         if (r < 0)
-                                goto finish;
+                                return r;
                 }
         }
 
@@ -147,14 +150,11 @@ int verb_set_default(int argc, char *argv[], void *userdata) {
 
                 r = determine_default(&final);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
                 if (!streq(final, unit))
                         log_notice("Note: \"%s\" is the default unit (possibly a runtime override).", final);
         }
 
-finish:
-        install_changes_free(changes, n_changes);
-
-        return r < 0 ? r : 0;
+        return 0;
 }
index 4a14c105dd733c065350113b6084f0c3726b8e44..6422550af48afa54409f5068630385eaafe2e365 100644 (file)
@@ -516,7 +516,7 @@ static void print_status_info(
         if (!i->condition_result && i->condition_timestamp > 0) {
                 int n = 0;
 
-                printf("  Condition: start %scondition failed%s at %s; %s\n",
+                printf("  Condition: start %scondition unmet%s at %s; %s\n",
                        ansi_highlight_yellow(), ansi_normal(),
                        FORMAT_TIMESTAMP_STYLE(i->condition_timestamp, arg_timestamp_style),
                        FORMAT_TIMESTAMP_RELATIVE(i->condition_timestamp));
index 136b98012a5e5e83be8eb09a451d05f41ee0cc55..3dac7da460b417e357c66e36ba9b12321ed0f962 100644 (file)
@@ -162,7 +162,14 @@ fail:
         if (arg_action != ACTION_SYSTEMCTL)
                 return r;
 
-        log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
+        if (sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED) &&
+            STR_IN_SET(method, "TryRestartUnit", "ReloadOrTryRestartUnit")) {
+                /* Ignore masked unit if try-* is requested */
+
+                log_debug_errno(r, "Failed to %s %s, ignoring: %s", job_type, name, bus_error_message(error, r));
+                return 0;
+        } else
+                log_error_errno(r, "Failed to %s %s: %s", job_type, name, bus_error_message(error, r));
 
         if (!sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT,
                                            BUS_ERROR_UNIT_MASKED,
@@ -359,7 +366,6 @@ int verb_start(int argc, char *argv[], void *userdata) {
 
         if (arg_marked)
                 ret = enqueue_marked_jobs(bus, w);
-
         else
                 STRV_FOREACH(name, names) {
                         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
index da0f5e137a75ea669301101d080ae27fc9985ad1..6180e19839ff0f8d402ac750c1624a77f111ebef 100644 (file)
@@ -102,7 +102,7 @@ static void test_compress_decompress(const char* label, const char* type,
 
                 r = compress(text, size, buf, size, &j);
                 /* assume compression must be successful except for small or random inputs */
-                assert_se(r > 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
+                assert_se(r >= 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
 
                 /* check for overwrites */
                 assert_se(buf[size] == 0);
index f5ec47cb3c8a6216a1331e1c3a7a12601b7fef3c..18f8ce3b351e139618ceba79f7600052eaf97ab0 100644 (file)
@@ -46,7 +46,6 @@ typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size);
 
 #if HAVE_COMPRESSION
 _unused_ static void test_compress_decompress(
-                int flag,
                 const char *compression,
                 compress_blob_t compress,
                 decompress_blob_t decompress,
@@ -67,7 +66,7 @@ _unused_ static void test_compress_decompress(
                 log_info_errno(r, "compression failed: %m");
                 assert_se(may_fail);
         } else {
-                assert_se(r == flag);
+                assert_se(r >= 0);
                 r = decompress(compressed, csize,
                                (void **) &decompressed, &csize, 0);
                 assert_se(r == 0);
@@ -120,7 +119,7 @@ _unused_ static void test_decompress_startswith(const char *compression,
                 assert_se(compressed2);
                 r = compress(data, data_len, compressed, BUFSIZE_2, &csize);
         }
-        assert_se(r > 0);
+        assert_se(r >= 0);
 
         len = strlen(data);
 
@@ -151,7 +150,7 @@ _unused_ static void test_decompress_startswith_short(const char *compression,
         log_info("/* %s with %s */", __func__, compression);
 
         r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize);
-        assert_se(r > 0);
+        assert_se(r >= 0);
 
         for (size_t i = 1; i < strlen(TEXT); i++) {
                 _cleanup_free_ void *buf2 = NULL;
@@ -163,8 +162,7 @@ _unused_ static void test_decompress_startswith_short(const char *compression,
         }
 }
 
-_unused_ static void test_compress_stream(int flag,
-                                          const char *compression,
+_unused_ static void test_compress_stream(const char *compression,
                                           const char *cat,
                                           compress_stream_t compress,
                                           decompress_stream_t decompress,
@@ -195,7 +193,7 @@ _unused_ static void test_compress_stream(int flag,
 
         assert_se((dst = mkostemp_safe(pattern)) >= 0);
 
-        assert_se(compress(src, dst, -1, &uncompressed_size) == flag);
+        assert_se(compress(src, dst, -1, &uncompressed_size) >= 0);
 
         if (cat) {
                 assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0);
@@ -293,11 +291,9 @@ int main(int argc, char *argv[]) {
         random_bytes(data + 7, sizeof(data) - 7);
 
 #if HAVE_XZ
-        test_compress_decompress(COMPRESSION_XZ, "XZ",
-                                 compress_blob_xz, decompress_blob_xz,
+        test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz,
                                  text, sizeof(text), false);
-        test_compress_decompress(COMPRESSION_XZ, "XZ",
-                                 compress_blob_xz, decompress_blob_xz,
+        test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz,
                                  data, sizeof(data), true);
 
         test_decompress_startswith("XZ",
@@ -310,7 +306,7 @@ int main(int argc, char *argv[]) {
                                    compress_blob_xz, decompress_startswith_xz,
                                    huge, HUGE_SIZE, true);
 
-        test_compress_stream(COMPRESSION_XZ, "XZ", "xzcat",
+        test_compress_stream("XZ", "xzcat",
                              compress_stream_xz, decompress_stream_xz, srcfile);
 
         test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz);
@@ -320,11 +316,9 @@ int main(int argc, char *argv[]) {
 #endif
 
 #if HAVE_LZ4
-        test_compress_decompress(COMPRESSION_LZ4, "LZ4",
-                                 compress_blob_lz4, decompress_blob_lz4,
+        test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4,
                                  text, sizeof(text), false);
-        test_compress_decompress(COMPRESSION_LZ4, "LZ4",
-                                 compress_blob_lz4, decompress_blob_lz4,
+        test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4,
                                  data, sizeof(data), true);
 
         test_decompress_startswith("LZ4",
@@ -337,7 +331,7 @@ int main(int argc, char *argv[]) {
                                    compress_blob_lz4, decompress_startswith_lz4,
                                    huge, HUGE_SIZE, true);
 
-        test_compress_stream(COMPRESSION_LZ4, "LZ4", "lz4cat",
+        test_compress_stream("LZ4", "lz4cat",
                              compress_stream_lz4, decompress_stream_lz4, srcfile);
 
         test_lz4_decompress_partial();
@@ -349,11 +343,9 @@ int main(int argc, char *argv[]) {
 #endif
 
 #if HAVE_ZSTD
-        test_compress_decompress(COMPRESSION_ZSTD, "ZSTD",
-                                 compress_blob_zstd, decompress_blob_zstd,
+        test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd,
                                  text, sizeof(text), false);
-        test_compress_decompress(COMPRESSION_ZSTD, "ZSTD",
-                                 compress_blob_zstd, decompress_blob_zstd,
+        test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd,
                                  data, sizeof(data), true);
 
         test_decompress_startswith("ZSTD",
@@ -366,7 +358,7 @@ int main(int argc, char *argv[]) {
                                    compress_blob_zstd, decompress_startswith_zstd,
                                    huge, HUGE_SIZE, true);
 
-        test_compress_stream(COMPRESSION_ZSTD, "ZSTD", "zstdcat",
+        test_compress_stream("ZSTD", "zstdcat",
                              compress_stream_zstd, decompress_stream_zstd, srcfile);
 
         test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd);
index beda749fb88b5fb873f2d478f29dd8430e371b46..4253490443f894b407f665d5b009605a4a54c08b 100644 (file)
 
 #include "alloc-util.h"
 #include "conf-files.h"
+#include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
 #include "macro.h"
 #include "mkdir.h"
-#include "parse-util.h"
 #include "path-util.h"
 #include "rm-rf.h"
 #include "string-util.h"
 #include "strv.h"
 #include "tests.h"
-#include "user-util.h"
+#include "tmpfile-util.h"
 
-static void setup_test_dir(char *tmp_dir, const char *files, ...) {
-        va_list ap;
+TEST(conf_files_list) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF;
+        _cleanup_strv_free_ char **result = NULL;
+        const char *search1, *search2, *search1_a, *search1_b, *search1_c, *search2_aa;
+
+        tfd = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t);
+        assert(tfd >= 0);
 
-        assert_se(mkdtemp(tmp_dir));
+        assert_se(mkdirat(tfd, "dir1", 0755) >= 0);
+        assert_se(mkdirat(tfd, "dir2", 0755) >= 0);
 
-        va_start(ap, files);
-        while (files) {
-                _cleanup_free_ char *path;
+        search1 = strjoina(t, "/dir1/");
+        search2 = strjoina(t, "/dir2/");
 
-                assert_se(path = path_join(tmp_dir, files));
-                assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0);
+        FOREACH_STRING(p, "a.conf", "b.conf", "c.foo") {
+                _cleanup_free_ char *path = NULL;
 
-                files = va_arg(ap, const char *);
+                assert_se(path = path_join(search1, p));
+                assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0);
         }
-        va_end(ap);
-}
 
-static void test_conf_files_list_one(bool use_root) {
-        char tmp_dir[] = "/tmp/test-conf-files-XXXXXX";
-        _cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL;
-        const char *root_dir, *search, *expect_a, *expect_b, *expect_c, *mask;
+        assert_se(symlinkat("/dev/null", tfd, "dir1/m.conf") >= 0);
+
+        FOREACH_STRING(p, "a.conf", "aa.conf", "m.conf") {
+                _cleanup_free_ char *path = NULL;
 
-        log_info("/* %s(%s) */", __func__, yes_no(use_root));
+                assert_se(path = path_join(search2, p));
+                assert_se(write_string_file(path, "hogehoge", WRITE_STRING_FILE_CREATE) >= 0);
+        }
 
-        setup_test_dir(tmp_dir,
-                       "/dir/a.conf",
-                       "/dir/b.conf",
-                       "/dir/c.foo",
-                       NULL);
+        search1_a = strjoina(search1, "a.conf");
+        search1_b = strjoina(search1, "b.conf");
+        search1_c = strjoina(search1, "c.foo");
+        search2_aa = strjoina(search2, "aa.conf");
 
-        mask = strjoina(tmp_dir, "/dir/d.conf");
-        assert_se(symlink("/dev/null", mask) >= 0);
+        /* search dir1 without suffix */
+        assert_se(conf_files_list(&result, NULL, NULL, CONF_FILES_FILTER_MASKED, search1) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
 
-        if (use_root) {
-                root_dir = tmp_dir;
-                search = "/dir";
-        } else {
-                root_dir = NULL;
-                search = strjoina(tmp_dir, "/dir");
-        }
+        result = strv_free(result);
 
-        expect_a = strjoina(tmp_dir, "/dir/a.conf");
-        expect_b = strjoina(tmp_dir, "/dir/b.conf");
-        expect_c = strjoina(tmp_dir, "/dir/c.foo");
+        assert_se(conf_files_list(&result, NULL, t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
 
-        log_debug("/* Check when filtered by suffix */");
+        result = strv_free(result);
 
-        assert_se(conf_files_list(&found_files, ".conf", root_dir, CONF_FILES_FILTER_MASKED, search) == 0);
-        strv_print(found_files);
+        assert_se(conf_files_list_at(&result, NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c)));
 
-        assert_se(found_files);
-        assert_se(streq_ptr(found_files[0], expect_a));
-        assert_se(streq_ptr(found_files[1], expect_b));
-        assert_se(!found_files[2]);
+        result = strv_free(result);
 
-        log_debug("/* Check when unfiltered */");
-        assert_se(conf_files_list(&found_files2, NULL, root_dir, CONF_FILES_FILTER_MASKED, search) == 0);
-        strv_print(found_files2);
+        assert_se(conf_files_list_at(&result, NULL, tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf", "dir1/c.foo")));
 
-        assert_se(found_files2);
-        assert_se(streq_ptr(found_files2[0], expect_a));
-        assert_se(streq_ptr(found_files2[1], expect_b));
-        assert_se(streq_ptr(found_files2[2], expect_c));
-        assert_se(!found_files2[3]);
+        result = strv_free(result);
 
-        assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
-}
+        /* search dir1 with suffix */
+        assert_se(conf_files_list(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, search1) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
 
-TEST(conf_files_list) {
-        test_conf_files_list_one(false);
-        test_conf_files_list_one(true);
+        result = strv_free(result);
+
+        assert_se(conf_files_list(&result, ".conf", t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b)));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf")));
+
+        result = strv_free(result);
+
+        /* search two dirs */
+        assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b)));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir2/aa.conf", "dir1/b.conf")));
+
+        result = strv_free(result);
+
+        /* filename only */
+        assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
+
+        result = strv_free(result);
+
+        assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0);
+        strv_print(result);
+        assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf")));
 }
 
 static void test_conf_files_insert_one(const char *root) {
index 4cd83dbeeaab5f5927e101e40d899b6c641770bc..df08b7ed618734b32630d266a84d0bc80bb2893b 100644 (file)
@@ -301,7 +301,7 @@ static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink,
 
         log_info("%s try_reflink=%s max_bytes=%" PRIu64, __func__, yes_no(try_reflink), max_bytes);
 
-        fd = open(src, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+        fd = open(src, O_CLOEXEC | O_PATH);
         assert_se(fd >= 0);
 
         fd2 = mkostemp_safe(fn2);
index bcc98ae88ab14e3cd0eed57383ff85926266764c..8f2293b6c0bcb7092ab2ac212a4ddc9eb89cdc6d 100644 (file)
@@ -479,12 +479,9 @@ TEST(getenv_steal_erase) {
 }
 
 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));
+        assert_se(strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "PATH")));
+        assert_se(!strv_env_name_is_valid(STRV_MAKE("", "PATH", "home", "user", "SHELL")));
+        assert_se(!strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "USER")));
 }
 
 DEFINE_TEST_MAIN(LOG_DEBUG);
index d8d622e44df0e15d984836cc9cfd30ad2e498694..a570941cf303fe6dd937908c4345c40a86d62f50 100644 (file)
@@ -1067,7 +1067,8 @@ static void test_read_virtual_file_one(size_t max_size) {
                                   IN_SET(r,
                                          -ENOENT,  /* Some of the files might be absent */
                                          -EINVAL,  /* too small reads from /proc/self/pagemap trigger EINVAL */
-                                         -EFBIG)); /* /proc/kcore and /proc/self/pagemap should be too large */
+                                         -EFBIG,   /* /proc/kcore and /proc/self/pagemap should be too large */
+                                         -EBADF)); /* /proc/kcore is masked when we are running in docker. */
                 } else
                         log_info("read_virtual_file(\"%s\", %zu): %s (%zu bytes)", filename, max_size, r ? "non-truncated" : "truncated", size);
         }
index bc9e3ec91bc0f2aab0ecd6049b5b2de64a2cc558..84e55e175445bfc63cbb5b5354d7ef5f4484f0b5 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, "test", false, "ID", &id, "VERSION_ID", &version_id) == 0);
+        log_info("ID: %s VERSION_ID: %s", id, version_id);
+        assert_se(streq(id, "the-id"));
+        assert_se(streq(version_id, "the-version-id"));
+
+        assert_se(b = path_join(tempdir, "/etc/extension-release.d/extension-release.tester"));
+        assert_se(mkdir_parents(b, 0777) >= 0);
+
+        r = write_string_file(b, "ID=\"ignored\" \n ID=\"the-id\" \n VERSION_ID='the-version-id'", WRITE_STRING_FILE_CREATE);
+        if (r < 0)
+                log_error_errno(r, "Failed to write file: %m");
+
+        assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "ID", &id, "VERSION_ID", &version_id) == 0);
+        log_info("ID: %s VERSION_ID: %s", id, version_id);
+        assert_se(streq(id, "the-id"));
+        assert_se(streq(version_id, "the-version-id"));
+
+        assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "FOOBAR", &foobar) == 0);
+        log_info("FOOBAR: %s", strnull(foobar));
+        assert_se(foobar == NULL);
+
+        assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "FOOBAR", &foobar) == 0);
+        log_info("FOOBAR: %s", strnull(foobar));
+        assert_se(foobar == NULL);
+}
+
 TEST(load_os_release_pairs) {
         _cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
         assert_se(write_tmpfile(tmpfile,
index 136005d51fe7f1cf4f053f5e7a8ff82cadcdaf1f..22e8f3481afbf33f1e64c6bdb5b99eafc81f6440 100644 (file)
@@ -46,11 +46,6 @@ TEST(path) {
         assert_se(!path_equal_ptr("/a", "/b"));
         assert_se(!path_equal_ptr("/a", NULL));
         assert_se(!path_equal_ptr(NULL, "/a"));
-
-        assert_se(path_equal_filename("/a/c", "/b/c"));
-        assert_se(path_equal_filename("/a", "/a"));
-        assert_se(!path_equal_filename("/a/b", "/a/c"));
-        assert_se(!path_equal_filename("/b", "/c"));
 }
 
 static void test_path_simplify_one(const char *in, const char *out) {
@@ -150,6 +145,55 @@ TEST(path_compare) {
         test_path_compare_one("/foo/a/b", "/foo/aaa", -1);
 }
 
+static void test_path_compare_filename_one(const char *a, const char *b, int expected) {
+        int r;
+
+        assert_se(path_compare_filename(a, a) == 0);
+        assert_se(path_compare_filename(b, b) == 0);
+
+        r = path_compare_filename(a, b);
+        assert_se((r > 0) == (expected > 0) && (r < 0) == (expected < 0));
+        r = path_compare_filename(b, a);
+        assert_se((r < 0) == (expected > 0) && (r > 0) == (expected < 0));
+
+        assert_se(path_equal_filename(a, a) == 1);
+        assert_se(path_equal_filename(b, b) == 1);
+        assert_se(path_equal_filename(a, b) == (expected == 0));
+        assert_se(path_equal_filename(b, a) == (expected == 0));
+}
+
+TEST(path_compare_filename) {
+        test_path_compare_filename_one("/goo", "/goo", 0);
+        test_path_compare_filename_one("/goo", "/goo", 0);
+        test_path_compare_filename_one("//goo", "/goo", 0);
+        test_path_compare_filename_one("//goo/////", "/goo", 0);
+        test_path_compare_filename_one("goo/////", "goo", 0);
+        test_path_compare_filename_one("/goo/boo", "/goo//boo", 0);
+        test_path_compare_filename_one("//goo/boo", "/goo/boo//", 0);
+        test_path_compare_filename_one("//goo/././//./boo//././//", "/goo/boo//.", 0);
+        test_path_compare_filename_one("/.", "//.///", -1);
+        test_path_compare_filename_one("/x", "x/", 0);
+        test_path_compare_filename_one("x/", "/", 1);
+        test_path_compare_filename_one("/x/./y", "x/y", 0);
+        test_path_compare_filename_one("/x/./y", "/x/y", 0);
+        test_path_compare_filename_one("/x/./././y", "/x/y/././.", 0);
+        test_path_compare_filename_one("./x/./././y", "./x/y/././.", 0);
+        test_path_compare_filename_one(".", "./.", -1);
+        test_path_compare_filename_one(".", "././.", -1);
+        test_path_compare_filename_one("./..", ".", 1);
+        test_path_compare_filename_one("x/.y", "x/y", -1);
+        test_path_compare_filename_one("foo", "/foo", 0);
+        test_path_compare_filename_one("/foo", "/foo/bar", 1);
+        test_path_compare_filename_one("/foo/aaa", "/foo/b", -1);
+        test_path_compare_filename_one("/foo/aaa", "/foo/b/a", 1);
+        test_path_compare_filename_one("/foo/a", "/foo/aaa", -1);
+        test_path_compare_filename_one("/foo/a/b", "/foo/aaa", 1);
+        test_path_compare_filename_one("/a/c", "/b/c", 0);
+        test_path_compare_filename_one("/a", "/a", 0);
+        test_path_compare_filename_one("/a/b", "/a/c", -1);
+        test_path_compare_filename_one("/b", "/c", -1);
+}
+
 TEST(path_equal_root) {
         /* Nail down the details of how path_equal("/", ...) works. */
 
@@ -450,6 +494,45 @@ TEST(fsck_exists) {
         assert_se(fsck_exists_for_fstype("/../bin/") == 0);
 }
 
+TEST(path_prefix_root_cwd) {
+        _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL;
+
+        assert_se(safe_getcwd(&cwd) >= 0);
+
+        assert_se(path_prefix_root_cwd("hoge", NULL, &ret) >= 0);
+        assert_se(expected = path_join(cwd, "hoge"));
+        assert_se(streq(ret, expected));
+
+        ret = mfree(ret);
+        expected = mfree(expected);
+
+        assert_se(path_prefix_root_cwd("/hoge", NULL, &ret) >= 0);
+        assert_se(streq(ret, "/hoge"));
+
+        ret = mfree(ret);
+
+        assert_se(path_prefix_root_cwd("hoge", "/a/b//./c///", &ret) >= 0);
+        assert_se(streq(ret, "/a/b/c/hoge"));
+
+        ret = mfree(ret);
+
+        assert_se(path_prefix_root_cwd("hoge", "a/b//./c///", &ret) >= 0);
+        assert_se(expected = path_join(cwd, "a/b/c/hoge"));
+        assert_se(streq(ret, expected));
+
+        ret = mfree(ret);
+        expected = mfree(expected);
+
+        assert_se(path_prefix_root_cwd("/../hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0);
+        assert_se(streq(ret, "/a/b/c/../hoge/aaa/../././b"));
+
+        ret = mfree(ret);
+
+        assert_se(path_prefix_root_cwd("/../hoge/aaa/../././b", "a/b//./c///", &ret) >= 0);
+        assert_se(expected = path_join(cwd, "a/b/c/../hoge/aaa/../././b"));
+        assert_se(streq(ret, expected));
+}
+
 static void test_path_make_relative_one(const char *from, const char *to, const char *expected) {
         _cleanup_free_ char *z = NULL;
         int r;
@@ -632,9 +715,10 @@ static void test_path_find_first_component_one(
                 r = path_find_first_component(&p, accept_dot_dot, &e);
                 if (r <= 0) {
                         if (r == 0) {
-                                if (path)
+                                if (path) {
                                         assert_se(p == path + strlen_ptr(path));
-                                else
+                                        assert_se(isempty(p));
+                                } else
                                         assert_se(!p);
                                 assert_se(!e);
                         }
@@ -647,6 +731,15 @@ static void test_path_find_first_component_one(
                 assert_se(strcspn(e, "/") == (size_t) r);
                 assert_se(strlen_ptr(*expected) == (size_t) r);
                 assert_se(strneq(e, *expected++, r));
+
+                assert_se(p);
+                log_debug("p=%s", p);
+                if (!isempty(*expected))
+                        assert_se(startswith(p, *expected));
+                else if (ret >= 0) {
+                        assert_se(p == path + strlen_ptr(path));
+                        assert_se(isempty(p));
+                }
         }
 }
 
@@ -668,7 +761,7 @@ TEST(path_find_first_component) {
         test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0);
         test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL);
         test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL);
-        test_path_find_first_component_one("a/foo./b", false, STRV_MAKE("a", "foo.", "b"), 0);
+        test_path_find_first_component_one("a/foo./b//././/", false, STRV_MAKE("a", "foo.", "b"), 0);
 
         test_path_find_first_component_one(NULL, true, NULL, 0);
         test_path_find_first_component_one("", true, NULL, 0);
@@ -684,7 +777,7 @@ TEST(path_find_first_component) {
         test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0);
         test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0);
         test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0);
-        test_path_find_first_component_one("a/foo./b", true, STRV_MAKE("a", "foo.", "b"), 0);
+        test_path_find_first_component_one("a/foo./b//././/", true, STRV_MAKE("a", "foo.", "b"), 0);
 
         memset(foo, 'a', sizeof(foo) -1);
         char_array_0(foo);
@@ -726,6 +819,15 @@ static void test_path_find_last_component_one(
                 assert_se(strcspn(e, "/") == (size_t) r);
                 assert_se(strlen_ptr(*expected) == (size_t) r);
                 assert_se(strneq(e, *expected++, r));
+
+                assert_se(next);
+                log_debug("path=%s\nnext=%s", path, next);
+                if (!isempty(*expected)) {
+                        assert_se(next < path + strlen(path));
+                        assert_se(next >= path + strlen(*expected));
+                        assert_se(startswith(next - strlen(*expected), *expected));
+                } else if (ret >= 0)
+                        assert_se(next == path);
         }
 }
 
index 7f8330cc24521da7c461461a391bc14ad87dd533..6df2bca7876ea00941350176425933372e075f65 100644 (file)
@@ -6,9 +6,12 @@
 #include "initrd-util.h"
 #include "log.h"
 #include "macro.h"
+#include "nulstr-util.h"
 #include "proc-cmdline.h"
+#include "process-util.h"
 #include "special.h"
 #include "string-util.h"
+#include "strv.h"
 #include "tests.h"
 
 static int obj;
@@ -27,6 +30,7 @@ TEST(proc_cmdline_parse) {
 
 TEST(proc_cmdline_override) {
         _cleanup_free_ char *line = NULL, *value = NULL;
+        _cleanup_strv_free_ char **args = NULL;
 
         assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
         assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=different") == 0);
@@ -35,6 +39,9 @@ TEST(proc_cmdline_override) {
         assert_se(proc_cmdline(&line) >= 0);
         assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
         line = mfree(line);
+        assert_se(proc_cmdline_strv(&args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("foo_bar=quux", "wuff-piep=tuet", "zumm", "some_arg_with_space=foo bar", "and_one_more=zzz aaa")));
+        args = strv_free(args);
 
         /* Test if parsing makes uses of the override */
         assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
@@ -52,6 +59,9 @@ TEST(proc_cmdline_override) {
         assert_se(proc_cmdline(&line) >= 0);
         assert_se(streq(line, "hoge"));
         line = mfree(line);
+        assert_se(proc_cmdline_strv(&args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("hoge")));
+        args = strv_free(args);
 
         assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
         value = mfree(value);
@@ -256,6 +266,48 @@ TEST(proc_cmdline_key_startswith) {
         assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx"));
 }
 
+#define test_proc_cmdline_filter_pid1_args_one(nulstr, expected)        \
+        ({                                                              \
+                _cleanup_strv_free_ char **a = NULL, **b = NULL;        \
+                const char s[] = (nulstr);                              \
+                                                                        \
+                /* This emulates get_process_cmdline_strv(). */         \
+                assert_se(a = strv_parse_nulstr_full(s, ELEMENTSOF(s),  \
+                                                     /* drop_trailing_nuls = */ true)); \
+                assert_se(proc_cmdline_filter_pid1_args(a, &b) >= 0);   \
+                assert_se(strv_equal(b, expected));                     \
+        })
+
+TEST(proc_cmdline_filter_pid1_args) {
+        test_proc_cmdline_filter_pid1_args_one("systemd\0",
+                                               STRV_MAKE_EMPTY);
+
+        test_proc_cmdline_filter_pid1_args_one("systemd\0"
+                                               "hoge\0"
+                                               "-x\0"
+                                               "foo\0"
+                                               "--aaa\0"
+                                               "var\0",
+                                               STRV_MAKE("hoge", "foo", "var"));
+
+        test_proc_cmdline_filter_pid1_args_one("/usr/lib/systemd/systemd\0"
+                                               "--switched-root\0"
+                                               "--system\0"
+                                               "--deserialize\030\0"   /* followed with space */
+                                               "--deserialize=31\0"    /* followed with '=' */
+                                               "--exit-code=42\0"
+                                               "\0\0\0"
+                                               "systemd.log_level=debug\0"
+                                               "--unit\0foo.target\0"
+                                               "  '  quoted '\0"
+                                               "systemd.log_target=console\0"
+                                               "\t\0"
+                                               "  arg   with   space \0"
+                                               "3\0"
+                                               "\0\0\0",
+                                               STRV_MAKE("", "", "", "systemd.log_level=debug", "  '  quoted '", "systemd.log_target=console", "\t", "  arg   with   space ", "3"));
+}
+
 static int intro(void) {
         if (access("/proc/cmdline", R_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
                 return log_tests_skipped("can't read /proc/cmdline");
index 364d5677051248f0f3b7d7cad89f65f1937d2417..e7d8da7c50f6d969f39e3c0430e05b3af1d613dd 100644 (file)
@@ -32,8 +32,10 @@ static const struct option options[] = {
         { "device",             required_argument, NULL, 'd' },
         { "config",             required_argument, NULL, 'f' },
         { "page",               required_argument, NULL, 'p' },
-        { "blacklisted",        no_argument,       NULL, 'b' },
-        { "whitelisted",        no_argument,       NULL, 'g' },
+        { "denylisted",         no_argument,       NULL, 'b' },
+        { "allowlisted",        no_argument,       NULL, 'g' },
+        { "blacklisted",        no_argument,       NULL, 'b' }, /* backward compat */
+        { "whitelisted",        no_argument,       NULL, 'g' }, /* backward compat */
         { "replace-whitespace", no_argument,       NULL, 'u' },
         { "sg-version",         required_argument, NULL, 's' },
         { "verbose",            no_argument,       NULL, 'v' },
@@ -222,8 +224,8 @@ static void help(void) {
                "  -f --config=                     Location of config file\n"
                "  -p --page=0x80|0x83|pre-spc3-83  SCSI page (0x80, 0x83, pre-spc3-83)\n"
                "  -s --sg-version=3|4              Use SGv3 or SGv4\n"
-               "  -b --blacklisted                 Treat device as blacklisted\n"
-               "  -g --whitelisted                 Treat device as whitelisted\n"
+               "  -b --denylisted                  Treat device as denylisted\n"
+               "  -g --allowlisted                 Treat device as allowlisted\n"
                "  -u --replace-whitespace          Replace all whitespace by underscores\n"
                "  -v --verbose                     Verbose logging\n"
                "  -x --export                      Print values as environment keys\n",
index c619506877c8cba092f4478ac26a3f3c1ca5aa05..a271b1786cfa3011d7309e7a3fada09df4f56730 100644 (file)
@@ -161,7 +161,7 @@ static int scsi_dump_sense(struct scsi_id_device *dev_scsi,
          * Figure out and print the sense key, asc and ascq.
          *
          * If you want to suppress these for a particular drive model, add
-         * a black list entry in the scsi_id config file.
+         * a deny list entry in the scsi_id config file.
          *
          * XXX We probably need to: lookup the sense/asc/ascq in a retry
          * table, and if found return 1 (after dumping the sense, asc, and
index 19bbf295c51acded9b10e045d23caf561723b742..e3d2adbafdd614dd2516a326227d55bfae0942f7 100644 (file)
@@ -35,7 +35,7 @@
 #include "user-util.h"
 #include "virt.h"
 
-#define RULES_DIRS (const char* const*) CONF_PATHS_STRV("udev/rules.d")
+#define RULES_DIRS ((const char* const*) CONF_PATHS_STRV("udev/rules.d"))
 
 typedef enum {
         OP_MATCH,        /* == */
@@ -2487,16 +2487,14 @@ static int udev_rule_apply_token_to_event(
                 if (token->op == OP_ASSIGN)
                         device_cleanup_tags(dev);
 
-                if (buf[strspn(buf, ALPHANUMERICAL "-_")] != '\0') {
-                        log_event_error(dev, token, "Invalid tag name '%s', ignoring", buf);
-                        break;
-                }
                 if (token->op == OP_REMOVE)
                         device_remove_tag(dev, buf);
                 else {
                         r = device_add_tag(dev, buf, true);
+                        if (r == -ENOMEM)
+                                return log_oom();
                         if (r < 0)
-                                return log_event_error_errno(dev, token, r, "Failed to add tag '%s': %m", buf);
+                                log_event_warning_errno(dev, token, r, "Failed to add tag '%s', ignoring: %m", buf);
                 }
                 break;
         }
@@ -2541,7 +2539,7 @@ static int udev_rule_apply_token_to_event(
                 break;
         }
         case TK_A_DEVLINK: {
-                char buf[UDEV_PATH_SIZE], *p;
+                char buf[UDEV_PATH_SIZE];
                 bool truncated;
                 size_t count;
 
@@ -2554,17 +2552,18 @@ static int udev_rule_apply_token_to_event(
                 if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL))
                         device_cleanup_devlinks(dev);
 
-                /* allow multiple symlinks separated by spaces */
-                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE, &truncated);
+                (void) udev_event_apply_format(event, token->value, buf, sizeof(buf),
+                                               /* replace_whitespace = */ event->esc != ESCAPE_NONE, &truncated);
                 if (truncated) {
                         log_event_truncated(dev, token, "symbolic link path", token->value, "SYMLINK", /* is_match = */ false);
                         break;
                 }
 
+                /* By default or string_escape=none, allow multiple symlinks separated by spaces. */
                 if (event->esc == ESCAPE_UNSET)
-                        count = udev_replace_chars(buf, "/ ");
+                        count = udev_replace_chars(buf, /* allow = */ "/ ");
                 else if (event->esc == ESCAPE_REPLACE)
-                        count = udev_replace_chars(buf, "/");
+                        count = udev_replace_chars(buf, /* allow = */ "/");
                 else
                         count = 0;
                 if (count > 0)
@@ -2572,32 +2571,36 @@ static int udev_rule_apply_token_to_event(
                                         "Replaced %zu character(s) from result of SYMLINK=\"%s\"",
                                         count, token->value);
 
-                p = skip_leading_chars(buf, NULL);
-                while (!isempty(p)) {
-                        char filename[UDEV_PATH_SIZE], *next;
+                for (const char *p = buf;;) {
+                        _cleanup_free_ char *path = NULL;
 
-                        next = strchr(p, ' ');
-                        if (next) {
-                                *next++ = '\0';
-                                next = skip_leading_chars(next, NULL);
+                        r = extract_first_word(&p, &path, NULL, EXTRACT_RETAIN_ESCAPE);
+                        if (r == -ENOMEM)
+                                return log_oom();
+                        if (r < 0) {
+                                log_warning_errno(r, "Failed to extract first path in SYMLINK=, ignoring: %m");
+                                break;
                         }
-
-                        strscpyl_full(filename, sizeof(filename), &truncated, "/dev/", p, NULL);
-                        if (truncated)
-                                continue;
+                        if (r == 0)
+                                break;
 
                         if (token->op == OP_REMOVE) {
-                                device_remove_devlink(dev, filename);
-                                log_event_debug(dev, token, "Dropped SYMLINK '%s'", p);
+                                r = device_remove_devlink(dev, path);
+                                if (r == -ENOMEM)
+                                        return log_oom();
+                                if (r < 0)
+                                        log_event_warning_errno(dev, token, r, "Failed to remove devlink '%s', ignoring: %m", path);
+                                else if (r > 0)
+                                        log_event_debug(dev, token, "Dropped SYMLINK '%s'", path);
                         } else {
-                                r = device_add_devlink(dev, filename);
+                                r = device_add_devlink(dev, path);
+                                if (r == -ENOMEM)
+                                        return log_oom();
                                 if (r < 0)
-                                        return log_event_error_errno(dev, token, r, "Failed to add devlink '%s': %m", filename);
-
-                                log_event_debug(dev, token, "Added SYMLINK '%s'", p);
+                                        log_event_warning_errno(dev, token, r, "Failed to add devlink '%s', ignoring: %m", path);
+                                else if (r > 0)
+                                        log_event_debug(dev, token, "Added SYMLINK '%s'", path);
                         }
-
-                        p = next;
                 }
                 break;
         }
index 9a4a8adbc321a2dcadbc41f54e0af50e4aa4d69f..b9f18d1552c50a95545d0eff0e9c07bd494e0769 100644 (file)
@@ -75,6 +75,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0)
 
                 switch (c) {
index 30a72f2a429a7b7d064802928d51a5eebd433968..b803f7bb0f5f0e29e020739bf49237174e788313 100644 (file)
@@ -62,6 +62,9 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
         while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0)
                 switch (c) {
 
index eab0c3af1515987c6b1ef053fca7260f52613969..67675b4b7f7ea6237e5e9585e5b52650ef468827 100644 (file)
@@ -1150,6 +1150,10 @@ static int parse_argv(int argc, char *argv[]) {
                 arg_services = l;
         }
 
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
+        optind = 0;
+
         for (;;) {
                 int c;
 
index 2d77bb678fe1d408a35bcc15befc0c1f6ea93d37..d41a4f00f9addab055589d16fb1c53498d4521cf 100755 (executable)
@@ -88,6 +88,8 @@ _image_cleanup() {
     # Clean up certain "problematic" files which may be left over by failing tests
     : >"${initdir:?}/etc/fstab"
     : >"${initdir:?}/etc/crypttab"
+    # Clear previous assignment
+    QEMU_OPTIONS_ARRAY=()
 }
 
 test_run_one() {
@@ -193,15 +195,34 @@ testcase_nvme_basic() {
     local i
     local qemu_opts=()
 
-    for i in {0..27}; do
+    for (( i = 0; i < 5; i++ )); do
         qemu_opts+=(
-            "-device nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
-            "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+            "-device" "nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
+            "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+        )
+    done
+    for (( i = 5; i < 10; i++ )); do
+        qemu_opts+=(
+            "-device" "nvme,drive=nvme$i,serial=    deadbeef  $i   ,num_queues=8"
+            "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+        )
+    done
+    for (( i = 10; i < 15; i++ )); do
+        qemu_opts+=(
+            "-device" "nvme,drive=nvme$i,serial=    dead/beef/$i   ,num_queues=8"
+            "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
+        )
+    done
+    for (( i = 15; i < 20; i++ )); do
+        qemu_opts+=(
+            "-device" "nvme,drive=nvme$i,serial=dead/../../beef/$i,num_queues=8"
+            "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
         )
     done
 
     KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
-    QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
+    QEMU_OPTIONS="${USER_QEMU_OPTIONS}"
+    QEMU_OPTIONS_ARRAY=("${qemu_opts[@]}")
     test_run_one "${1:?}"
 }
 
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 0136140a46697c69bc2206b44c41c5e25a700668..40d6261c6e5bd540e2a7134cc8778f634cb91423 100644 (file)
@@ -6,7 +6,7 @@ ACTION=="remove", GOTO="persistent_storage_tape_end"
 ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
 
 # type 8 devices are "Medium Changers"
-SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --allowlisted -d $devnode", \
   SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}"
 
 # iSCSI devices from the same host have all the same ID_SERIAL,
@@ -22,7 +22,7 @@ SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end"
 KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
 KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
 KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
-KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --allowlisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
 KERNEL=="st*[0-9]",  ENV{ID_SERIAL}=="?*",      SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
 KERNEL=="st*[0-9]",  ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
 KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*",      SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
index 93d562b07042139518d710c176413c776e7ae7e0..b63225875b71433c1050479c359152e8b3bb4abe 100644 (file)
@@ -49,8 +49,8 @@ KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=
 KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
 
 # SCSI devices
-KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
-KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --allowlisted -d $devnode", ENV{ID_BUS}="cciss"
 KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
 KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
 
index de553afb5058a66d6280a1716eb6b7ebf7cba79e..27951cb1053bc7c9265b83c33d989bedf8f4352c 100644 (file)
@@ -36,7 +36,6 @@ EFI_MOUNT="${EFI_MOUNT:-$(bootctl -x 2>/dev/null || echo /boot)}"
 # To force creating a new image from scratch (eg: to encrypt it), also define
 # TEST_FORCE_NEWIMAGE=1 in the test setup script.
 IMAGE_NAME=${IMAGE_NAME:-default}
-STRIP_BINARIES="${STRIP_BINARIES:-yes}"
 TEST_REQUIRE_INSTALL_TESTS="${TEST_REQUIRE_INSTALL_TESTS:-1}"
 TEST_PARALLELIZE="${TEST_PARALLELIZE:-0}"
 TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED="${TEST_SUPPORTING_SERVICES_SHOULD_BE_MASKED:-1}"
@@ -220,6 +219,7 @@ BASICTOOLS=(
     sh
     sleep
     stat
+    stty
     su
     sulogin
     sysctl
@@ -262,7 +262,6 @@ DEBUGTOOLS=(
     route
     sort
     strace
-    stty
     tty
     vi
     /usr/libexec/vi
@@ -298,7 +297,6 @@ IS_BUILT_WITH_ASAN=$(is_built_with_asan "$SYSTEMD_JOURNALD" && echo yes || echo
 IS_BUILT_WITH_COVERAGE=$(is_built_with_coverage && echo yes || echo no)
 
 if get_bool "$IS_BUILT_WITH_ASAN"; then
-    STRIP_BINARIES=no
     SKIP_INITRD="${SKIP_INITRD:-yes}"
     PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
     QEMU_MEM="${QEMU_MEM:-2G}"
@@ -512,6 +510,7 @@ run_qemu() {
         read -ra user_qemu_options <<< "$QEMU_OPTIONS"
         qemu_options+=("${user_qemu_options[@]}")
     fi
+    qemu_options+=(${QEMU_OPTIONS_ARRAY:+"${QEMU_OPTIONS_ARRAY[@]}"})
 
     if [[ -n "${KERNEL_APPEND:=}" ]]; then
         local user_kernel_append
@@ -773,7 +772,6 @@ setup_basic_environment() {
     install_testuser
     has_user_dbus_socket && install_user_dbus
     setup_selinux
-    strip_binaries
     instmods veth
     install_depmod_files
     generate_module_dependencies
@@ -1354,8 +1352,8 @@ create_empty_image() {
     fi
 
     # Partition sizes are in MiBs
-    local root_size=1000
-    local data_size=50
+    local root_size=768
+    local data_size=100
     if ! get_bool "$NO_BUILD"; then
         if meson configure "${BUILD_DIR:?}" | grep 'static-lib\|standalone-binaries' | awk '{ print $2 }' | grep -q 'true'; then
             root_size=$((root_size+=200))
@@ -1366,11 +1364,11 @@ create_empty_image() {
         if get_bool "$IS_BUILT_WITH_COVERAGE"; then
             root_size=$((root_size+=250))
         fi
+        if get_bool "$IS_BUILT_WITH_ASAN"; then
+            root_size=$((root_size * 2))
+        fi
     fi
-    if ! get_bool "$STRIP_BINARIES"; then
-        root_size=$((4 * root_size))
-        data_size=$((2 * data_size))
-    fi
+
     if [ "$IMAGE_NAME" = "repart" ]; then
         root_size=$((root_size+=1000))
     fi
@@ -1735,17 +1733,6 @@ check_result_qemu_unittests() {
     return $ret
 }
 
-strip_binaries() {
-    dinfo "Strip binaries"
-    if ! get_bool "$STRIP_BINARIES"; then
-        dinfo "STRIP_BINARIES == no, keeping binaries unstripped"
-        return 0
-    fi
-    while read -r bin; do
-        strip --strip-unneeded "$bin" |& grep -vi 'file format not recognized' | ddebug || :
-    done < <(find "${initdir:?}" -executable -not -path '*/lib/modules/*.ko' -type f)
-}
-
 create_rc_local() {
     dinfo "Create rc.local"
     mkdir -p "${initdir:?}/etc/rc.d"
index 7d5667f2975eb3e2da2bfd678cb5ca1b83a6e262..e22a3ef628eff155edd790b8db2ad11107078992 100644 (file)
@@ -695,4 +695,4 @@ cat >"$root/etc/os-release2" <<EOF
 ID='the-id2'
 EOF
 
-SYSTEMD_OS_RELEASE="$root/etc/os-release2" check_alias o 'the-id2'
+SYSTEMD_OS_RELEASE="/etc/os-release2" check_alias o 'the-id2'
index a493485fc17b9b38184e1aea50d0f80eaa1edfe3..0164c158b50c836eb07f465c9635b0b0fe29bf6b 100755 (executable)
@@ -212,13 +212,44 @@ EOF
                         {
                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
                                 exp_links       => ["boot_disk1", "boot_diskXY1"],
-                                not_exp_links   => ["boot_diskXX1", "hoge"],
+                                not_exp_links   => ["boot_diskXX1"],
                         }],
                 rules           => <<EOF
 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n"
 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n", SYMLINK+="hoge"
-SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK-="hoge"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
+EOF
+        },
+        {
+                desc            => "SYMLINK tests",
+                devices => [
+                        {
+                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
+                                exp_links       => ["link1", "link2/foo", "link3/aaa/bbb",
+                                                    "abs1", "abs2/foo", "abs3/aaa/bbb",
+                                                    "default___replace_test/foo_aaa",
+                                                    "string_escape___replace/foo_bbb",
+                                                    "env_with_space",
+                                                    "default/replace/mode_foo__hoge",
+                                                    "replace_env_harder_foo__hoge"],
+                                not_exp_links   => ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"],
+                        }],
+                rules           => <<EOF
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed2"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="././removed3"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="/dev//./removed3/./"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="unsafe/../../path"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/nondev/path/will/be/refused"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="link1 .///link2/././/foo//./ .///link3/aaa/bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/dev/abs1 /dev//./abs2///foo/./ ////dev/abs3/aaa/bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="default?;;replace%%test/foo'aaa"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", SYMLINK+="string_escape   replace/foo%%bbb"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="env    with    space", SYMLINK+="%E{.HOGE}"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="default/replace/mode?foo;;hoge", SYMLINK+="%E{.HOGE}"
+SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}"
 EOF
         },
         {
@@ -1784,9 +1815,9 @@ EOF
                                 not_exp_name    => "bad",
                        }],
                 rules           => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="|foo", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="|foo", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
 EOF
         },
         {
@@ -1812,9 +1843,9 @@ EOF
                                 not_exp_name    => "bad",
                         }],
                 rules           => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="foo||bar", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="foo||bar", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
 EOF
         },
         {
@@ -1840,9 +1871,9 @@ EOF
                                 not_exp_name    => "bad",
                         }],
                 rules           => <<EOF
-KERNEL=="sda", TAG=""
-TAGS=="foo|", SYMLINK+="found"
-TAGS=="aaa|bbb", SYMLINK+="bad"
+KERNEL=="sda", ENV{HOGE}=""
+ENV{HOGE}=="foo|", SYMLINK+="found"
+ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
 EOF
         },
         {
@@ -1857,6 +1888,25 @@ EOF
 KERNEL=="sda", TAG="c"
 TAGS=="foo||bar||c", SYMLINK+="found"
 TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
+EOF
+        },
+        {
+                desc            => "TAG refuses invalid string",
+                devices => [
+                        {
+                                devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
+                                exp_links       => ["valid", "found"],
+                                not_exp_links   => ["empty", "invalid_char", "path", "bad", "bad2"],
+                        }],
+                rules           => <<EOF
+KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid"
+TAGS=="", SYMLINK+="empty"
+TAGS=="invalid.char", SYMLINK+="invalid_char"
+TAGS=="path/is/also/invalid", SYMLINK+="path"
+TAGS=="valid", SYMLINK+="valid"
+TAGS=="valid|", SYMLINK+="found"
+TAGS=="aaa|", SYMLINK+="bad"
+TAGS=="aaa|bbb", SYMLINK+="bad2"
 EOF
         },
         {
@@ -2148,7 +2198,7 @@ TAGS=="aaa", SYMLINK+="bad"
                     EOF
         },
         {
-                desc            => "continuations with white only line",
+                desc            => "continuations with space only line",
                 devices => [
                         {
                                 devpath         => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
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..cd18a9f
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Check if we properly differentiate between a full systemd setup and a "light"
+# version of it that's done during daemon-reexec
+#
+# See: https://github.com/systemd/systemd/issues/27106
+if systemd-detect-virt -q --container; then
+    # We initialize /run/systemd/container only during a full setup
+    test -e /run/systemd/container
+    cp -afv /run/systemd/container /tmp/container
+    rm -fv /run/systemd/container
+    systemctl daemon-reexec
+    test ! -e /run/systemd/container
+    cp -afv /tmp/container /run/systemd/container
+else
+    # We bring the loopback netdev up only during a full setup, so it should
+    # not get brought back up during reexec if we disable it beforehand
+    [[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]]
+    ip link set lo down
+    [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+    systemctl daemon-reexec
+    [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+    ip link set lo up
+
+    # We also disable coredumps only during a full setup
+    sysctl -w kernel.core_pattern=dont-overwrite-me
+    systemctl daemon-reexec
+    diff <(echo dont-overwrite-me) <(sysctl --values kernel.core_pattern)
+fi
+
+# Collect failed units & do one daemon-reload to a basic sanity check
+systemctl --state=failed --no-legend --no-pager | tee /failed
+systemctl daemon-reload
+
+# Check that the early setup is actually skipped on reexec.
+# If the early setup is done more than once, then several timestamps,
+# e.g. SecurityStartTimestamp, are re-initialized, and causes an ABRT
+# of systemd-analyze blame. See issue #27187.
+systemd-analyze blame
+
+echo OK >/testok
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 c1cb2bc33ec57bb870815d4dc903373556b7d450..1e4bd4ec42a81f5ce492f8bad32142b101ebd56d 100755 (executable)
@@ -29,7 +29,7 @@ if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
     STATE_DIRECTORY=/var/lib/
 fi
 # Bump the timeout if we're running with plain QEMU
-[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=60 || TIMEOUT=30
+[[ "$(systemd-detect-virt -v)" == "qemu" ]] && TIMEOUT=90 || TIMEOUT=30
 
 systemd-dissect --no-pager /usr/share/minimal_0.raw | grep -q '✓ portable service'
 systemd-dissect --no-pager /usr/share/minimal_1.raw | grep -q '✓ portable service'
index 02a02301912ff971ac86796fc161aa657c9b1982..52634c64b2d46e4571cac0167a235843ea8ba9e3 100755 (executable)
@@ -510,6 +510,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 03d2fcb4ef3774ac15e40f22c52cc00f258dfd90..015b6b69b50265bba8e4ece4dfe5969a349c6635 100755 (executable)
@@ -174,8 +174,54 @@ testcase_megasas2_basic() {
 }
 
 testcase_nvme_basic() {
+    local expected_symlinks=()
+    local i
+
+    for (( i = 0; i < 5; i++ )); do
+        expected_symlinks+=(
+            # both replace mode provides the same devlink
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"
+            # with nsid
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1
+        )
+    done
+    for (( i = 5; i < 10; i++ )); do
+        expected_symlinks+=(
+            # old replace mode
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i"
+            # newer replace mode
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"
+            # with nsid
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1
+        )
+    done
+    for (( i = 10; i < 15; i++ )); do
+        expected_symlinks+=(
+            # old replace mode does not provide devlink, as serial contains "/"
+            # newer replace mode
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"
+            # with nsid
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1
+        )
+    done
+    for (( i = 15; i < 20; i++ )); do
+        expected_symlinks+=(
+            # old replace mode does not provide devlink, as serial contains "/"
+            # newer replace mode
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"
+            # with nsid
+            /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"_1
+        )
+    done
+
+    udevadm settle
+    ls /dev/disk/by-id
+    for i in "${expected_symlinks[@]}"; do
+        udevadm wait --settle --timeout=30 "$i"
+    done
+
     lsblk --noheadings | grep "^nvme"
-    [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 28 ]]
+    [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]]
 }
 
 testcase_nvme_subsystem() {
index f28edf275e5a20986e7647a5d428d2f8b61e5f37..49e6b87a130be709ec2a9e818768fa50a5e9c0f6 100755 (executable)
@@ -11,13 +11,15 @@ export SYSTEMD_LOG_LEVEL=debug
 
 # Sanity checks
 #
-# We can't really test time, blame, critical-chain and plot verbs here, as
+# We can't really test time, critical-chain and plot verbs here, as
 # the testsuite service is a part of the boot transaction, so let's assume
 # they fail
 systemd-analyze || :
 systemd-analyze time || :
-systemd-analyze blame || :
 systemd-analyze critical-chain || :
+# blame
+systemd-analyze blame
+systemd-run --wait --user --pipe -M testuser@.host systemd-analyze blame
 # plot
 systemd-analyze plot >/dev/null || :
 systemd-analyze plot --json=pretty >/dev/null || :
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
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 ddd86d09bb247427c73f8ce3619d7a83be9dcf99..c5487555b6433a75166595e3195cb87aa2603f35 100755 (executable)
@@ -46,7 +46,7 @@ monitor_check_rr() (
 
 # Test for resolvectl, resolvconf
 systemctl unmask systemd-resolved.service
-systemctl start systemd-resolved.service
+systemctl enable --now systemd-resolved.service
 systemctl service-log-level systemd-resolved.service debug
 ip link add hoge type dummy
 ip link add hoge.foo type dummy
index 0072133cd4e1ca4adc274aea26fe9c1dca86a92f..15ea36bf88d7a8e9a3cee002350785649087c106 100755 (executable)
@@ -53,6 +53,7 @@ cat >> "$MYSCRIPT" <<'EOF'
 #!/usr/bin/env bash
 set -eux
 set -o pipefail
+test "$FDSTORE" -eq 7
 N="/tmp/$RANDOM"
 echo $RANDOM > "$N"
 systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N"
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 5999d38d35cc3ae198e5b07958cd2ccff25249bb..5c11eba7c974d3d2cf0657c421d3f2d669244f95 100644 (file)
@@ -26,7 +26,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]