]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #26944 from aafeijoo-suse/systemd-network-generator-initrd-fix
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 26 Apr 2023 12:55:41 +0000 (14:55 +0200)
committerGitHub <noreply@github.com>
Wed, 26 Apr 2023 12:55:41 +0000 (14:55 +0200)
network-generator: do not parse kernel command line more than once

724 files changed:
.github/labeler.yml
.github/workflows/build_test.sh
.github/workflows/codeql.yml
.github/workflows/differential-shellcheck.yml
.github/workflows/labeler.yml
.github/workflows/linter.yml
.github/workflows/make_release.yml [new file with mode: 0644]
.github/workflows/mkosi.yml
.github/workflows/scorecards.yml
.gitignore
README.md
TODO
docs/CODING_STYLE.md
docs/COREDUMP.md [new file with mode: 0644]
docs/CREDENTIALS.md
docs/ENVIRONMENT.md
docs/HACKING.md
docs/MEMORY_PRESSURE.md
docs/PORTABLE_SERVICES.md
docs/TEMPORARY_DIRECTORIES.md
docs/TESTING_WITH_SANITIZERS.md
docs/USER_RECORD.md
hwdb.d/60-evdev.hwdb
hwdb.d/60-keyboard.hwdb
hwdb.d/60-sensor.hwdb
hwdb.d/70-joystick.hwdb
hwdb.d/70-mouse.hwdb
hwdb.d/70-touchpad.hwdb
man/bootctl.xml
man/common-variables.xml
man/coredumpctl.xml
man/custom-entities.ent.in
man/file-hierarchy.xml
man/html.in
man/iocost.conf.xml [new file with mode: 0644]
man/journalctl.xml
man/kernel-command-line.xml
man/kernel-install.xml
man/logcontrol-example.c [new file with mode: 0644]
man/networkctl.xml
man/org.freedesktop.LogControl1.xml
man/org.freedesktop.login1.xml
man/org.freedesktop.systemd1.xml
man/os-release.xml
man/print-unit-path-call-method.c [new file with mode: 0644]
man/print-unit-path.c
man/rules/meson.build
man/sd-bus-errors.xml
man/sd_bus_add_node_enumerator.xml
man/sd_bus_add_object.xml
man/sd_bus_add_object_manager.xml
man/sd_bus_attach_event.xml
man/sd_bus_call_method.xml
man/sd_bus_close.xml
man/sd_bus_default.xml
man/sd_bus_emit_signal.xml
man/sd_bus_enqueue_for_read.xml
man/sd_bus_get_n_queued_read.xml
man/sd_bus_get_name_creds.xml
man/sd_bus_get_name_machine_id.xml
man/sd_bus_is_open.xml
man/sd_bus_list_names.xml
man/sd_bus_message_open_container.xml
man/sd_bus_negotiate_fds.xml
man/sd_bus_query_sender_creds.xml
man/sd_bus_set_close_on_exit.xml
man/sd_bus_set_connected_signal.xml
man/sd_bus_set_description.xml
man/sd_bus_set_exit_on_disconnect.xml
man/sd_bus_set_sender.xml
man/sd_bus_set_server.xml
man/sd_bus_set_watch_bind.xml
man/sd_bus_slot_set_floating.xml
man/sd_event_add_child.xml
man/sd_event_add_defer.xml
man/sd_event_add_inotify.xml
man/sd_event_add_io.xml
man/sd_event_add_memory_pressure.xml
man/sd_event_add_signal.xml
man/sd_event_add_time.xml
man/sd_event_exit.xml
man/sd_event_get_fd.xml
man/sd_event_now.xml
man/sd_event_run.xml
man/sd_event_set_signal_exit.xml
man/sd_event_set_watchdog.xml
man/sd_event_source_get_pending.xml
man/sd_event_source_set_description.xml
man/sd_event_source_set_enabled.xml
man/sd_event_source_set_floating.xml
man/sd_event_source_set_prepare.xml
man/sd_event_source_set_priority.xml
man/sd_event_source_set_ratelimit.xml
man/sd_event_wait.xml
man/sd_journal_get_data.xml
man/sd_notify.xml
man/standard-options.xml
man/system-or-user-ns.xml
man/systemctl.xml
man/systemd-analyze.xml
man/systemd-coredump.xml
man/systemd-creds.xml
man/systemd-cryptenroll.xml
man/systemd-dissect.xml
man/systemd-firstboot.xml
man/systemd-fsck@.service.xml
man/systemd-gpt-auto-generator.xml
man/systemd-journal-remote.service.xml
man/systemd-machine-id-setup.xml
man/systemd-mount.xml
man/systemd-notify.xml
man/systemd-nspawn.xml
man/systemd-poweroff.service.xml
man/systemd-random-seed.service.xml
man/systemd-repart.xml
man/systemd-run.xml
man/systemd-sysext.xml
man/systemd-system-update-generator.xml
man/systemd-sysupdate.xml
man/systemd-sysusers.xml
man/systemd-tmpfiles.xml
man/systemd-userdbd.service.xml
man/systemd-veritysetup-generator.xml
man/systemd.exec.xml
man/systemd.image-policy.xml [new file with mode: 0644]
man/systemd.link.xml
man/systemd.netdev.xml
man/systemd.nspawn.xml
man/systemd.offline-updates.xml
man/systemd.preset.xml
man/systemd.resource-control.xml
man/systemd.service.xml
man/systemd.special.xml
man/systemd.unit.xml
man/systemd.xml
man/tmpfiles.d.xml
man/udevadm.xml
man/ukify.xml
man/veritytab.xml
meson.build
mkosi.build
mkosi.conf.d/10-systemd.conf
mkosi.conf.d/20-arch.conf [moved from mkosi.conf.d/arch/10-arch.conf with 77% similarity]
mkosi.conf.d/20-centos-fedora.conf [moved from mkosi.conf.d/fedora/10-fedora.conf with 72% similarity]
mkosi.conf.d/20-centos.conf [new file with mode: 0644]
mkosi.conf.d/20-debian-ubuntu/mkosi.conf [moved from mkosi.conf.d/debian/10-debian.conf with 86% similarity]
mkosi.conf.d/20-debian-ubuntu/mkosi.extra/usr/lib/systemd/system-preset/99-ignore.preset [new file with mode: 0644]
mkosi.conf.d/20-debian-ubuntu/mkosi.extra/usr/lib/tmpfiles.d/locale.conf [new file with mode: 0644]
mkosi.conf.d/20-debian.conf [new file with mode: 0644]
mkosi.conf.d/20-fedora.conf [new file with mode: 0644]
mkosi.conf.d/20-opensuse.conf [moved from mkosi.conf.d/opensuse/10-opensuse.conf with 91% similarity]
mkosi.conf.d/20-ubuntu.conf [new file with mode: 0644]
mkosi.conf.d/21-centos-8/mkosi.conf [new file with mode: 0644]
mkosi.conf.d/21-centos-8/mkosi.reposdir/powertools.repo [new file with mode: 0644]
mkosi.conf.d/21-centos-9.conf [new file with mode: 0644]
mkosi.conf.d/centos/10-centos.conf [deleted file]
mkosi.conf.d/ubuntu/10-ubuntu.conf [deleted file]
mkosi.extra/root/.gdbinit
mkosi.extra/usr/lib/systemd/system-preset/00-mkosi.preset [new file with mode: 0644]
mkosi.kernel.config
mkosi.postinst
po/gl.po
po/ru.po
rules.d/60-persistent-storage-tape.rules
rules.d/60-persistent-storage.rules.in
rules.d/70-camera.rules
rules.d/90-iocost.rules [new file with mode: 0644]
rules.d/meson.build
shell-completion/bash/busctl
shell-completion/bash/portablectl
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-fdstore.c [new file with mode: 0644]
src/analyze/analyze-fdstore.h [new file with mode: 0644]
src/analyze/analyze-image-policy.c [new file with mode: 0644]
src/analyze/analyze-image-policy.h [new file with mode: 0644]
src/analyze/analyze-plot.c
src/analyze/analyze-security.c
src/analyze/analyze-syscall-filter.c
src/analyze/analyze-time-data.c
src/analyze/analyze-time-data.h
src/analyze/analyze-verify.c
src/analyze/analyze.c
src/analyze/analyze.h
src/analyze/meson.build
src/ask-password/ask-password.c
src/basic/architecture.h
src/basic/chase-symlinks.h [deleted file]
src/basic/chase.c [moved from src/basic/chase-symlinks.c with 57% similarity]
src/basic/chase.h [new file with mode: 0644]
src/basic/compress.c
src/basic/compress.h
src/basic/conf-files.c
src/basic/conf-files.h
src/basic/coverage.h
src/basic/devnum-util.c
src/basic/devnum-util.h
src/basic/env-file.c
src/basic/env-file.h
src/basic/env-util.c
src/basic/env-util.h
src/basic/fd-util.c
src/basic/fd-util.h
src/basic/fileio.c
src/basic/fileio.h
src/basic/fs-util.c
src/basic/fs-util.h
src/basic/getopt-defs.h [new file with mode: 0644]
src/basic/list.h
src/basic/lock-util.c
src/basic/lock-util.h
src/basic/log.c
src/basic/log.h
src/basic/macro.h
src/basic/meson.build
src/basic/missing_fcntl.h
src/basic/mkdir.c
src/basic/mountpoint-util.c
src/basic/nulstr-util.c
src/basic/nulstr-util.h
src/basic/origin-id.h [new file with mode: 0644]
src/basic/os-util.c
src/basic/os-util.h
src/basic/parse-util.c
src/basic/path-util.c
src/basic/path-util.h
src/basic/proc-cmdline.c
src/basic/proc-cmdline.h
src/basic/process-util.c
src/basic/process-util.h
src/basic/ratelimit.c
src/basic/ratelimit.h
src/basic/socket-util.c
src/basic/socket-util.h
src/basic/stat-util.c
src/basic/stat-util.h
src/basic/string-table.h
src/basic/string-util.c
src/basic/string-util.h
src/basic/strv.c
src/basic/strv.h
src/basic/terminal-util.c
src/basic/tmpfile-util.c
src/basic/tmpfile-util.h
src/basic/unit-def.c
src/basic/unit-def.h
src/basic/unit-file.c
src/basic/user-util.c
src/basic/user-util.h
src/basic/virt.c
src/boot/bootctl-install.c
src/boot/bootctl-status.c
src/boot/bootctl-uki.c
src/boot/bootctl-util.c
src/boot/bootctl.c
src/boot/bootctl.h
src/boot/efi/meson.build
src/boot/efi/pe.c
src/boot/efi/splash.c
src/boot/efi/stub.c
src/boot/efi/ticks.c
src/boot/efi/util.h
src/boot/pcrphase.c
src/busctl/busctl.c
src/cgls/cgls.c
src/core/bpf-firewall.c
src/core/bpf-lsm.c
src/core/bpf/meson.build [deleted file]
src/core/dbus-execute.c
src/core/dbus-execute.h
src/core/dbus-manager.c
src/core/dbus-service.c
src/core/dbus-service.h
src/core/dbus-unit.c
src/core/device.c
src/core/dynamic-user.c
src/core/dynamic-user.h
src/core/execute.c
src/core/execute.h
src/core/job.c
src/core/kmod-setup.c
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/main.c
src/core/manager-serialize.c
src/core/manager.c
src/core/manager.h
src/core/meson.build
src/core/mount.c
src/core/mount.h
src/core/namespace.c
src/core/namespace.h
src/core/scope.c
src/core/service.c
src/core/service.h
src/core/socket.c
src/core/socket.h
src/core/swap.c
src/core/swap.h
src/core/timer.c
src/core/unit-serialize.c
src/core/unit.c
src/core/unit.h
src/coredump/coredump.c
src/coredump/coredumpctl.c
src/creds/creds.c
src/cryptenroll/cryptenroll-list.c
src/cryptenroll/cryptenroll-tpm2.c
src/cryptenroll/cryptenroll.c
src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
src/cryptsetup/cryptsetup-tpm2.c
src/cryptsetup/cryptsetup-tpm2.h
src/cryptsetup/cryptsetup.c
src/delta/delta.c
src/dissect/dissect.c
src/firstboot/firstboot.c
src/fsck/fsck.c
src/fstab-generator/fstab-generator.c
src/fundamental/logarithm.h [moved from src/basic/logarithm.h with 76% similarity]
src/fundamental/macro-fundamental.h
src/fuzz/fuzz-compress.c
src/gpt-auto-generator/gpt-auto-generator.c
src/home/homectl.c
src/home/homed-manager.c
src/home/homework-luks.c
src/home/homework-mount.c
src/home/pam_systemd_home.c
src/import/importd.c
src/import/pull-raw.c
src/import/pull-tar.c
src/journal-remote/journal-remote-main.c
src/journal-remote/journal-remote-write.c
src/journal-remote/journal-remote-write.h
src/journal-remote/journal-remote.c
src/journal/cat.c
src/journal/journalctl.c
src/journal/journald-server.c
src/journal/managed-journal-file.c
src/journal/meson.build
src/journal/test-journal-verify.c
src/kernel-install/install.conf
src/kernel-install/kernel-install.in
src/kernel-install/test-kernel-install.sh
src/libsystemd-network/icmp6-util.c
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/sd-dhcp-server.c
src/libsystemd-network/sd-dhcp6-client.c
src/libsystemd/libsystemd.sym
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h
src/libsystemd/sd-bus/bus-control.c
src/libsystemd/sd-bus/bus-convenience.c
src/libsystemd/sd-bus/bus-error.c
src/libsystemd/sd-bus/bus-internal.h
src/libsystemd/sd-bus/bus-objects.c
src/libsystemd/sd-bus/bus-socket.c
src/libsystemd/sd-bus/sd-bus.c
src/libsystemd/sd-bus/test-bus-cleanup.c
src/libsystemd/sd-daemon/sd-daemon.c
src/libsystemd/sd-device/device-enumerator.c
src/libsystemd/sd-device/device-monitor.c
src/libsystemd/sd-device/device-private.c
src/libsystemd/sd-device/device-private.h
src/libsystemd/sd-device/device-util.c
src/libsystemd/sd-device/sd-device.c
src/libsystemd/sd-event/sd-event.c
src/libsystemd/sd-event/test-event.c
src/libsystemd/sd-id128/id128-util.c
src/libsystemd/sd-id128/id128-util.h
src/libsystemd/sd-id128/sd-id128.c
src/libsystemd/sd-journal/journal-file.c
src/libsystemd/sd-journal/journal-file.h
src/libsystemd/sd-journal/journal-internal.h
src/libsystemd/sd-journal/journal-vacuum.c
src/libsystemd/sd-journal/sd-journal.c
src/libsystemd/sd-journal/test-journal-init.c
src/locale/localed-util.c
src/locale/localed-util.h
src/locale/localed.c
src/locale/meson.build
src/locale/xkbcommon-util.c [new file with mode: 0644]
src/locale/xkbcommon-util.h [new file with mode: 0644]
src/login/inhibit.c
src/login/logind-dbus.c
src/login/logind-session-dbus.c
src/login/logind-session.c
src/login/logind-session.h
src/login/logind.h
src/login/org.freedesktop.login1.conf
src/login/pam_systemd.c
src/login/test-session-properties.c
src/machine-id-setup/machine-id-setup-main.c
src/machine/image-dbus.c
src/machine/machinectl.c
src/mount/mount-tool.c
src/network/networkctl.c
src/network/networkd-network-bus.c
src/network/networkd-nexthop.c
src/notify/notify.c
src/nspawn/nspawn-bind-user.c
src/nspawn/nspawn-gperf.gperf
src/nspawn/nspawn-mount.c
src/nspawn/nspawn-network.c
src/nspawn/nspawn-network.h
src/nspawn/nspawn-settings.c
src/nspawn/nspawn-settings.h
src/nspawn/nspawn.c
src/oom/oomd-manager.c
src/oom/oomd-util.c
src/partition/repart.c
src/portable/portable.c
src/portable/portable.h
src/portable/portablectl.c
src/portable/portabled-bus.c
src/portable/portabled-image-bus.c
src/pstore/pstore.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-scope.c
src/resolve/resolved-dns-stream.c
src/resolve/resolved-etc-hosts.c
src/resolve/resolved-link.c
src/resolve/resolved-link.h
src/resolve/resolved-manager.c
src/run/run.c
src/shared/boot-entry.c [new file with mode: 0644]
src/shared/boot-entry.h [new file with mode: 0644]
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/bpf-program.c
src/shared/bpf-program.h
src/shared/btrfs-util.c
src/shared/btrfs-util.h
src/shared/bus-get-properties.c
src/shared/bus-unit-util.c
src/shared/bus-unit-util.h
src/shared/condition.c
src/shared/copy.c
src/shared/copy.h
src/shared/coredump-util.c
src/shared/coredump-util.h
src/shared/cpu-set-util.c
src/shared/creds-util.c
src/shared/device-nodes.c
src/shared/discover-image.c
src/shared/discover-image.h
src/shared/dissect-image.c
src/shared/dissect-image.h
src/shared/dropin.c
src/shared/edit-util.c
src/shared/edit-util.h
src/shared/exec-util.c
src/shared/exec-util.h
src/shared/extension-release.h [deleted file]
src/shared/extension-util.c [moved from src/shared/extension-release.c with 51% similarity]
src/shared/extension-util.h [new file with mode: 0644]
src/shared/fdset.c
src/shared/fdset.h
src/shared/fileio-label.c
src/shared/find-esp.c
src/shared/find-esp.h
src/shared/format-table.c
src/shared/format-table.h
src/shared/image-policy.c [new file with mode: 0644]
src/shared/image-policy.h [new file with mode: 0644]
src/shared/install.c
src/shared/install.h
src/shared/kernel-image.c
src/shared/kernel-image.h
src/shared/logs-show.c
src/shared/loop-util.c
src/shared/loop-util.h
src/shared/lsm-util.c [new file with mode: 0644]
src/shared/lsm-util.h [new file with mode: 0644]
src/shared/machine-id-setup.c
src/shared/main-func.h
src/shared/meson.build
src/shared/mkfs-util.c
src/shared/mount-util.c
src/shared/mount-util.h
src/shared/numa-util.c
src/shared/pam-util.c
src/shared/pam-util.h
src/shared/rm-rf.c
src/shared/seccomp-util.c
src/shared/seccomp-util.h
src/shared/securebits-util.h
src/shared/specifier.c
src/shared/switch-root.c
src/shared/tpm2-util.c
src/shared/tpm2-util.h
src/shared/uid-alloc-range.c
src/shared/user-record-nss.c
src/shared/userdb.c
src/shared/varlink.c
src/shared/varlink.h
src/shared/xml.c
src/shutdown/shutdown.c
src/shutdown/umount.c
src/sleep/sleep.c
src/sulogin-shell/sulogin-shell.c
src/sysext/meson.build
src/sysext/sysext.c
src/system-update-generator/system-update-generator.c
src/systemctl/systemctl-add-dependency.c
src/systemctl/systemctl-clean-or-freeze.c
src/systemctl/systemctl-edit.c
src/systemctl/systemctl-enable.c
src/systemctl/systemctl-is-enabled.c
src/systemctl/systemctl-list-unit-files.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/systemctl/systemctl-switch-root.c
src/systemctl/systemctl-util.c
src/systemctl/systemctl.c
src/systemctl/systemctl.h
src/systemd/sd-bus-protocol.h
src/systemd/sd-daemon.h
src/sysupdate/sysupdate-resource.c
src/sysupdate/sysupdate-transfer.c
src/sysupdate/sysupdate.c
src/sysusers/sysusers.c
src/test/meson.build
src/test/test-acl-util.c
src/test/test-async.c
src/test/test-bitmap.c
src/test/test-calendarspec.c
src/test/test-chase-manual.c [moved from src/test/test-chase-symlinks.c with 96% similarity]
src/test/test-chase.c [new file with mode: 0644]
src/test/test-compare-operator.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-core-unit.c [new file with mode: 0644]
src/test/test-coredump-util.c
src/test/test-env-file.c
src/test/test-env-util.c
src/test/test-execute.c
src/test/test-fd-util.c
src/test/test-fdset.c
src/test/test-fileio.c
src/test/test-format-table.c
src/test/test-fs-util.c
src/test/test-hashmap-plain.c
src/test/test-hostname-setup.c
src/test/test-id128.c
src/test/test-image-policy.c [new file with mode: 0644]
src/test/test-install-root.c
src/test/test-install.c
src/test/test-load-fragment.c
src/test/test-lock-util.c [new file with mode: 0644]
src/test/test-log.c
src/test/test-logarithm.c
src/test/test-loop-block.c
src/test/test-mountpoint-util.c
src/test/test-namespace.c
src/test/test-ns.c
src/test/test-nss-hosts.c
src/test/test-nulstr-util.c
src/test/test-os-util.c
src/test/test-path-util.c
src/test/test-proc-cmdline.c
src/test/test-process-util.c
src/test/test-rm-rf.c
src/test/test-secure-bits.c [new file with mode: 0644]
src/test/test-set.c
src/test/test-sizeof.c
src/test/test-specifier.c
src/test/test-stat-util.c
src/test/test-string-util.c
src/test/test-strv.c
src/test/test-terminal-util.c
src/test/test-time-util.c
src/test/test-tpm2.c
src/test/test-varlink.c
src/timesync/timesyncd-manager.c
src/tmpfiles/offline-passwd.c
src/tmpfiles/tmpfiles.c
src/udev/ata_id/ata_id.c
src/udev/cdrom_id/cdrom_id.c
src/udev/dmi_memory_id/dmi_memory_id.c
src/udev/fido_id/fido_id.c
src/udev/fuzz-udev-rules.c
src/udev/iocost/iocost.c [new file with mode: 0644]
src/udev/iocost/iocost.conf [new file with mode: 0644]
src/udev/meson.build
src/udev/mtd_probe/mtd_probe.c
src/udev/scsi_id/scsi_id.c
src/udev/scsi_id/scsi_serial.c
src/udev/udev-builtin-net_id.c
src/udev/udev-ctrl.c
src/udev/udev-rules.c
src/udev/udev-rules.h
src/udev/udevadm-lock.c
src/udev/udevadm-verify.c
src/udev/udevadm-wait.c
src/udev/udevadm.c
src/udev/udevd.c
src/udev/v4l_id/v4l_id.c
src/ukify/ukify.py
src/user-sessions/user-sessions.c
src/userdb/userdbctl.c
src/vconsole/vconsole-setup.c
src/veritysetup/veritysetup.c
src/volatile-root/volatile-root.c
src/xdg-autostart-generator/xdg-autostart-service.c
test/README.testsuite
test/TEST-02-UNITTESTS/test.sh
test/TEST-64-UDEV-STORAGE/test.sh
test/TEST-78-SIGQUEUE/test.sh
test/TEST-79-MEMPRESS/test.sh
test/TEST-80-NOTIFYACCESS/Makefile [new symlink]
test/TEST-80-NOTIFYACCESS/test.sh [new file with mode: 0755]
test/TEST-81-GENERATORS/Makefile [new symlink]
test/TEST-81-GENERATORS/test.sh [new file with mode: 0755]
test/auxv/.gitattributes [new file with mode: 0644]
test/auxv/bash.riscv64 [new file with mode: 0644]
test/auxv/cat.s390x [new file with mode: 0644]
test/auxv/dbus-broker-launch.aarch64 [new file with mode: 0644]
test/auxv/dbus-broker-launch.amd64 [new file with mode: 0644]
test/auxv/polkitd.aarch64 [new file with mode: 0644]
test/auxv/resolved.arm32 [new file with mode: 0644]
test/auxv/sleep.i686 [new file with mode: 0644]
test/auxv/sleep32.i686 [new file with mode: 0644]
test/auxv/sleep64.amd64 [new file with mode: 0644]
test/auxv/sudo.aarch64 [new file with mode: 0644]
test/auxv/sudo.amd64 [new file with mode: 0644]
test/fuzz/fuzz-udev-rules/60-persistent-storage-tape.rules
test/fuzz/fuzz-udev-rules/60-persistent-storage.rules
test/meson.build
test/mkosi.build.networkd-test [deleted file]
test/mkosi.default.networkd-test [deleted file]
test/mkosi.nspawn.networkd-test [deleted file]
test/run-unit-tests.py
test/test-bootctl-json.sh
test/test-functions
test/test-network/systemd-networkd-tests.py
test/test-systemctl-enable.sh
test/test-sysusers.sh.in
test/testsuite-80.units/fdstore-nopin.service [new file with mode: 0644]
test/testsuite-80.units/fdstore-pin.service [new file with mode: 0644]
test/testsuite-80.units/fdstore-pin.sh [new file with mode: 0755]
test/testsuite-80.units/fdstore-pin.target [new file with mode: 0644]
test/testsuite-80.units/notify.service [new file with mode: 0644]
test/testsuite-80.units/test.sh [new file with mode: 0755]
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-02.sh
test/units/testsuite-03.sh
test/units/testsuite-04.sh
test/units/testsuite-16.sh
test/units/testsuite-17.06.sh
test/units/testsuite-17.07.sh
test/units/testsuite-17.08.sh
test/units/testsuite-17.09.sh
test/units/testsuite-17.11.sh
test/units/testsuite-18.sh
test/units/testsuite-19.sh
test/units/testsuite-20.sh
test/units/testsuite-21.sh
test/units/testsuite-22.03.sh
test/units/testsuite-22.08.sh
test/units/testsuite-23.sh
test/units/testsuite-25.sh
test/units/testsuite-26.sh
test/units/testsuite-29.sh
test/units/testsuite-33.sh
test/units/testsuite-34.sh
test/units/testsuite-35.sh
test/units/testsuite-39.sh
test/units/testsuite-41.sh
test/units/testsuite-42.sh
test/units/testsuite-43.sh
test/units/testsuite-44.sh
test/units/testsuite-45.sh
test/units/testsuite-46.service
test/units/testsuite-46.sh
test/units/testsuite-50.sh
test/units/testsuite-54.sh
test/units/testsuite-56.sh
test/units/testsuite-57-retry-fail.service [new file with mode: 0644]
test/units/testsuite-57-retry-upheld.service [new file with mode: 0644]
test/units/testsuite-57-retry-uphold.service [new file with mode: 0644]
test/units/testsuite-57-short-lived.sh
test/units/testsuite-57.sh
test/units/testsuite-58.sh
test/units/testsuite-59.sh
test/units/testsuite-60.sh
test/units/testsuite-64.sh
test/units/testsuite-65.sh
test/units/testsuite-70.sh
test/units/testsuite-73.sh
test/units/testsuite-74.coredump.sh [new file with mode: 0755]
test/units/testsuite-74.firstboot.sh
test/units/testsuite-74.machinectl.sh
test/units/testsuite-74.modules-load.sh [new file with mode: 0755]
test/units/testsuite-74.mount.sh
test/units/testsuite-75.sh
test/units/testsuite-80.service [new file with mode: 0644]
test/units/testsuite-80.sh [new file with mode: 0755]
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.getty-generator.sh [new file with mode: 0755]
test/units/testsuite-81.run-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]
test/units/testsuite-81.system-update-generator.sh [new file with mode: 0755]
tools/dump-auxv.py [new file with mode: 0644]
units/meson.build
units/system-update-cleanup.service
units/systemd-confext.service [new file with mode: 0644]
units/systemd-coredump.socket
units/systemd-sysext.service

index e9b88575719222e9fad2d7aaf1ab563cf818ceb5..6adaeb2447fcdca2b79070463010bc94a2ebd883 100644 (file)
@@ -35,6 +35,7 @@ hwdb:
   - hwdb.d/**/*
 journal:
   - src/journal/*
+  - src/libsystemd/sd-journal/*
 journal-remote:
   - src/journal-remote/*
 meson:
index 3ec229bd9b7bed551fc3fc63f66df4d4420b9b1b..40614f9084e3dc4182140e04cf6d39c2d115772c 100755 (executable)
@@ -11,7 +11,7 @@ ARGS=(
     "--optimization=0"
     "--optimization=s"
     "--optimization=3 -Db_lto=true -Ddns-over-tls=false"
-    "--optimization=3 -Db_lto=false"
+    "--optimization=3 -Db_lto=false -Dtpm2=false -Dlibfido2=false -Dp11kit=false"
     "--optimization=3 -Ddns-over-tls=openssl"
     "--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true"
     "-Db_ndebug=true"
index 19ec1219da359a119ad8122cde05a2bdb1d72e23..d5faedf7155ea4ace76cb4ac93a5707e2f7198eb 100644 (file)
@@ -45,7 +45,7 @@ jobs:
       uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5
+      uses: github/codeql-action/init@04df1262e6247151b5ac09cd2c303ac36ad3f62b
       with:
         languages: ${{ matrix.language }}
         config-file: ./.github/codeql-config.yml
@@ -53,7 +53,7 @@ jobs:
     - run: sudo -E .github/workflows/unit_tests.sh SETUP
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5
+      uses: github/codeql-action/autobuild@04df1262e6247151b5ac09cd2c303ac36ad3f62b
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5
+      uses: github/codeql-action/analyze@04df1262e6247151b5ac09cd2c303ac36ad3f62b
index 4d2e63edad0ece941278872be4078e4c239513e3..5405140adc5ac43863e68c4aeab845cde83a9cfd 100644 (file)
@@ -3,6 +3,9 @@
 
 name: Differential ShellCheck
 on:
+  push:
+    branches:
+      - main
   pull_request:
     branches:
       - main
@@ -17,7 +20,6 @@ jobs:
 
     permissions:
       security-events: write
-      pull-requests: write
 
     steps:
       - name: Repository checkout
@@ -26,6 +28,6 @@ jobs:
           fetch-depth: 0
 
       - name: Differential ShellCheck
-        uses: redhat-plumbers-in-action/differential-shellcheck@f3cd08fcf12680861615270b29494d2b87c3e1cc
+        uses: redhat-plumbers-in-action/differential-shellcheck@d24099b9f39ddee81dea31eb0e135e0a623cb2b8
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
index 6085150699f19605e7f58535c37c84dca8ee23b9..389f896f2383c3a2393e74f332eb8ab3e09a9407 100644 (file)
@@ -22,7 +22,7 @@ jobs:
 
     steps:
     - name: Label PR based on policy in labeler.yml
-      uses: actions/labeler@5c7539237e04b714afd8ad9b4aed733815b9fab4
+      uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b
       if: github.event_name == 'pull_request_target' && github.event.action != 'closed'
       with:
         repo-token: "${{ secrets.GITHUB_TOKEN }}"
index 83e0872d567d74719a811c67ed026aef50c4f8e9..8b4259ade632ccec8dd116e88b8094284e0f35b8 100644 (file)
@@ -29,7 +29,7 @@ jobs:
           fetch-depth: 0
 
       - name: Lint Code Base
-        uses: github/super-linter/slim@bb2d833b08b6c288608686672b93a8a4589cdc49
+        uses: github/super-linter/slim@454ba4482ce2cd0c505bc592e83c06e1e37ade61
         env:
           DEFAULT_BRANCH: main
           MULTI_STATUS: false
diff --git a/.github/workflows/make_release.yml b/.github/workflows/make_release.yml
new file mode 100644 (file)
index 0000000..9902a6c
--- /dev/null
@@ -0,0 +1,24 @@
+name: Make a Github release
+
+on:
+  push:
+    tags:
+      - "v*"
+
+permissions:
+  contents: read
+
+jobs:
+  release:
+    if: github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable'
+    runs-on: ubuntu-latest
+
+    permissions:
+      contents: write
+
+    steps:
+      - name: Release
+        uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
+        with:
+          prerelease: ${{ contains(github.ref_name, '-rc') }}
+          draft: ${{ github.repository == 'systemd/systemd' }}
index a4a315bc236b651c2b444476d2176595f3ea5556..c38a6e75b0a1ff1c68763e585d2ae95adae9b697 100644 (file)
@@ -1,7 +1,7 @@
 ---
 # vi: ts=2 sw=2 et:
 # SPDX-License-Identifier: LGPL-2.1-or-later
-# Simple boot tests that build and boot the mkosi images generated by the mkosi config files in mkosi.default.d/.
+# Simple boot tests that build and boot the mkosi images generated by the mkosi config files in mkosi.conf.d/.
 name: mkosi
 
 on:
@@ -61,7 +61,7 @@ jobs:
           - distro: ubuntu
             release: jammy
           - distro: fedora
-            release: "37"
+            release: "38"
           - distro: fedora
             release: rawhide
           - distro: opensuse
@@ -73,11 +73,11 @@ jobs:
 
     steps:
     - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
-    - uses: systemd/mkosi@c1f1e9ab2fe89f21ebdb4984b676f9a489081a64
+    - uses: systemd/mkosi@54f80aa8f687ed4d1759f0a7329c64c3f0ff70ad
 
     - name: Configure
       run: |
-        tee mkosi.default <<- EOF
+        tee mkosi.conf <<- EOF
         [Distribution]
         Distribution=${{ matrix.distro }}
         Release=${{ matrix.release }}
@@ -92,17 +92,20 @@ jobs:
                                systemd.journald.max_level_console=debug
                                # udev's debug log output is very verbose, so up it to info in CI.
                                udev.log_level=info
+
+        [Host]
+        ExtraSearchPaths=!*
         EOF
 
     - name: Generate secure boot key
       run: mkosi genkey
 
-    - name: Build ${{ matrix.distro }}
-      run: mkosi
-
     - name: Show ${{ matrix.distro }} image summary
       run: mkosi summary
 
+    - name: Build ${{ matrix.distro }}
+      run: mkosi
+
     - name: Boot ${{ matrix.distro }} systemd-nspawn
       run: sudo mkosi boot
 
index 26009939db6a75263d5ab3ef0cb463b7311fee21..397460a16e3244a74f9c861178e3343ccf6ec107 100644 (file)
@@ -37,7 +37,7 @@ jobs:
           persist-credentials: false
 
       - name: Run analysis
-        uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # tag=v2.1.2
+        uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # tag=v2.1.3
         with:
           results_file: results.sarif
           results_format: sarif
@@ -65,6 +65,6 @@ jobs:
       # Upload the results to GitHub's code scanning dashboard.
       - name: Upload to code-scanning
         if: github.event_name != 'pull_request'
-        uses: github/codeql-action/upload-sarif@32dc499307d133bb5085bae78498c0ac2cf762d5 # tag=v1.0.26
+        uses: github/codeql-action/upload-sarif@04df1262e6247151b5ac09cd2c303ac36ad3f62b # tag=v1.0.26
         with:
           sarif_file: results.sarif
index 844d67f0a1091ba81ed01161c074e49498d19cf9..1ad0675c4e0d0c1cad621135d8df14dc5bb0253e 100644 (file)
@@ -31,11 +31,10 @@ __pycache__/
 /.mkosi-*
 /mkosi.builddir/
 /mkosi.output/
-/mkosi.default
 /mkosi.installdir/
 /mkosi.secure-boot.*
 # Ignore any mkosi config files with "local" in the name
-/mkosi.default.d/**/*local*.conf
+/mkosi.conf.d/**/*local*.conf
 /tags
 .dir-locals-2.el
 .vscode/
index 8f96590f62716314493379fc1bc10e6457ef47da..841e483fe0b5c0099b204b832c165e4b675036c9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ System and Service Manager
 [![CentOS CI - Arch (sanitizers)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/buildStatus/icon?subject=CentOS%20CI%20-%20Arch%20(sanitizers)&job=upstream-vagrant-archlinux-sanitizers)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/job/upstream-vagrant-archlinux-sanitizers/)<br/>
 [![CentOS CI - Rawhide (SELinux)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/buildStatus/icon?subject=CentOS%20CI%20-%20Rawhide%20(SELinux)&job=upstream-vagrant-rawhide-selinux)](https://jenkins-systemd.apps.ocp.cloud.ci.centos.org/view/Upstream/job/upstream-vagrant-rawhide-selinux/)<br/>
 [![Fossies codespell report](https://fossies.org/linux/test/systemd-main.tar.gz/codespell.svg)](https://fossies.org/linux/test/systemd-main.tar.gz/codespell.html)</br>
+[![Weblate](https://translate.fedoraproject.org/widgets/systemd/-/master/svg-badge.svg)](https://translate.fedoraproject.org/engage/systemd/)</br>
 [![Coverage Status](https://coveralls.io/repos/github/systemd/systemd/badge.svg?branch=main)](https://coveralls.io/github/systemd/systemd?branch=main)</br>
 [![Packaging status](https://repology.org/badge/tiny-repos/systemd.svg)](https://repology.org/project/systemd/versions)</br>
 [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/systemd/systemd/badge)](https://api.securityscorecards.dev/projects/github.com/systemd/systemd)
diff --git a/TODO b/TODO
index fd7c348f9a1b35f3ac167696a3f0717a9b9fcd5d..ea35f3b7dba9aead4720cba4b48ee47d5b9fa687 100644 (file)
--- a/TODO
+++ b/TODO
@@ -75,7 +75,7 @@ Janitorial Clean-ups:
 * rework mount.c and swap.c to follow proper state enumeration/deserialization
   semantics, like we do for device.c now
 
-* get rid of prefix_roota() and similar, only use chase_symlinks() and related
+* get rid of prefix_roota() and similar, only use chase() and related
   calls instead.
 
 * get rid of basename() and replace by path_extract_filename()
@@ -129,14 +129,22 @@ Deprecations and removals:
 
 Features:
 
+* rework journalctl -M to be based on a machined method that generates a mount
+  fd of the relevant journal dirs in the container with uidmapping applied to
+  allow the host to read it, while making everything read-only.
+
+* fix our various hwdb lookup keys to end with ":" again. The original idea was
+  that hwdb patterns can match arbitrary fields with expressions like
+  "*:foobar:*", to wildcard match both the start and the end of the string.
+  This only works safely for later extensions of the string if the strings
+  always end in a colon. This requires updating our udev rules, as well as
+  checking if the various hwdb files are fine with that.
+
 * mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user
   among other things such as dynamic uid ranges for containers and so on. That
-  way noone can create files there with these uids and we enforce they are only
+  way no one can create files there with these uids and we enforce they are only
   used transiently, never persistently.
 
-* set MS_NOSYMFOLLOW for ESP and XBOOTLDR mounts both in gpt-generator and in
-  dissect.c
-
 * rework loopback support in fstab: when "loop" option is used, then
   instantiate a new systemd-loop@.service for the source path, set the
   lo_file_name field for it to something recognizable derived from the fstab
@@ -180,7 +188,7 @@ Features:
 
 * journald: also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive
   "corrected" CLOCK_REALTIME information on display from that and the timestamp
-  info of the newest entry of the specificy boot (as identified by the boot
+  info of the newest entry of the specific boot (as identified by the boot
   ID). This way, if a system comes up without a valid clock but acquires a
   better clock later, we can "fix" older entry timestamps on display, by
   calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does
@@ -285,9 +293,6 @@ Features:
   userspace to allow ordering boots (for example in journalctl). The counter
   would be monotonically increased on every boot.
 
-* systemd-sysext: for sysext DDIs picked up via EFI stub, set much stricter
-  image policy by default
-
 * pam_systemd_home: add module parameter to control whether to only accept
   only password or only pcks11/fido2 auth, and then use this to hook nicely
   into two of the three PAM stacks gdm provides.
@@ -513,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
@@ -530,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.
 
@@ -589,13 +594,10 @@ Features:
   keyring, so that the kernel does this validation for us for verity and kernel
   modules
 
-* for systemd-syscfg: add a tool that can generate suitable DDIs with verity +
+* for systemd-confext: add a tool that can generate suitable DDIs with verity +
   sig using squashfs-tools-ng's library. Maybe just systemd-repart called under
   a new name with a built-in config?
 
-* gpt-auto: generate mount units that reference partitions via
-  /dev/disk/by-diskseq/… so that they can't be swapped out behind our back.
-
 * lock down acceptable encrypted credentials at boot, via simple allowlist,
   maybe on kernel command line:
   systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked
@@ -604,15 +606,12 @@ Features:
 * Add support for extra verity configuration options to systemd-repart (FEC,
   hash type, etc)
 
-* chase_symlinks(): take inspiration from path_extract_filename() and return
+* chase(): take inspiration from path_extract_filename() and return
   O_DIRECTORY if input path contains trailing slash.
 
-* chase_symlinks(): refuse resolution if trailing slash is specified on input,
+* chase(): refuse resolution if trailing slash is specified on input,
   but final node is not a directory
 
-* chase_symlinks(): add new flag that simply refuses all symlink use in a path,
-  then use that for accessing XBOOTLDR/ESP
-
 * document in boot loader spec that symlinks in XBOOTLDR/ESP are not OK even if
   non-VFAT fs is used.
 
@@ -623,6 +622,8 @@ Features:
 
 * pick up creds from EFI vars
 
+* Add and pickup tpm2 metadata for creds structure.
+
 * sd-boot: we probably should include all BootXY EFI variable defined boot
   entries in our menu, and then suppress ourselves. Benefit: instant
   compatibility with all other OSes which register things there, in particular
@@ -742,9 +743,6 @@ Features:
 
 * implement varlink introspection
 
-* we should probably drop all use of prefix_roota() and friends, and use
-  chase_symlinks() instead
-
 * make persistent restarts easier by adding a new setting OpenPersistentFile=
   or so, which allows opening one or more files that is "persistent" across
   service restarts, hot reboot, cold reboots (depending on configuration): the
@@ -785,7 +783,7 @@ Features:
   not unprivileged code.
 
 * given that /etc/ssh/ssh_config.d/ is a thing now, ship a drop-in for that
-  that hooks up userbdctl ssh-key stuff.
+  that hooks up userdbctl ssh-key stuff.
 
 * maybe add support for binding and connecting AF_UNIX sockets in the file
   system outside of the 108ch limit. When connecting, open O_PATH fd to socket
@@ -835,9 +833,6 @@ Features:
   virtio-fs.
 
 * for vendor-built signed initrds:
-  - make sysext run in the initrd
-  - sysext should pick up sysext images from /.extra/ in the initrd, and insist
-    on verification if in secureboot mode
   - kernel-install should be able to install pre-built unified kernel images in
     type #2 drop-in dir in the ESP.
   - kernel-install should be able install encrypted creds automatically for
@@ -857,10 +852,6 @@ Features:
   signal for setting service log level, that carries the level via the
   sigqueue() data parameter. Enable this via unit file setting.
 
-* firstboot: maybe just default to C.UTF-8 locale if nothing is set, so that we
-  don't query this unnecessarily in entirely uninitialized
-  containers. (i.e. containers with empty /etc).
-
 * sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services,
   then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically
   fixed to "2", i.e. the official host cid) and the expected guest cid, for the
@@ -869,8 +860,8 @@ Features:
   directly to host service manager.
 
 * maybe write a tool that binds an AF_VFSOCK socket, then invokes qemu,
-  extending the command line to enable vsock on the VM, and using fw_cfg to
-  configure socket address.
+  extending the command line to enable vsock on the VM, and using SMBIOS
+  credentials to configure socket address.
 
 * sd-boot: add menu item for shutdown? or hotkey?
 
@@ -901,7 +892,7 @@ Features:
 
 * sd-boot: maybe add support for embedding the various auxiliary resources we
   look for right in the sd-boot binary. i.e. take inspiration from sd-stub
-  logic: allow combining sd-boot via objcopy with kernels to enumerate, .conf
+  logic: allow combining sd-boot via ukify with kernels to enumerate, .conf
   files, drivers, keys to enroll and so on. Then, add whatever we find that way
   to the menu. Usecase: allow building a single PE image you can boot into via
   UEFI HTTP boot.
@@ -917,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).
@@ -965,8 +950,6 @@ Features:
 
 * kernel-install:
   - add --all switch for rerunning kernel-install for all installed kernels
-  - maybe add env var that shortcuts kernel-install for installers that want to
-    call it at the end only
 
 * doc: prep a document explaining resolved's internal objects, i.e. Query
   vs. Question vs. Transaction vs. Stream and so on.
@@ -1051,9 +1034,6 @@ Features:
   CapabilityQuintet we already have. (This likely allows us to drop libcap
   dep in the base OS image)
 
-* sysext: automatically activate sysext images dropped in via new sd-stub
-  sysext pickup logic. (must insist on verity + signature on those though)
-
 * add concept for "exitrd" as inverse of "initrd", that we can transition to at
   shutdown, and has similar security semantics. This should then take the place
   of dracut's shutdown logic. Should probably support sysexts too. Care needs
@@ -1083,22 +1063,6 @@ Features:
   keys of /etc/crypttab. That way people can store/provide the roothash
   externally and provide to us on demand only.
 
-* add high-level lockdown level for GPT dissection logic: e.g. an enum that can
-  be ANY (to mount anything), TRUSTED (to require that /usr is on signed
-  verity, but rest doesn't matter), LOCKEDDOWN (to require that everything is
-  on signed verity, except for ESP), SUPERLOCKDOWN (like LOCKEDDOWN but ESP not
-  allowed). And then maybe some flavours of that that declare what is expected
-  from home/srv/var… Then, add a new cmdline flag to all tools that parse such
-  images, to configure this. Also, add a kernel cmdline option for this, to be
-  honoured by the gpt auto generator.
-
-  Alternative idea: add "systemd.gpt_auto_policy=rhvs" to allow gpt-auto to
-  only mount root dir, /home/ dir, /var/ and /srv/, but nothing else. And then
-  minor extension to this, insisting on encryption, for example
-  "systemd.gpt_auto_policy=r+v+h" to require encryption for root and var but not
-  for /home/, and similar. Similar add --image-dissect-policy= to tools that
-  take --image= that take the same short string.
-
 * we probably should extend the root verity hash of the root fs into some PCR
   on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
   it into PCR 12); Similar: we probably should extend the LUKS volume key of
@@ -1111,10 +1075,6 @@ Features:
   (i.e. sysext, root verity) from those inherently local (i.e. encryption key),
   which is useful if they shall be signed separately.
 
-* add a "policy" to the dissection logic. i.e. a bit mask what is OK to mount,
-  what must be read-only, what requires encryption, and what requires
-  authentication.
-
 * in uefi stub: query firmware regarding which PCR banks are being used, store
   that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify
   that the selected PCRs actually are used by firmware.
@@ -1217,7 +1177,7 @@ Features:
 
 * introduce a new group to own TPM devices
 
-* cyptsetup: add option for automatically removing empty password slot on boot
+* cryptsetup: add option for automatically removing empty password slot on boot
 
 * cryptsetup: optionally, when run during boot-up and password is never
   entered, and we are on battery power (or so), power off machine again
@@ -1261,10 +1221,10 @@ Features:
   documented in the pivot_root(2) man page, so that we can drop the /oldroot
   temporary dir.
 
-* special case some calls of chase_symlinks() to use openat2() internally, so
+* special case some calls of chase() to use openat2() internally, so
   that the kernel does what we otherwise do.
 
-* add a new flag to chase_symlinks() that stops chasing once the first missing
+* add a new flag to chase() that stops chasing once the first missing
   component is found and then allows the caller to create the rest.
 
 * make use of new glibc 2.32 APIs sigabbrev_np() and strerrorname_np().
@@ -1303,10 +1263,6 @@ Features:
 * busctl: maybe expose a verb "ping" for pinging a dbus service to see if it
   exists and responds.
 
-* Maybe add a separate GPT partition type to the discoverable partition spec
-  for "hibernate" partitions, that are exactly like swap partitions but only
-  activated right before hibernation and thus never used for regular swapping.
-
 * socket units: allow creating a udev monitor socket with ListenDevices= or so,
   with matches, then activate app through that passing socket over
 
@@ -1356,9 +1312,6 @@ Features:
 
 * teach parse_timestamp() timezones like the calendar spec already knows it
 
-* beef up hibernation to optionally do swapon/swapoff immediately before/after
-  the hibernation
-
 * beef up s2h to implement a battery watch loop: instead of entering
   hibernation unconditionally after coming back from resume make a decision
   based on the battery load level: if battery level is above a specific
@@ -1526,8 +1479,6 @@ Features:
 * maybe rework get_user_creds() to query the user database if $SHELL is used
   for root, but only then.
 
-* be stricter with fds we receive for the fdstore: close them asynchronously
-
 * calenderspec: add support for week numbers and day numbers within a
   year. This would allow us to define "bi-weekly" triggers safely.
 
@@ -1548,10 +1499,6 @@ Features:
 
 * deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char
 
-* add a new RuntimeDirectoryPreserve= mode that defines a similar lifecycle for
-  the runtime dir as we maintain for the fdstore: i.e. keep it around as long
-  as the unit is running or has a job queued.
-
 * support projid-based quota in machinectl for containers
 
 * add a way to lock down cgroup migration: a boolean, which when set for a unit
@@ -1951,11 +1898,6 @@ Features:
 
 * mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units.
 
-* systemd-firstboot: make sure to always use chase_symlinks() before
-  reading/writing files
-
-* firstboot: make it useful to be run immediately after yum --installroot to set up a machine. (most specifically, make --copy-root-password work even if /etc/passwd already exists
-
 * EFI:
   - honor language efi variables for default language selection (if there are any?)
   - honor timezone efi variables for default timezone selection (if there are any?)
@@ -2025,7 +1967,7 @@ Features:
   - check if we can make journalctl by default use --follow mode inside of less if called without args?
   - maybe add API to send pairs of iovecs via sd_journal_send
   - journal: add a setgid "systemd-journal" utility to invoke from libsystemd-journal, which passes fds via STDOUT and does PK access
-  - journactl: support negative filtering, i.e. FOOBAR!="waldo",
+  - journalctl: support negative filtering, i.e. FOOBAR!="waldo",
     and !FOOBAR for events without FOOBAR.
   - journal: store timestamp of journal_file_set_offline() in the header,
     so it is possible to display when the file was last synced.
@@ -2254,11 +2196,6 @@ Features:
   properties as JSON, similar to busctl's new JSON output. In contrast to that
   it should skip the variant type string though.
 
-* add an explicit "vertical" mode to format-table, so that "systemctl
-  status"-like outputs (i.e. with a series of field names left and values
-  right) become genuine first class citizens, and we gain automatic, sane JSON
-  output for them.
-
 * Add a "systemctl list-units --by-slice" mode or so, which rearranges the
   output of "systemctl list-units" slightly by showing the tree structure of
   the slices, and the units attached to them.
index 1a044c023ac7973b5c8bde88bb24f6e9533bcbfa..f76525205fdd261450a539d5c0e2d412e132e38f 100644 (file)
@@ -613,6 +613,15 @@ SPDX-License-Identifier: LGPL-2.1-or-later
   effect on the regular file. If in doubt consider turning off `O_NONBLOCK`
   again after opening.
 
+- These days we generally prefer `openat()`-style file APIs, i.e. APIs that
+  accept a combination of file descriptor and path string, and where the path
+  (if not absolute) is considered relative to the specified file
+  descriptor. When implementing library calls in similar style, please make
+  sure to imply `AT_EMPTY_PATH` if an empty or `NULL` path argument is
+  specified (and convert that latter to an empty string). This differs from the
+  underlying kernel semantics, where `AT_EMPTY_PATH` must always be specified
+  explicitly, and `NULL` is not acepted as path.
+
 ## Command Line
 
 - If you parse a command line, and want to store the parsed parameters in
diff --git a/docs/COREDUMP.md b/docs/COREDUMP.md
new file mode 100644 (file)
index 0000000..c64579e
--- /dev/null
@@ -0,0 +1,147 @@
+---
+title: systemd Coredump Handling
+category: Concepts
+layout: default
+SPDX-License-Identifier: LGPL-2.1-or-later
+---
+
+# systemd Coredump Handling
+
+## Support in the Service Manager (PID 1)
+
+The systemd service manager natively provides coredump handling functionality,
+as implemented by the Linux kernel. Specifically, PID 1 provides the following
+functionality:
+
+1. During very early boot it will raise the
+   [`LIMIT_CORE`](https://man7.org/linux/man-pages/man2/getrlimit.2.html)
+   resource limit for itself to infinity (and thus implicitly also all its
+   children). This removes any limits on the size of generated coredumps, for
+   all invoked processes, from earliest boot on. (The Linux kernel sets the
+   limit to 0 by default.)
+
+2. At the same time it will turn off coredump handling in the kernel by writing
+   `|/bin/false` into `/proc/sys/kernel/core_pattern` (also known as the
+   "`kernel.core_pattern` sysctl"; see
+   [core(5)](https://man7.org/linux/man-pages/man5/core.5.html) for
+   details). This means that coredumps are not actually processed. (The Linux
+   kernel sets the pattern to `core` by default, so that coredumps are written
+   to the current working directory of the crashing process.)
+
+Net effect: after PID1 has started and performed this setup coredumps are
+disabled, but by means of the the `kernel.core_pattern` sysctl rather than by
+size limit. This is generally preferable, since the pattern can be updated
+trivially at the right time to enable coredumping once the system is ready,
+taking comprehensive effect on all userspace. (Or to say this differently:
+disabling coredumps via the size limit is problematic, since it cannot easily
+be undone without iterating through all already running processes once the
+system is ready for coredump handling.)
+
+Processing of core dumps may be enabled at the appropriate time by updating the
+`kernel.core_pattern` sysctl. Only coredumps that happen later will be
+processed.
+
+During the final shutdown phase the `kernel.core_pattern` sysctl is updated
+again to `|/bin/false`, disabling coredump support again, should it have been
+enabled in the meantime.
+
+This means coredump handling is generally not available during earliest boot
+and latest shutdown, reflecting the fact that storage is typically not
+available in these environments, and many other facilities are missing too that
+are required to collect and process a coredump successfully.
+
+## `systemd-coredump` Handler
+
+The systemd suite provides a coredump handler
+[`systemd-coredump`](https://www.freedesktop.org/software/systemd/man/systemd-coredump.html)
+which can be enabled at build-time. It is activated during boot via the
+`/usr/lib/sysctl.d/50-coredump.conf` drop-in file for
+`systemd-sysctl.service`. It registers the `systemd-coredump` tool as
+`kernel.core_pattern` sysctl.
+
+`systemd-coredump` is implemented as socket activated service: when the kernel
+invokes the userspace coredump handler, the received coredump file descriptor
+is immediately handed off to the socket activated service
+`systemd-coredump@.service` via the `systemd-coredump.socket` socket unit. This
+means the coredump handler runs for a very short time only, and the potentially
+*heavy* and security sensitive coredump processing work is done as part of the
+specified service unit, and thus can take benefit of regular service resource
+management and sandboxing.
+
+The `systemd-coredump` handler will extract a backtrace and [ELF packaging
+metadata](https://systemd.io/ELF_PACKAGE_METADATA) from any coredumps it
+receives and log both. The information about coredumps stored in the journal
+can be enumerated and queried with the
+[`coredumpctl`](https://www.freedesktop.org/software/systemd/man/coredumpctl.html)
+tool, for example for directly invoking a debugger such as `gdb` on a collected
+coredump.
+
+The handler writes coredump files to `/var/lib/systemd/coredump/`.  Old files
+are cleaned up periodically by
+[`systemd-tmpfiles(8)`](https://www.freedesktop.org/software/systemd/man/systemd-tmpfiles.html).
+
+## User Experience
+
+With the above, any coredumps generated on the system are by default collected
+and turned into logged events — except during very early boot and late
+shutdown. Individual services, processes or users can opt-out of coredump
+collection, by setting `LIMIT_CORE` to 0 (or alternatively invoke
+[`PR_SET_DUMPABLE`](https://man7.org/linux/man-pages/man2/prctl.2.html)). The
+resource limit can be set freely by daemons/processes/users to arbitrary
+values, which the coredump handler will respect. The `coredumpctl` tool may be
+used to further analyze/debug coredumps.
+
+## Alternative Coredump Handlers
+
+While we recommend usage of the `systemd-coredump` handler, it's fully
+supported to use alternative coredump handlers instead. A similar
+implementation pattern is recommended. Specifically:
+
+1. Use a `sysctl.d/` drop-in to register your handler with the kernel. Make
+   sure to include the `%c` specifier in the pattern (which reflects the
+   crashing process' `RLIMIT_CORE`) and act on it: limit the stored coredump
+   file to the specified limit.
+
+2. Do not do heavy processing directly in the coredump handler. Instead,
+   quickly pass off the kernel's coredump file descriptor to an
+   auxiliary service running as service under the service manager, so that it
+   can be done under supervision, sandboxing and resource management.
+
+Note that at any given time only a single handler can be enabled, i.e. the
+`kernel.core_pattern` sysctl cannot reference multiple executables.
+
+## Packaging
+
+It might make sense to split `systemd-coredump` into a separate distribution
+package. If doing so, make sure that `/usr/lib/sysctl.d/50-coredump.conf` and
+the associated service and socket units are also added to the split off package.
+
+Note that in a scenario where `systemd-coredump` is split out and not
+installed, coredumping is turned off during the entire runtime of the system —
+unless an alternative handler is installed, or behaviour is manually reverted
+to legacy style handling (see below).
+
+## Restoring Legacy Coredump Handling
+
+The default policy of the kernel to write coredumps into the current working
+directory of the crashing process is considered highly problematic by many,
+including by the systemd maintainers. Nonetheless, if users locally want to
+return to this behaviour, two changes must be made (followed by a reboot):
+
+```console
+$ mkdir -p /etc/sysctl.d
+$ cat >/etc/sysctl.d/50-coredump.conf <<EOF
+# Party like it's 1995!
+kernel.core_pattern=core
+EOF
+```
+
+and
+
+```console
+$ mkdir -p /etc/systemd/system.conf.d
+$ cat >/etc/systemd/system.conf.d/50-coredump.conf <<EOF
+[Manager]
+DefaultLimitCORE=0:infinity
+EOF
+```
index 8cdf6d7ffa98bf076ebeed4d94a159986464d46c..083c7ecc3c532409622b15128e64cf00827dc604 100644 (file)
@@ -195,7 +195,7 @@ The `systemd-creds` tool provides the commands `encrypt` and `decrypt` to
 encrypt and decrypt/authenticate credentials. Example:
 
 ```sh
-systemd-creds encrypt plaintext.txt ciphertext.cred
+systemd-creds encrypt --name=foobar plaintext.txt ciphertext.cred
 shred -u plaintext.txt
 systemd-run -P --wait -p LoadCredentialEncrypted=foobar:$(pwd)/ciphertext.cred systemd-creds cat foobar
 ```
index 3ec5573ff9524e0ee4059331c6fc9ae8473a1f95..2ba222d3a627571aee6180c8fae937d08cebd89a 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.
@@ -91,7 +90,7 @@ All tools:
 
 * `$SYSTEMD_UTF8=` — takes a boolean value, and overrides whether to generate
   non-ASCII special glyphs at various places (i.e. "→" instead of
-  "->"). Usually this is deterined automatically, based on $LC_CTYPE, but in
+  "->"). Usually this is determined automatically, based on `$LC_CTYPE`, but in
   scenarios where locale definitions are not installed it might make sense to
   override this check explicitly.
 
@@ -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`:
 
@@ -340,9 +341,9 @@ the journal instead of only when logging in debug mode.
 
 `systemd-sysusers`
 
-* `SOURCE_DATE_EPOCH` — if unset, the field of the date of last password change
+* `$SOURCE_DATE_EPOCH` — if unset, the field of the date of last password change
   in `/etc/shadow` will be the number of days from Jan 1, 1970 00:00 UTC until
-  today. If SOURCE_DATE_EPOCH is set to a valid UNIX epoch value in seconds,
+  today. If `$SOURCE_DATE_EPOCH` is set to a valid UNIX epoch value in seconds,
   then the field will be the number of days until that time instead. This is to
   support creating bit-by-bit reproducible system images by choosing a
   reproducible value for the field of the date of last password change in
@@ -430,7 +431,7 @@ disk images with `--image=` or similar:
   specified defaults to something like: `ext4:btrfs:xfs:vfat:erofs:squashfs`
 
 * `$SYSTEMD_LOOP_DIRECT_IO` – takes a boolean, which controls whether to enable
-  LO_FLAGS_DIRECT_IO (i.e. direct IO + asynchronous IO) on loopback block
+  `LO_FLAGS_DIRECT_IO` (i.e. direct IO + asynchronous IO) on loopback block
   devices when opening them. Defaults to on, set this to "0" to disable this
   feature.
 
@@ -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 0adf5ada5470d2f26819ca19f288c213d6c51a67..d5c53734300145f31536fd07987708ea203053b6 100644 (file)
@@ -88,7 +88,7 @@ $ git clone https://github.com/systemd/systemd.git
 $ cd systemd
 $ git checkout -b <BRANCH>        # where BRANCH is the name of the branch
 $ vim src/core/main.c             # or wherever you'd like to make your changes
-$ meson build                     # configure the build
+$ meson setup build -Danalyze=true -Drepart=true -Defi=true -Dbootloader=true -Dukify=true # configure the build
 $ ninja -C build                  # build it locally, see if everything compiles fine
 $ meson test -C build             # run some simple regression tests
 $ cd ..
@@ -215,24 +215,25 @@ vscode documentation [here](https://code.visualstudio.com/docs/cpp/launch-json-r
 ## Debugging systemd with mkosi + vscode
 
 To simplify debugging systemd when testing changes using mkosi, we're going to show how to attach
-[VSCode](https://code.visualstudio.com/)'s debugger to an instance of systemd running in a mkosi image
-(either using QEMU or systemd-nspawn).
+[VSCode](https://code.visualstudio.com/)'s debugger to an instance of systemd running in a mkosi image using
+QEMU.
 
 To allow VSCode's debugger to attach to systemd running in a mkosi image, we have to make sure it can access
-the container/virtual machine spawned by mkosi where systemd is running. mkosi makes this possible via a
-handy SSH option that makes the generated image accessible via SSH when booted. Thus you must build
-the image with `mkosi --ssh`. The easiest way to set the
-option is to create a file 20-local.conf in mkosi.default.d/ (in the directory you ran mkosi in) and add
-the following contents:
+the virtual machine spawned by mkosi where systemd is running. mkosi makes this possible via a handy SSH
+option that makes the generated image accessible via SSH when booted. Thus you must build the image with
+`mkosi --ssh`. The easiest way to set the option is to create a file 20-local.conf in mkosi.conf.d/ (in the
+directory you ran mkosi in) and add the following contents:
 
 ```
 [Host]
 Ssh=yes
 ```
 
-Next, make sure systemd-networkd is running on the host system so that it can configure the network interface
-connecting the host system to the container/VM spawned by mkosi. Once systemd-networkd is running, you should
-be able to connect to a running mkosi image by executing `mkosi ssh` in the systemd repo directory.
+Also make sure that the SSH agent is running on your system and that you've added your SSH key to it with
+`ssh-add`.
+
+After rebuilding the image and booting it with `mkosi qemu`, you should now be able to connect to it by
+running `mkosi ssh` from the same directory in another terminal window.
 
 Now we need to configure VSCode. First, make sure the C/C++ extension is installed. If you're already using
 a different extension for code completion and other IDE features for C in VSCode, make sure to disable the
@@ -270,11 +271,11 @@ the directory, and add the following contents:
             },
             "MIMode": "gdb",
             "sourceFileMap": {
-                "/root/build/../src": {
+                "/work/build/../src": {
                     "editorPath": "${workspaceFolder}",
                     "useForBreakpoints": false
                 },
-                "/root/build/*": {
+                "/work/build/*": {
                     "editorPath": "${workspaceFolder}/mkosi.builddir",
                     "useForBreakpoints": false
                 }
@@ -339,7 +340,7 @@ To debug systemd-boot in an IDE such as VSCode we can use a launch configuration
 If you're hacking on the kernel in tandem with systemd, you can clone a kernel repository in mkosi.kernel/ in
 the systemd repository, and mkosi will automatically build that kernel and install it into the final image.
 To prevent the distribution's kernel from being installed (which isn't necessary since we're building our
-own kernel), you can add the following snippets to mkosi.default.d/20-local.conf:
+own kernel), you can add the following snippets to mkosi.conf.d/20-local.conf:
 
 (This snippet is for Fedora, the list of packages will need to be changed for other distributions)
 
index 6a14d74f4416edffe5c12d739b8d75c85b7cfa34..aed25cb49afb7909a4b44abb1c4bc0f13bc52e03 100644 (file)
@@ -29,7 +29,7 @@ userspace process can detect mounting memory pressure early and release memory
 back to the kernel as it happens, relieving the memory pressure before it
 becomes too critical.
 
-The effects of memory pressure during runtime generaly are growing latencies
+The effects of memory pressure during runtime generally are growing latencies
 during operation: when a program requires memory but the system is busy writing
 out memory to (relatively slow) disks in order make some available, this
 generally surfaces in scheduling latencies, and applications and services will
@@ -146,8 +146,8 @@ is specifically useful for services that consist of multiple processes, and
 where each of them shall be able to release resources on memory pressure.
 
 The `POLLPRI`/`POLLIN` conditions will be triggered every time memory pressure
-is detected, but not continously. It is thus safe to keep `poll()`-ing on the
-same file descriptor continously, and executing resource release operations
+is detected, but not continuously. It is thus safe to keep `poll()`-ing on the
+same file descriptor continuously, and executing resource release operations
 whenever the file descriptor triggers without having to expect overloading the
 process.
 
index b12e7754e7490e6e6b51b3c161f20d90a8642a6f..8d65c9002d0a361d990275eaf616a78009bd0b82 100644 (file)
@@ -335,6 +335,45 @@ service data may be placed on the host file system. Use `StateDirectory=` in
 the unit files to enable such behaviour and add a local data directory to the
 services copied onto the host.
 
+## Logging
+
+Several fields are autotmatically added to log messages generated by a portable
+service (or about a portable service, e.g.: start/stop logs from systemd).
+The `PORTABLE=` field will refer to the name of the portable image where the unit
+was loaded from. In case extensions are used, additionally there will be a
+`PORTABLE_ROOT=` field, referring to the name of image used as the base layer
+(i.e.: `RootImage=` or `RootDirectory=`), and one `PORTABLE_EXTENSION=` field per
+each extension image used.
+
+The `os-release` file from the portable image will be parsed and added as structured
+metadata to the journal log entries. The parsed fields will be the first ID field which
+is set from the set of `IMAGE_ID` and `ID` in this order of preference, and the first
+version field which is set from a set of `IMAGE_VERSION`, `VERSION_ID`, and `BUILD_ID`
+in this order of preference. The ID and version, if any, are concatenated with an
+underscore (`_`) as separator. If only either one is found, it will be used by itself.
+The field will be named `PORTABLE_NAME_AND_VERSION=`.
+
+In case extensions are used, the same fields in the same order are, but prefixed by
+`SYSEXT_`, are parsed from each `extension-release` file, and are appended to the
+journal as log entries, using `PORTABLE_EXTENSION_NAME_AND_VERSION=` as the field
+name. The base layer's field will be named `PORTABLE_ROOT_NAME_AND_VERSION=` instead
+of `PORTABLE_NAME_AND_VERSION=` in this case.
+
+For example, a portable service `app0` using two extensions `app0.raw` and
+`app1.raw` (with `SYSEXT_ID=app`, and `SYSEXT_VERSION_ID=` `0` and `1` in their
+respective extension-releases), and a base layer `base.raw` (with `VERSION_ID=10` and
+`ID=debian` in `os-release`), will create log entries with the following fields:
+
+```
+PORTABLE=app0.raw
+PORTABLE_ROOT=base.raw
+PORTABLE_ROOT_NAME_AND_VERSION=debian_10
+PORTABLE_EXTENSION=app0.raw
+PORTABLE_EXTENSION_NAME_AND_VERSION=app_0
+PORTABLE_EXTENSION=app1.raw
+PORTABLE_EXTENSION_NAME_AND_VERSION=app_1
+```
+
 ## Links
 
 [`portablectl(1)`](https://www.freedesktop.org/software/systemd/man/portablectl.html)<br>
index 4e815ed4d285aef530c62ee31aa747601048de58..bc9cb7bc455c8f0e180ba288199a77aa8a23ed7a 100644 (file)
@@ -84,7 +84,7 @@ By default, `systemd-tmpfiles` will apply a concept of ⚠️ "ageing" to all fi
 and directories stored in `/tmp/` and `/var/tmp/`. This means that files that
 have neither been changed nor read within a specific time frame are
 automatically removed in regular intervals. (This concept is not new to
-`systemd-tmpfiles` btw, it's inherited from previous subsystems such as
+`systemd-tmpfiles`, it's inherited from previous subsystems such as
 `tmpwatch`.) By default files in `/tmp/` are cleaned up after 10 days, and
 those in `/var/tmp` after 30 days.
 
@@ -111,14 +111,13 @@ strategies to avoid these issues:
    towards unexpected program termination as there are never files on disk that
    need to be explicitly deleted.
 
-3. 🥇 Operate below a sub-directory of `/tmp/` and `/var/tmp/` you created, and
-   take a BSD file lock ([`flock(dir_fd,
-   LOCK_SH)`](https://man7.org/linux/man-pages/man2/flock.2.html)) on that
-   sub-directory. This is particularly interesting when operating on more than
-   a single file, or on file nodes that are not plain regular files, for
-   example when extracting a tarball to a temporary directory. The ageing
-   algorithm will skip all directories (and everything below them) that are
-   locked through a BSD file lock. As BSD file locks are automatically released
+3. 🥇 Take an exclusive or shared BSD file lock ([`flock()`](
+   https://man7.org/linux/man-pages/man2/flock.2.html)) on files and directories
+   you don't want to be removed. This is particularly interesting when operating
+   on more than a single file, or on file nodes that are not plain regular files,
+   for example when extracting a tarball to a temporary directory. The ageing
+   algorithm will skip all directories (and everything below them) and files that
+   are locked through a BSD file lock. As BSD file locks are automatically released
    when the file descriptor they are taken on is closed, and all file
    descriptors opened by a process are implicitly closed when it exits, this is
    a robust mechanism that ensures all temporary files are subject to ageing
@@ -127,9 +126,7 @@ strategies to avoid these issues:
    modification/access times, as extracted files are otherwise immediately
    candidates for deletion by the ageing algorithm. The
    [`flock`](https://man7.org/linux/man-pages/man1/flock.1.html) tool of the
-   `util-linux` packages makes this concept available to shell scripts. Note
-   that `systemd-tmpfiles` only checks for BSD file locks on directories, locks
-   on other types of file nodes (including regular files) are not considered.
+   `util-linux` packages makes this concept available to shell scripts.
 
 4. Keep the access time of all temporary files created current. In regular
    intervals, use `utimensat()` or a related call to update the access time
index ef98cf0121c63cc05619dd3700d294858fb1cdc5..67b6be46c284db10fd2e011a414b0cead037176d 100644 (file)
@@ -15,7 +15,7 @@ compiler you want to use and which part of the test suite you want to run.
 
 ## mkosi
 
-To build with sanitizers in mkosi, create a file 20-local.conf in mkosi.default.d/ and add the following
+To build with sanitizers in mkosi, create a file 20-local.conf in mkosi.conf.d/ and add the following
 contents:
 
 ```
index 9170a678507893dd1cff9dc07cee830a5b75f73c..768f9d0072a5610a57c5ec1a6fcc920439b79dd0 100644 (file)
@@ -638,7 +638,7 @@ field of `struct passwd`).
 
 `sshAuthorizedKeys` → An array of strings, each listing an SSH public key that
 is authorized to access the account. The strings should follow the same format
-as the lines in the traditional `~/.ssh/authorized_key` file.
+as the lines in the traditional `~/.ssh/authorized_keys` file.
 
 `pkcs11EncryptedKey` → An array of objects. Each element of the array should be
 an object consisting of three string fields: `uri` shall contain a PKCS#11
index 7a969a84b5cd46d842cbd4eb72650004ea3efa16..56cb3d3636fbf8474c11f75bc1d9c1854e204282 100644 (file)
@@ -583,6 +583,13 @@ evdev:input:b0003v17EFp60B5*
  EVDEV_ABS_35=::12
  EVDEV_ABS_36=::11
 
+# Lenovo Thinkpad L14 Gen1 (AMD)
+evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*pvrThinkPadL14Gen1**
+ EVDEV_ABS_00=::44
+ EVDEV_ABS_01=::50
+ EVDEV_ABS_35=::44
+ EVDEV_ABS_36=::50
+
 # Lenovo T460
 evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO*:pn*ThinkPad*T460:*
  EVDEV_ABS_00=1266:5677:44
index f638023f76c2b7934eead980c9580e19a7973761..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
@@ -1406,6 +1406,7 @@ evdev:input:b0003v1532p0200*
 
 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pn*:*
 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*:*
+ KEYBOARD_KEY_76=f21                                    # Toggle touchpad, sends meta+ctrl+toggle
  KEYBOARD_KEY_91=config                                 # MSIControl Center
  KEYBOARD_KEY_a0=mute                                   # Fn+F9
  KEYBOARD_KEY_ae=volumedown                             # Fn+F7
@@ -1446,8 +1447,6 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnU90/U100:*
 # Keymaps MSI Prestige And MSI Modern FnKeys and Special keys
 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*Prestige*:*
 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*Modern*:*
- KEYBOARD_KEY_f1=f20                                    # Fn+F5 Micmute
- KEYBOARD_KEY_76=f21                                    # Fn+F4 Toggle touchpad, sends meta+ctrl+toggle
  KEYBOARD_KEY_91=prog1                                  # Fn+F7 Creation Center, sometime F7
  KEYBOARD_KEY_f2=prog2                                  # Fn+F12 Screen rotation
  KEYBOARD_KEY_8d=prog3                                  # Fn+A Change True Color selections
index 2c19ab33d611f1bcf72833471c3f2eb388924352..ebf8c718a736defd09f53fad105435a1d32a3014 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
 #########################################
@@ -189,6 +192,15 @@ sensor:modalias:acpi:BMI0160*:dmi:*:svnAYANEO:pnAIR*:*
 sensor:modalias:acpi:BMI0160*:dmi:*:svnAYANEO:pn*NEXT*:*
  ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
 
+#########################################
+# BMAX
+#########################################
+
+# BMAX Y13
+sensor:modalias:acpi:KIOX010A:*:dmi:*:svnAMI:*:skuH2M6:*
+ ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, -1, 0; 0, 0, -1
+ ACCEL_LOCATION=display
+
 #########################################
 # Chuwi
 #########################################
@@ -648,6 +660,10 @@ sensor:modalias:i2c:bmc150_accel:dmi:*:svnLENOVO:*:pvrLenovoYoga300-11IBR:*
  ACCEL_MOUNT_MATRIX=1, 0, 0; 0, 1, 0; 0, 0, -1
  ACCEL_LOCATION=base
 
+# Yoga Tablet 2 851F/L
+sensor:modalias:acpi:ACCL0001*:dmi:*:svnLENOVO:pn60072:pvr851*:*
+ ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
+
 # IdeaPad Duet 3 10IGL5 (82AT)
 sensor:modalias:acpi:SMO8B30*:dmi:*:svnLENOVO*:pn82AT:*
  ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1
@@ -808,6 +824,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
 #########################################
@@ -963,6 +987,10 @@ sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAENCORE2WT8-B:*
 sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-103:*
  ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
 
+# Toshiba Encore WT10A-102 tablet
+sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-102:*
+ ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
+
 #########################################
 # Trekstor
 #########################################
index 9d5c4fc06916e5cd2cb284dd94120d19a2962b84..8e942c8c1b39ad42e3ed57cd5fe6228f3a7c4db4 100644 (file)
@@ -5,11 +5,6 @@
 # The lookup keys are composed in:
 #   70-joystick.rules
 #
-# Note: The format of the "joystick:" prefix match key is a
-# contract between the rules file and the hardware data, it might
-# change in later revisions to support more or better matches, it
-# is not necessarily expected to be a stable ABI.
-#
 # Match string format:
 # joystick:<bustype>:v<vid>p<pid>:name:<name>:
 #
index 463f0ad30b02aa62afde41f3cec041e2e5b89da5..159bc786213d6d04821adc2c2b7c856f8baf9eee 100644 (file)
@@ -153,6 +153,15 @@ mouse:*:name:*TrackBall*:*
 mouse:bluetooth:v05acp030d:name:*:*
  MOUSE_DPI=1300@1000
 
+##########################################
+# Cherry
+##########################################
+
+# Cherry MW 2310
+mouse:usb:v1A81p1701:name:G-Tech Wireless Dongle Mouse:*
+ KEYBOARD_KEY_90005=back
+ KEYBOARD_KEY_90004=forward
+
 ##########################################
 # Chicony
 ##########################################
@@ -391,7 +400,9 @@ mouse:usb:v046dpc08b:name:Logitech G502 HERO Gaming Mouse:*
 # Logitech G502 X (Wired)
 mouse:usb:v046dpc098:name:Logitech, Inc. G502 X LIGHTSPEED:*
 # Logitech G502 X (Wireless)
-mouse:usb:v046dpc547:name:Logitech USB Receiver:*
+# The USB receiver is also used by other mice. See #27118.
+# If you want to enable the entry, please copy below to your custom hwdb file.
+#mouse:usb:v046dpc547:name:Logitech USB Receiver:*
  MOUSE_DPI=1200@1000 *2400@1000 3200@1000 6400@1000
 
 # Logitech G700 Laser Mouse (Wired)
index 8194d983587df17f1c8684d5af5fdb4c7acce38b..262bca3a195dc5fe8c935c83b76b4d37ae34f076 100644 (file)
@@ -5,11 +5,6 @@
 # The lookup keys are composed in:
 #   70-touchpad.rules
 #
-# Note: The format of the "touchpad:" prefix match key is a
-# contract between the rules file and the hardware data, it might
-# change in later revisions to support more or better matches, it
-# is not necessarily expected to be a stable ABI.
-#
 # Match string format:
 # touchpad:<subsystem>:v<vid>p<pid>:name:<name>:
 #
index a6f1fc1c4cfedd094ba4675ae6f89eb3f5f8dd2f..5f98486343d813539f44901a940c679ed4fefd48 100644 (file)
         switch of the same name.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--install-source=</option></term>
         <listitem><para>When installing binaries with <option>--root=</option> or
index 0e220b3f9ebb93e757d7d533170ac52664cc7d42..81425e57e20aeb51cae8701ebb1a24b20c27d573 100644 (file)
       </listitem>
     </varlistentry>
 
+    <varlistentry id='log-ratelimit-kmsg'>
+      <term><varname>$SYSTEMD_LOG_RATELIMIT_KMSG</varname></term>
+
+      <listitem><para id='log-ratelimit-kmsg-body'> Whether to ratelimit kmsg or not. Takes a boolean.
+      Defaults to <literal>true</literal>. If disabled, systemd will not ratelimit messages written to kmsg.
+      </para></listitem>
+    </varlistentry>
+
     <varlistentry id='pager'>
       <term><varname>$SYSTEMD_PAGER</varname></term>
 
index 79632eb2d4f7f3737eb86aec9800bd7b92322fff..0f4a2e83e671981459efd334c8986e47fee94eba 100644 (file)
         switch of the same name.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>-q</option></term>
         <term><option>--quiet</option></term>
index 0376e0feb99731095e3b9366903b65af88f43280..aa0e1ef1ae36c54d031847f5067985fc4813522d 100644 (file)
@@ -17,5 +17,5 @@
 <!ENTITY DEFAULT_DNS_OVER_TLS_MODE "{{DEFAULT_DNS_OVER_TLS_MODE_STR}}">
 <!ENTITY DEFAULT_TIMEOUT "{{DEFAULT_TIMEOUT_SEC}} s">
 <!ENTITY DEFAULT_USER_TIMEOUT "{{DEFAULT_USER_TIMEOUT_SEC}} s">
-<!ENTITY fedora_latest_version "36">
-<!ENTITY fedora_cloud_release "1.5">
+<!ENTITY fedora_latest_version "37">
+<!ENTITY fedora_cloud_release "1.7">
index 4961f019f08782c78628367fc5aa57b6d83fd804..9e91af9060986a2d6c4bac3cdb4609731a1f19b3 100644 (file)
           </row>
           <row>
       <entry><filename>/var/log/<replaceable>package</replaceable>/</filename></entry>
-      <entry>Persistent log data of the package. As above, the package should make sure to create this directory if necessary, possibly using <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> or <varname>LogsDirectory=</varname> (see <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>), as it might be missing.</entry>
+      <entry>Persistent log data of the package. As above, the package should make sure to create this directory if necessary, possibly using <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> or <varname>LogsDirectory=</varname> (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>), as it might be missing.</entry>
           </row>
           <row>
       <entry><filename>/var/spool/<replaceable>package</replaceable>/</filename></entry>
index 5e545b741282a3d40c5676050b312f85afdbac8c..aaff9d138382f4c38ac1d367e3e2d892ba24aed4 100755 (executable)
@@ -14,7 +14,11 @@ target="man/$1.html"
 ninja -C "@BUILD_ROOT@" "$target"
 
 fullname="@BUILD_ROOT@/$target"
-redirect="$(test -f "$fullname" && readlink "$fullname" || :)"
+if [ -f "$fullname" ]; then
+    redirect="$(readlink "$fullname" || :)"
+else
+    redirect=""
+fi
 if [ -n "$redirect" ]; then
     ninja -C "@BUILD_ROOT@" "man/$redirect"
 
diff --git a/man/iocost.conf.xml b/man/iocost.conf.xml
new file mode 100644 (file)
index 0000000..be74244
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="iocost.conf" xmlns:xi="http://www.w3.org/2001/XInclude">
+  <refentryinfo>
+    <title>iocost.conf</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>iocost.conf</refentrytitle>
+    <manvolnum>5</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>iocost.conf</refname>
+    <refpurpose>Configuration files for the iocost solution manager</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para>
+      <filename>/etc/systemd/iocost.conf</filename>
+      <filename>/etc/systemd/iocost.conf.d/*.conf</filename>
+    </para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para>This file configures the behavior of <literal>iocost</literal>, a tool mostly used by
+    <citerefentry><refentrytitle>systemd-udevd</refentrytitle><manvolnum>8</manvolnum></citerefentry> rules
+    to automatically apply I/O cost solutions to <filename>/sys/fs/cgroup/io.cost.*</filename>.</para>
+
+    <para>The qos and model values are calculated based on benchmarks collected on the
+    <ulink url="https://github.com/iocost-benchmark/iocost-benchmarks">iocost-benchmark</ulink>
+    project and turned into a set of solutions that go from most to least isolated.
+    Isolation allows the system to remain responsive in face of high I/O load.
+    Which solutions are available for a device can be queried from the udev metadata attached to it. By
+    default the naive solution is used, which provides the most bandwidth.</para>
+  </refsect1>
+
+  <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+  <refsect1>
+    <title>Options</title>
+
+    <para>All options are configured in the [IOCost] section:</para>
+
+    <variablelist class='config-directives'>
+
+      <varlistentry>
+        <term><varname>TargetSolution=</varname></term>
+
+        <listitem><para>Chooses which I/O cost solution (identified by named string) should be used
+        for the devices in this system. The known solutions can be queried from the udev metadata
+        attached to the devices. If a device does not have the specified solution, the first one
+        listed in <varname>IOCOST_SOLUTIONS</varname> is used instead.</para>
+
+        <para>E.g. <literal>TargetSolution=isolated-bandwidth</literal>.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <ulink url="https://github.com/iocost-benchmark/iocost-benchmarks">The iocost-benchmarks github project</ulink>,
+      <ulink url="https://github.com/facebookexperimental/resctl-demo/tree/main/resctl-bench/doc">The resctl-bench
+      documentation details how the values are obtained</ulink>
+    </para>
+  </refsect1>
+
+</refentry>
index ae86c50d6248bb445cf545df013b2ee112deaaf5..aa124dd98f06c63112aac73e1be9f602c29352e2 100644 (file)
         switch of the same name.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--namespace=<replaceable>NAMESPACE</replaceable></option></term>
 
index 0528c4b67255abd6dae80d465ab72119fd6ff8e7..09f8ace4de691f7287e91cdad435a8479f281518 100644 (file)
@@ -66,6 +66,7 @@
         <term><varname>systemd.log_level=</varname></term>
         <term><varname>systemd.log_location=</varname></term>
         <term><varname>systemd.log_color</varname></term>
+        <term><varname>systemd.log_ratelimit_kmsg</varname></term>
         <term><varname>systemd.default_standard_output=</varname></term>
         <term><varname>systemd.default_standard_error=</varname></term>
         <term><varname>systemd.setenv=</varname></term>
         <term><varname>rd.systemd.gpt_auto=</varname></term>
 
         <listitem>
-          <para>Configures whether GPT based partition auto-discovery
-          shall be attempted. For details, see
+          <para>Configures whether GPT-based partition auto-discovery shall be attempted. For details, see
           <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>systemd.image_policy=</varname></term>
+        <term><varname>rd.systemd.image_policy=</varname></term>
+
+        <listitem><para>When GPT-based partition auto-discovery is used, configures the image dissection
+        policy string to apply, as per
+        <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. For
+        details see
+        <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>systemd.default_timeout_start_sec=</varname></term>
 
         is set in <filename>/etc/hostname</filename>. Note that this does not bar later runtime changes to
         the hostname, it simply controls the initial hostname set during early boot.</para></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>systemd.tty.term.<replaceable>tty</replaceable>=</varname></term>
+        <term><varname>systemd.tty.rows.<replaceable>tty</replaceable>=</varname></term>
+        <term><varname>systemd.tty.columns.<replaceable>tty</replaceable>=</varname></term>
+
+        <listitem><para>These arguments allow configuring default values for <varname>$TERM</varname>,
+        <varname>TTYRows=</varname>, and <varname>TTYColumns=</varname> for tty
+        <replaceable>tty</replaceable>. The tty name should be specified without the
+        <filename>/dev/</filename> prefix (e.g. <literal>systemd.tty.rows.ttyS0=80</literal>).
+        </para></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index e1fc4e0b493a49275d60dc7f2ec3ec9c930ec028..4f30e9b25241c6d69be8ec2d0fd6bd6078d591e8 100644 (file)
         </varlistentry>
       </variablelist>
 
-      <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> is set for plugins to select the initrd
-      generator. This may be configured as <varname>initrd_generator=</varname> in
-      <filename>install.conf</filename>, see below.</para>
+      <para><varname>$KERNEL_INSTALL_INITRD_GENERATOR</varname> and <varname>$KERNEL_INSTALL_UKI_GENERATOR</varname>
+      are set for plugins to select the initrd and/or UKI generator. This may be configured as
+      <varname>initrd_generator=</varname> and <varname>uki_generator=</varname> in <filename>install.conf</filename>, see below.</para>
 
       <para><varname>$KERNEL_INSTALL_STAGING_AREA</varname> is set for plugins to a path to a directory.
       Plugins may drop files in that directory, and they will be installed as part of the loader entry, based
             <varname>MACHINE_ID=</varname>,
             <varname>BOOT_ROOT=</varname>,
             <varname>layout=</varname>,
-            <varname>initrd_generator=</varname>.
+            <varname>initrd_generator=</varname>,
+            <varname>uki_generator=</varname>.
             See the Environment variables section above for details.</para>
           </listitem>
       </varlistentry>
diff --git a/man/logcontrol-example.c b/man/logcontrol-example.c
new file mode 100644 (file)
index 0000000..734318d
--- /dev/null
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: MIT-0 */
+
+/* Implements the LogControl1 interface as per specification:
+ * https://www.freedesktop.org/software/systemd/man/org.freedesktop.LogControl1.html
+ *
+ * Compile with 'cc logcontrol-example.c $(pkg-config --libs --cflags libsystemd)'
+ *
+ * To get and set properties via busctl:
+ *
+ * $ busctl --user get-property org.freedesktop.Example \
+ *                              /org/freedesktop/LogControl1 \
+ *                              org.freedesktop.LogControl1 \
+ *                              SyslogIdentifier
+ *   s "example"
+ * $ busctl --user get-property org.freedesktop.Example \
+ *                              /org/freedesktop/LogControl1 \
+ *                              org.freedesktop.LogControl1 \
+ *                              LogTarget
+ *   s "journal"
+ * $ busctl --user get-property org.freedesktop.Example \
+ *                              /org/freedesktop/LogControl1 \
+ *                              org.freedesktop.LogControl1 \
+ *                              LogLevel
+ *   s "info"
+ * $ busctl --user set-property org.freedesktop.Example \
+ *                              /org/freedesktop/LogControl1 \
+ *                              org.freedesktop.LogControl1 \
+ *                              LogLevel \
+ *                              "s" debug
+ * $ busctl --user get-property org.freedesktop.Example \
+ *                              /org/freedesktop/LogControl1 \
+ *                              org.freedesktop.LogControl1 \
+ *                              LogLevel
+ *   s "debug"
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-journal.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
+
+#define check(log_level, x) ({                  \
+  int _r = (x);                                 \
+  errno = _r < 0 ? -_r : 0;                     \
+  sd_journal_print((log_level), #x ": %m");     \
+  if (_r < 0)                                   \
+    return EXIT_FAILURE;                        \
+  })
+
+typedef enum LogTarget {
+  LOG_TARGET_JOURNAL,
+  LOG_TARGET_KMSG,
+  LOG_TARGET_SYSLOG,
+  LOG_TARGET_CONSOLE,
+  _LOG_TARGET_MAX,
+} LogTarget;
+
+static const char* const log_target_table[_LOG_TARGET_MAX] = {
+  [LOG_TARGET_JOURNAL] = "journal",
+  [LOG_TARGET_KMSG]    = "kmsg",
+  [LOG_TARGET_SYSLOG]  = "syslog",
+  [LOG_TARGET_CONSOLE] = "console",
+};
+
+static const char* const log_level_table[LOG_DEBUG + 1] = {
+  [LOG_EMERG]   = "emerg",
+  [LOG_ALERT]   = "alert",
+  [LOG_CRIT]    = "crit",
+  [LOG_ERR]     = "err",
+  [LOG_WARNING] = "warning",
+  [LOG_NOTICE]  = "notice",
+  [LOG_INFO]    = "info",
+  [LOG_DEBUG]   = "debug",
+};
+
+typedef struct object {
+  const char *syslog_identifier;
+  LogTarget log_target;
+  int log_level;
+} object;
+
+static int property_get(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+  object *o = userdata;
+
+  if (strcmp(property, "LogLevel") == 0)
+    return sd_bus_message_append(reply, "s", log_level_table[o->log_level]);
+
+  if (strcmp(property, "LogTarget") == 0)
+    return sd_bus_message_append(reply, "s", log_target_table[o->log_target]);
+
+  if (strcmp(property, "SyslogIdentifier") == 0)
+    return sd_bus_message_append(reply, "s", o->syslog_identifier);
+
+  return sd_bus_error_setf(error,
+                           SD_BUS_ERROR_UNKNOWN_PROPERTY,
+                           "Unknown property '%s'",
+                           property);
+}
+
+static int property_set(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+  object *o = userdata;
+  const char *value;
+  int r;
+
+  r = sd_bus_message_read(message, "s", &value);
+  if (r < 0)
+    return r;
+
+  if (strcmp(property, "LogLevel") == 0) {
+    for (int i = 0; i < LOG_DEBUG + 1; i++)
+      if (strcmp(value, log_level_table[i]) == 0) {
+        o->log_level = i;
+        return 0;
+      }
+
+    return sd_bus_error_setf(error,
+                             SD_BUS_ERROR_INVALID_ARGS,
+                             "Invalid value for LogLevel: '%s'",
+                             value);
+  }
+
+  if (strcmp(property, "LogTarget") == 0) {
+    for (LogTarget i = 0; i < _LOG_TARGET_MAX; i++)
+      if (strcmp(value, log_target_table[i]) == 0) {
+        o->log_target = i;
+        return 0;
+      }
+
+    return sd_bus_error_setf(error,
+                             SD_BUS_ERROR_INVALID_ARGS,
+                             "Invalid value for LogTarget: '%s'",
+                             value);
+  }
+
+  return sd_bus_error_setf(error,
+                           SD_BUS_ERROR_UNKNOWN_PROPERTY,
+                           "Unknown property '%s'",
+                           property);
+}
+
+/* https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html
+ */
+static const sd_bus_vtable vtable[] = {
+  SD_BUS_VTABLE_START(0),
+  SD_BUS_WRITABLE_PROPERTY(
+    "LogLevel", "s",
+    property_get, property_set,
+    0,
+    0),
+  SD_BUS_WRITABLE_PROPERTY(
+    "LogTarget", "s",
+    property_get, property_set,
+    0,
+    0),
+  SD_BUS_PROPERTY(
+    "SyslogIdentifier", "s",
+    property_get,
+    0,
+    SD_BUS_VTABLE_PROPERTY_CONST),
+  SD_BUS_VTABLE_END
+};
+
+int main(int argc, char **argv) {
+  /* The bus should be relinquished before the program terminates. The cleanup
+   * attribute allows us to do it nicely and cleanly whenever we exit the
+   * block.
+   */
+  _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+  object o = {
+    .log_level = LOG_INFO,
+    .log_target = LOG_TARGET_JOURNAL,
+    .syslog_identifier = "example",
+  };
+
+  /* Acquire a connection to the bus, letting the library work out the details.
+   * https://www.freedesktop.org/software/systemd/man/sd_bus_default.html
+   */
+  check(o.log_level, sd_bus_default(&bus));
+
+  /* Publish an interface on the bus, specifying our well-known object access
+   * path and public interface name.
+   * https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html
+   * https://dbus.freedesktop.org/doc/dbus-tutorial.html
+   */
+  check(o.log_level, sd_bus_add_object_vtable(bus, NULL,
+                                              "/org/freedesktop/LogControl1",
+                                              "org.freedesktop.LogControl1",
+                                              vtable,
+                                              &o));
+
+  /* By default the service is assigned an ephemeral name. Also add a fixed
+   * one, so that clients know whom to call.
+   * https://www.freedesktop.org/software/systemd/man/sd_bus_request_name.html
+   */
+  check(o.log_level, sd_bus_request_name(bus, "org.freedesktop.Example", 0));
+
+  for (;;) {
+    /* https://www.freedesktop.org/software/systemd/man/sd_bus_wait.html
+     */
+    check(o.log_level, sd_bus_wait(bus, UINT64_MAX));
+    /* https://www.freedesktop.org/software/systemd/man/sd_bus_process.html
+     */
+    check(o.log_level, sd_bus_process(bus, NULL));
+  }
+
+  /* https://www.freedesktop.org/software/systemd/man/sd_bus_release_name.html
+   */
+  check(o.log_level, sd_bus_release_name(bus, "org.freedesktop.Example"));
+
+  return 0;
+}
index f67ad99adfaabe3b376f7225f91fa2058c091954..94ec3dc63121eab084f30d03d5822aaca55a843a 100644 (file)
@@ -18,7 +18,7 @@
 
   <refnamediv>
     <refname>networkctl</refname>
-    <refpurpose>Query the status of network links</refpurpose>
+    <refpurpose>Query or modify the status of network links</refpurpose>
   </refnamediv>
 
   <refsynopsisdiv>
@@ -33,7 +33,7 @@
   <refsect1>
     <title>Description</title>
 
-    <para><command>networkctl</command> may be used to introspect the
+    <para><command>networkctl</command> may be used to query or modify the
     state of the network links as seen by
     <command>systemd-networkd</command>.  Please refer to
     <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
index da6dd7628e281732e7ccfc2f48a820c00e9bd317..1694453bafce0b1666e9fa5d1e4145a3c1c09a60 100644 (file)
@@ -116,6 +116,20 @@ node /org/freedesktop/LogControl1 {
     for details about <varname>BusName=</varname>.)</para>
   </refsect1>
 
+  <refsect1>
+    <title>Example</title>
+
+    <example>
+      <title>Create a simple listener on the bus that implements LogControl1</title>
+
+      <programlisting><xi:include href="logcontrol-example.c" parse="text"/></programlisting>
+
+      <para>This creates a simple server on the bus. It implements the LogControl1 interface by providing
+      the required properties and allowing to set the writable ones. It logs at the configured log level using
+      <citerefentry><refentrytitle>sd_journal_print</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
+    </example>
+  </refsect1>
+
   <refsect1>
     <title>See Also</title>
     <para>
index 6781e1dcaaec50e59a5e8615d6a9a869a20e8c30..034fd1078fb894242468be0b808f9ce5344e4d1d 100644 (file)
@@ -1083,6 +1083,7 @@ node /org/freedesktop/login1/session/1 {
       ReleaseControl();
       SetType(in  s type);
       SetDisplay(in  s display);
+      SetTTY(in  h tty_fd);
       TakeDevice(in  u major,
                  in  u minor,
                  out h fd,
@@ -1182,6 +1183,8 @@ node /org/freedesktop/login1/session/1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="SetDisplay()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="SetTTY()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="TakeDevice()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="ReleaseDevice()"/>
@@ -1283,6 +1286,11 @@ node /org/freedesktop/login1/session/1 {
       controller. If <function>TakeControl()</function> has not been called, this method will fail. The only argument
       <varname>display</varname> is the new display name.</para>
 
+      <para><function>SetTTY()</function> allows the device name of the session to be changed. This is
+      useful if the tty device is only known after authentication. It can only be called by session's
+      current controller. If <function>TakeControl()</function> has not been called, this method will fail.
+      The only argument <varname>tty_fd</varname> is a file handle to the new tty device.</para>
+
       <para><function>TakeDevice()</function> allows a session controller to get a file descriptor for a
       specific device. Pass in the major and minor numbers of the character device and
       <filename>systemd-logind</filename> will return a file descriptor for the device. Only a limited set of
index 141fde05b424657736408fc7cf497417b65996cd..e462c606362c6255817387baf2d47da16e58f9a6 100644 (file)
@@ -273,6 +273,8 @@ node /org/freedesktop/systemd1 {
       LookupDynamicUserByUID(in  u uid,
                              out s name);
       GetDynamicUsers(out a(us) users);
+      DumpUnitFileDescriptorStore(in  s name,
+                                  out a(suuutuusu) entries);
     signals:
       UnitNew(s id,
               o unit);
@@ -974,6 +976,8 @@ node /org/freedesktop/systemd1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="GetDynamicUsers()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="DumpUnitFileDescriptorStore()"/>
+
     <variablelist class="dbus-signal" generated="True" extra-ref="UnitNew"/>
 
     <variablelist class="dbus-signal" generated="True" extra-ref="UnitRemoved"/>
@@ -1531,6 +1535,11 @@ node /org/freedesktop/systemd1 {
       <ulink url="https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface">New Control Group
       Interface</ulink> for more information how to make use of this functionality for resource control
       purposes.</para>
+
+      <para><function>DumpUnitFileDescriptorStore()</function> returns an array with information about the
+      file descriptors currently in the file descriptor store of the specified unit. This call is equivalent
+      to <function>DumpFileDescriptorStore()</function> on the
+      <interfacename>org.freedesktop.systemd1.Service</interfacename>. For further details, see below.</para>
     </refsect2>
 
     <refsect2>
@@ -2492,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>
 
@@ -2548,6 +2557,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
                  in  b read_only,
                  in  b mkdir,
                  in  a(ss) options);
+      DumpFileDescriptorStore(out a(suuutuusu) entries);
       GetProcesses(out a(sus) processes);
       AttachProcesses(in  s subcgroup,
                       in  au pids);
@@ -2560,11 +2570,16 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       readonly s Restart = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s PIDFile = '...';
-      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s NotifyAccess = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t RestartUSec = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly u RestartSteps = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly t RestartUSecMax = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+      readonly t RestartUSecNext = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t TimeoutStartUSec = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly t TimeoutStopUSec = ...;
@@ -2604,6 +2619,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       readonly u FileDescriptorStoreMax = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
       readonly u NFileDescriptorStore = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+      readonly s FileDescriptorStorePreserve = '...';
       readonly s StatusText = '...';
       readonly i StatusErrno = ...;
       readonly s Result = '...';
@@ -3152,6 +3169,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s IPCNamespacePath = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s RootImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s MountImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s ExtensionImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s KillMode = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i KillSignal = ...;
@@ -3189,6 +3212,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property RestartUSec is not documented!-->
 
+    <!--property RestartSteps is not documented!-->
+
+    <!--property RestartUSecMax is not documented!-->
+
+    <!--property RestartUSecNext is not documented!-->
+
     <!--property TimeoutStartFailureMode is not documented!-->
 
     <!--property TimeoutStopFailureMode is not documented!-->
@@ -3217,6 +3246,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property NFileDescriptorStore is not documented!-->
 
+    <!--property FileDescriptorStorePreserve is not documented!-->
+
     <!--property StatusErrno is not documented!-->
 
     <!--property ReloadResult is not documented!-->
@@ -3703,6 +3734,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property IPCNamespacePath is not documented!-->
 
+    <!--property RootImagePolicy is not documented!-->
+
+    <!--property MountImagePolicy is not documented!-->
+
+    <!--property ExtensionImagePolicy is not documented!-->
+
     <!--property KillMode is not documented!-->
 
     <!--property KillSignal is not documented!-->
@@ -3731,6 +3768,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-method" generated="True" extra-ref="MountImage()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="DumpFileDescriptorStore()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="GetProcesses()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
@@ -3747,6 +3786,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RestartUSec"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="RestartSteps"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecMax"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="RestartUSecNext"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="TimeoutStartUSec"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="TimeoutStopUSec"/>
@@ -3789,6 +3834,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NFileDescriptorStore"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="FileDescriptorStorePreserve"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="StatusText"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="StatusErrno"/>
@@ -4351,6 +4398,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@@ -4376,6 +4429,16 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       directly on the Manager object has the advantage of not requiring a <function>GetUnit()</function> call
       to get the unit object for a specific unit name. Calling the methods on the Manager object is hence a round
       trip optimization.</para>
+
+      <para><function>DumpFileDescriptorStore()</function> returns an array with information about the file
+      descriptors currently in the file descriptor store of the service. Each entry consists of a file
+      descriptor name (i.e. the <varname>FDNAME=</varname> field), the file descriptor inode type and access
+      mode as integer (i.e. a <type>mode_t</type> value, flags such as <constant>S_IFREG</constant>,
+      <constant>S_IRUSR</constant>, …), the major and minor numbers of the device number of the file system
+      backing the inode of the file descriptor, the inode number, the major and minor numbers of the device
+      number if this refers to a character or block device node, a file system path pointing to the inode,
+      and the file descriptor flags (i.e. <constant>O_RDWR</constant>, <constant>O_RDONLY</constant>,
+      …).</para>
     </refsect2>
 
     <refsect2>
@@ -5108,6 +5171,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s IPCNamespacePath = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s RootImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s MountImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s ExtensionImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s KillMode = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i KillSignal = ...;
@@ -5679,6 +5748,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <!--property IPCNamespacePath is not documented!-->
 
+    <!--property RootImagePolicy is not documented!-->
+
+    <!--property MountImagePolicy is not documented!-->
+
+    <!--property ExtensionImagePolicy is not documented!-->
+
     <!--property KillMode is not documented!-->
 
     <!--property KillSignal is not documented!-->
@@ -6317,6 +6392,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@@ -6963,6 +7044,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s IPCNamespacePath = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s RootImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s MountImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s ExtensionImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s KillMode = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i KillSignal = ...;
@@ -7462,6 +7549,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <!--property IPCNamespacePath is not documented!-->
 
+    <!--property RootImagePolicy is not documented!-->
+
+    <!--property MountImagePolicy is not documented!-->
+
+    <!--property ExtensionImagePolicy is not documented!-->
+
     <!--property KillMode is not documented!-->
 
     <!--property KillSignal is not documented!-->
@@ -8018,6 +8111,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@@ -8791,6 +8890,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s IPCNamespacePath = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s RootImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s MountImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly s ExtensionImagePolicy = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s KillMode = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly i KillSignal = ...;
@@ -9276,6 +9381,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <!--property IPCNamespacePath is not documented!-->
 
+    <!--property RootImagePolicy is not documented!-->
+
+    <!--property MountImagePolicy is not documented!-->
+
+    <!--property ExtensionImagePolicy is not documented!-->
+
     <!--property KillMode is not documented!-->
 
     <!--property KillSignal is not documented!-->
@@ -9818,6 +9929,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
+
+    <variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
index 113ef9fc18a8f0fc038862faa17882de9994f9aa..6cc786acf9e50367fb6a343e5163071728539f67 100644 (file)
       <varname>VERSION_ID=</varname> exists and matches. This ensures ABI/API compatibility between the
       layers and prevents merging of an incompatible image in an overlay.</para>
 
+      <para>In order to identify the extension image itself, the same fields defined below can be added to the
+      <filename>extension-release</filename> file with a <varname>SYSEXT_</varname> prefix (to disambiguate
+      from fields used to match on the base image). E.g.: <varname>SYSEXT_ID=myext</varname>,
+      <varname>SYSEXT_VERSION_ID=1.2.3</varname>.</para>
+
       <para>In the <filename>extension-release.<replaceable>IMAGE</replaceable></filename> filename, the
       <replaceable>IMAGE</replaceable> part must exactly match the file name of the containing image with the
       suffix removed. In case it is not possible to guarantee that an image file name is stable and doesn't
           </para></listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>CONFEXT_LEVEL=</varname></term>
+
+          <listitem><para>Semantically the same as <varname>SYSEXT_LEVEL=</varname> but for confext images.
+          See <filename>/etc/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename>
+          for more information.</para>
+
+          <para>Examples: <literal>CONFEXT_LEVEL=2</literal>, <literal>CONFEXT_LEVEL=15.14</literal>.
+          </para></listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><varname>SYSEXT_SCOPE=</varname></term>
           <listitem><para>Takes a space-separated list of one or more of the strings
           but not to initrd environments.</para></listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>CONFEXT_SCOPE=</varname></term>
+
+          <listitem><para>Semantically the same as <varname>SYSEXT_SCOPE=</varname> but for confext images.</para></listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><varname>PORTABLE_PREFIXES=</varname></term>
           <listitem><para>Takes a space-separated list of one or more valid prefix match strings for the
diff --git a/man/print-unit-path-call-method.c b/man/print-unit-path-call-method.c
new file mode 100644 (file)
index 0000000..f73dd07
--- /dev/null
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: MIT-0 */
+
+/* This is equivalent to:
+ * busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+ *       org.freedesktop.systemd1.Manager GetUnitByPID $$
+ *
+ * Compile with 'cc print-unit-path-call-method.c -lsystemd'
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
+#define DESTINATION "org.freedesktop.systemd1"
+#define PATH        "/org/freedesktop/systemd1"
+#define INTERFACE   "org.freedesktop.systemd1.Manager"
+#define MEMBER      "GetUnitByPID"
+
+static int log_error(int error, const char *message) {
+  errno = -error;
+  fprintf(stderr, "%s: %m\n", message);
+  return error;
+}
+
+int main(int argc, char **argv) {
+  _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+  _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+  _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+  int r;
+
+  r = sd_bus_open_system(&bus);
+  if (r < 0)
+    return log_error(r, "Failed to acquire bus");
+
+  r = sd_bus_call_method(bus, DESTINATION, PATH, INTERFACE, MEMBER, &error, &reply, "u", (unsigned) getpid());
+  if (r < 0)
+    return log_error(r, MEMBER " call failed");
+
+  const char *ans;
+  r = sd_bus_message_read(reply, "o", &ans);
+  if (r < 0)
+    return log_error(r, "Failed to read reply");
+
+  printf("Unit path is \"%s\".\n", ans);
+
+  return 0;
+}
index 44c8271820f79acc3dfd74e3adea3c87fe2f7340..0b89318736aeb5060235d5100be2fd062ac6f726 100644 (file)
@@ -1,20 +1,20 @@
 /* SPDX-License-Identifier: MIT-0 */
 
-#include <errno.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-
-#include <systemd/sd-bus.h>
-#define _cleanup_(f) __attribute__((cleanup(f)))
-
 /* This is equivalent to:
  * busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 \
  *       org.freedesktop.systemd1.Manager GetUnitByPID $$
  *
- * Compile with 'cc -lsystemd print-unit-path.c'
+ * Compile with 'cc print-unit-path.c -lsystemd'
  */
 
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+
+#define _cleanup_(f) __attribute__((cleanup(f)))
 #define DESTINATION "org.freedesktop.systemd1"
 #define PATH        "/org/freedesktop/systemd1"
 #define INTERFACE   "org.freedesktop.systemd1.Manager"
@@ -26,12 +26,16 @@ static int log_error(int error, const char *message) {
   return error;
 }
 
-static int print_unit_path(sd_bus *bus) {
-  _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+int main(int argc, char **argv) {
+  _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
   _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-  _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+  _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
   int r;
 
+  r = sd_bus_open_system(&bus);
+  if (r < 0)
+    return log_error(r, "Failed to acquire bus");
+
   r = sd_bus_message_new_method_call(bus, &m,
                                      DESTINATION, PATH, INTERFACE, MEMBER);
   if (r < 0)
@@ -43,7 +47,7 @@ static int print_unit_path(sd_bus *bus) {
 
   r = sd_bus_call(bus, m, -1, &error, &reply);
   if (r < 0)
-    return log_error(r, "Call failed");
+    return log_error(r, MEMBER " call failed");
 
   const char *ans;
   r = sd_bus_message_read(reply, "o", &ans);
@@ -54,14 +58,3 @@ static int print_unit_path(sd_bus *bus) {
 
   return 0;
 }
-
-int main(int argc, char **argv) {
-  _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-  int r;
-
-  r = sd_bus_open_system(&bus);
-  if (r < 0)
-    return log_error(r, "Failed to acquire bus");
-
-  print_unit_path(bus);
-}
index 39cc55a929f608beff07e031c52f032031d75039..13d2bd9b58a8fe90d14b39c12caa2dcea506ebb4 100644 (file)
@@ -24,6 +24,7 @@ manpages = [
  ['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
  ['hwdb', '7', [], 'ENABLE_HWDB'],
  ['integritytab', '5', [], 'HAVE_LIBCRYPTSETUP'],
+ ['iocost.conf', '5', [], ''],
  ['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'],
  ['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'],
  ['journalctl', '1', [], ''],
@@ -85,6 +86,7 @@ manpages = [
    'SD_BUS_ERROR_INCONSISTENT_MESSAGE',
    'SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED',
    'SD_BUS_ERROR_INVALID_ARGS',
+   'SD_BUS_ERROR_INVALID_FILE_CONTENT',
    'SD_BUS_ERROR_INVALID_SIGNATURE',
    'SD_BUS_ERROR_IO_ERROR',
    'SD_BUS_ERROR_LIMITS_EXCEEDED',
@@ -96,8 +98,11 @@ manpages = [
    'SD_BUS_ERROR_NO_NETWORK',
    'SD_BUS_ERROR_NO_REPLY',
    'SD_BUS_ERROR_NO_SERVER',
+   'SD_BUS_ERROR_OBJECT_PATH_IN_USE',
    'SD_BUS_ERROR_PROPERTY_READ_ONLY',
+   'SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN',
    'SD_BUS_ERROR_SERVICE_UNKNOWN',
+   'SD_BUS_ERROR_TIMED_OUT',
    'SD_BUS_ERROR_TIMEOUT',
    'SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN',
    'SD_BUS_ERROR_UNKNOWN_INTERFACE',
@@ -803,7 +808,8 @@ manpages = [
    'sd_notifyf',
    'sd_pid_notify',
    'sd_pid_notify_with_fds',
-   'sd_pid_notifyf'],
+   'sd_pid_notifyf',
+   'sd_pid_notifyf_with_fds'],
   ''],
  ['sd_path_lookup', '3', ['sd_path_lookup_strv'], ''],
  ['sd_pid_get_owner_uid',
@@ -1039,7 +1045,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',
@@ -1100,6 +1109,7 @@ manpages = [
  ['systemd.environment-generator', '7', [], 'ENABLE_ENVIRONMENT_D'],
  ['systemd.exec', '5', [], ''],
  ['systemd.generator', '7', [], ''],
+ ['systemd.image-policy', '7', [], ''],
  ['systemd.journal-fields', '7', [], ''],
  ['systemd.kill', '5', [], ''],
  ['systemd.link', '5', [], ''],
index f3b1515c78db7faa81916f10b5c66aab92d85358..dc9d9fc63b560589a8475051d3f3257993867247 100644 (file)
     <refname>SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN</refname>
     <refname>SD_BUS_ERROR_INVALID_SIGNATURE</refname>
     <refname>SD_BUS_ERROR_INCONSISTENT_MESSAGE</refname>
+    <refname>SD_BUS_ERROR_TIMED_OUT</refname>
     <refname>SD_BUS_ERROR_MATCH_RULE_NOT_FOUND</refname>
     <refname>SD_BUS_ERROR_MATCH_RULE_INVALID</refname>
     <refname>SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED</refname>
+    <refname>SD_BUS_ERROR_INVALID_FILE_CONTENT</refname>
+    <refname>SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN</refname>
+    <refname>SD_BUS_ERROR_OBJECT_PATH_IN_USE</refname>
 
     <refpurpose>Standard D-Bus error names</refpurpose>
   </refnamediv>
     <funcsynopsis>
       <funcsynopsisinfo>#include &lt;systemd/sd-bus.h&gt;</funcsynopsisinfo>
 
-<funcsynopsisinfo>#define SD_BUS_ERROR_FAILED                     "org.freedesktop.DBus.Error.Failed"
-#define SD_BUS_ERROR_NO_MEMORY                  "org.freedesktop.DBus.Error.NoMemory"
-#define SD_BUS_ERROR_SERVICE_UNKNOWN            "org.freedesktop.DBus.Error.ServiceUnknown"
-#define SD_BUS_ERROR_NAME_HAS_NO_OWNER          "org.freedesktop.DBus.Error.NameHasNoOwner"
-#define SD_BUS_ERROR_NO_REPLY                   "org.freedesktop.DBus.Error.NoReply"
-#define SD_BUS_ERROR_IO_ERROR                   "org.freedesktop.DBus.Error.IOError"
-#define SD_BUS_ERROR_BAD_ADDRESS                "org.freedesktop.DBus.Error.BadAddress"
-#define SD_BUS_ERROR_NOT_SUPPORTED              "org.freedesktop.DBus.Error.NotSupported"
-#define SD_BUS_ERROR_LIMITS_EXCEEDED            "org.freedesktop.DBus.Error.LimitsExceeded"
-#define SD_BUS_ERROR_ACCESS_DENIED              "org.freedesktop.DBus.Error.AccessDenied"
-#define SD_BUS_ERROR_AUTH_FAILED                "org.freedesktop.DBus.Error.AuthFailed"
-#define SD_BUS_ERROR_NO_SERVER                  "org.freedesktop.DBus.Error.NoServer"
-#define SD_BUS_ERROR_TIMEOUT                    "org.freedesktop.DBus.Error.Timeout"
-#define SD_BUS_ERROR_NO_NETWORK                 "org.freedesktop.DBus.Error.NoNetwork"
-#define SD_BUS_ERROR_ADDRESS_IN_USE             "org.freedesktop.DBus.Error.AddressInUse"
-#define SD_BUS_ERROR_DISCONNECTED               "org.freedesktop.DBus.Error.Disconnected"
-#define SD_BUS_ERROR_INVALID_ARGS               "org.freedesktop.DBus.Error.InvalidArgs"
-#define SD_BUS_ERROR_FILE_NOT_FOUND             "org.freedesktop.DBus.Error.FileNotFound"
-#define SD_BUS_ERROR_FILE_EXISTS                "org.freedesktop.DBus.Error.FileExists"
-#define SD_BUS_ERROR_UNKNOWN_METHOD             "org.freedesktop.DBus.Error.UnknownMethod"
-#define SD_BUS_ERROR_UNKNOWN_OBJECT             "org.freedesktop.DBus.Error.UnknownObject"
-#define SD_BUS_ERROR_UNKNOWN_INTERFACE          "org.freedesktop.DBus.Error.UnknownInterface"
-#define SD_BUS_ERROR_UNKNOWN_PROPERTY           "org.freedesktop.DBus.Error.UnknownProperty"
-#define SD_BUS_ERROR_PROPERTY_READ_ONLY         "org.freedesktop.DBus.Error.PropertyReadOnly"
-#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN    "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
-#define SD_BUS_ERROR_INVALID_SIGNATURE          "org.freedesktop.DBus.Error.InvalidSignature"
-#define SD_BUS_ERROR_INCONSISTENT_MESSAGE       "org.freedesktop.DBus.Error.InconsistentMessage"
-#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND       "org.freedesktop.DBus.Error.MatchRuleNotFound"
-#define SD_BUS_ERROR_MATCH_RULE_INVALID         "org.freedesktop.DBus.Error.MatchRuleInvalid"
+      <funcsynopsisinfo>
+#define SD_BUS_ERROR_FAILED                  "org.freedesktop.DBus.Error.Failed"
+#define SD_BUS_ERROR_NO_MEMORY               "org.freedesktop.DBus.Error.NoMemory"
+#define SD_BUS_ERROR_SERVICE_UNKNOWN         "org.freedesktop.DBus.Error.ServiceUnknown"
+#define SD_BUS_ERROR_NAME_HAS_NO_OWNER       "org.freedesktop.DBus.Error.NameHasNoOwner"
+#define SD_BUS_ERROR_NO_REPLY                "org.freedesktop.DBus.Error.NoReply"
+#define SD_BUS_ERROR_IO_ERROR                "org.freedesktop.DBus.Error.IOError"
+#define SD_BUS_ERROR_BAD_ADDRESS             "org.freedesktop.DBus.Error.BadAddress"
+#define SD_BUS_ERROR_NOT_SUPPORTED           "org.freedesktop.DBus.Error.NotSupported"
+#define SD_BUS_ERROR_LIMITS_EXCEEDED         "org.freedesktop.DBus.Error.LimitsExceeded"
+#define SD_BUS_ERROR_ACCESS_DENIED           "org.freedesktop.DBus.Error.AccessDenied"
+#define SD_BUS_ERROR_AUTH_FAILED             "org.freedesktop.DBus.Error.AuthFailed"
+#define SD_BUS_ERROR_NO_SERVER               "org.freedesktop.DBus.Error.NoServer"
+#define SD_BUS_ERROR_TIMEOUT                 "org.freedesktop.DBus.Error.Timeout"
+#define SD_BUS_ERROR_NO_NETWORK              "org.freedesktop.DBus.Error.NoNetwork"
+#define SD_BUS_ERROR_ADDRESS_IN_USE          "org.freedesktop.DBus.Error.AddressInUse"
+#define SD_BUS_ERROR_DISCONNECTED            "org.freedesktop.DBus.Error.Disconnected"
+#define SD_BUS_ERROR_INVALID_ARGS            "org.freedesktop.DBus.Error.InvalidArgs"
+#define SD_BUS_ERROR_FILE_NOT_FOUND          "org.freedesktop.DBus.Error.FileNotFound"
+#define SD_BUS_ERROR_FILE_EXISTS             "org.freedesktop.DBus.Error.FileExists"
+#define SD_BUS_ERROR_UNKNOWN_METHOD          "org.freedesktop.DBus.Error.UnknownMethod"
+#define SD_BUS_ERROR_UNKNOWN_OBJECT          "org.freedesktop.DBus.Error.UnknownObject"
+#define SD_BUS_ERROR_UNKNOWN_INTERFACE       "org.freedesktop.DBus.Error.UnknownInterface"
+#define SD_BUS_ERROR_UNKNOWN_PROPERTY        "org.freedesktop.DBus.Error.UnknownProperty"
+#define SD_BUS_ERROR_PROPERTY_READ_ONLY      "org.freedesktop.DBus.Error.PropertyReadOnly"
+#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
+#define SD_BUS_ERROR_INVALID_SIGNATURE       "org.freedesktop.DBus.Error.InvalidSignature"
+#define SD_BUS_ERROR_INCONSISTENT_MESSAGE    "org.freedesktop.DBus.Error.InconsistentMessage"
+#define SD_BUS_ERROR_TIMED_OUT               "org.freedesktop.DBus.Error.TimedOut"
+#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND    "org.freedesktop.DBus.Error.MatchRuleNotFound"
+#define SD_BUS_ERROR_MATCH_RULE_INVALID      "org.freedesktop.DBus.Error.MatchRuleInvalid"
 #define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \
-                                                "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"</funcsynopsisinfo>
-
+                                             "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"
+#define SD_BUS_ERROR_INVALID_FILE_CONTENT    "org.freedesktop.DBus.Error.InvalidFileContent"
+#define SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN \
+                                             "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"
+#define SD_BUS_ERROR_OBJECT_PATH_IN_USE      "org.freedesktop.DBus.Error.ObjectPathInUse"
+      </funcsynopsisinfo>
     </funcsynopsis>
   </refsynopsisdiv>
 
index c6beb1dcd1bbab17fd40e739eebca87a4234e802..541fa7b06da2b3b4d7840572c1cff25ad7756489 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 991f3a8064ff8040fb1a4736b19de9edf36795da..5a9e67abb0b46b3e149629321f37fe243ed04286 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index df2704ac99116040010d199990cea252486e5ed4..6c66fd3001883fd9460a86006d13dc41e5173380 100644 (file)
@@ -90,7 +90,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index bb34d4a7b920a630c63cc0a7a3f22b8010a17dc9..ba3e58c25dcd6f1fba7151e01ef056cb8730b767 100644 (file)
@@ -94,7 +94,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index 762ea11c01067531c11e309005de21edaa930b6d..7b525558d70c26df4be3aebcde929116c8f8baa1 100644 (file)
 
   <xi:include href="libsystemd-pkgconfig.xml" />
 
+  <refsect1>
+    <title>Examples</title>
+
+    <example>
+      <title>Make a call to a D-Bus method that takes a single parameter</title>
+
+      <programlisting><xi:include href="print-unit-path-call-method.c" parse="text" /></programlisting>
+      <para>This defines a minimally useful program that will open a connection to the bus, call a method,
+      wait for the reply, and finally extract and print the answer. It does error handling and proper
+      memory management.</para>
+    </example>
+  </refsect1>
+
   <refsect1>
     <title>See Also</title>
 
index 95427bd7f94a8f1eba5955ac5e844ad7275445c3..dab1d77336692a5c5bdc09c8bac799c14a8c4a13 100644 (file)
@@ -96,7 +96,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para>
           </listitem>
         </varlistentry>
       </variablelist>
index 48d9c9a1082f6bbcbb5b11d2aa9e5e6ece0588b5..8be4254be116f1b31fbd1a5cf6d8d09d5bbc361e 100644 (file)
     <title>Description</title>
 
     <para><function>sd_bus_default()</function> acquires a bus
-    connection object to the user bus when invoked in user context, or
-    to the system bus otherwise. The connection object is associated
-    with the calling thread. Each time the function is invoked from
-    the same thread, the same object is returned, but its reference
-    count is increased by one, as long as at least one reference is
-    kept. When the last reference to the connection is dropped (using
-    the
+    connection object to the user bus when invoked from within a user
+    slice (any session under <literal>user-*.slice</literal>, e.g.:
+    <literal>user@1000.service</literal>), or to the system bus
+    otherwise. The connection object is associated with the calling
+    thread. Each time the function is invoked from the same thread,
+    the same object is returned, but its reference count is increased
+    by one, as long as at least one reference is kept. When the last
+    reference to the connection is dropped (using the
     <citerefentry><refentrytitle>sd_bus_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>
     call), the connection is terminated. Note that the connection is
     not automatically terminated when the associated thread ends. It
index 52d08b7a92369571b14a9c822f257baefa301770..ec2a7976fd0cc0cb723f427f02bb65b85f7b1a5b 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 9bdf4981950e7b8059300cf8e0379b3b2e017821..601edafd6a524fdaf091208dda2b733c77bb146c 100644 (file)
@@ -67,7 +67,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index c9978d8ba760709792ca34641c02f53b78a19b03..3b0f6148f88da4c325d298132659aacc37984c2d 100644 (file)
@@ -78,7 +78,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection was created in a different process.</para></listitem>
+          <listitem><para>The bus connection was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
       </variablelist>
index 6f0541d97ad61d5937b324c25fb1a7b863478a6a..5444eeddb14d536616caed8cb76c11012cd2b28a 100644 (file)
@@ -95,7 +95,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 8249485f254efe8d7949bb9212e2a4d81de0ae5a..cd702e4063c879df186f10d7b0ef84c5e774d9b5 100644 (file)
@@ -72,7 +72,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 621ed272bb8eba1b990f4f28551c58b90d4d29f1..8f6b3bcba1a7a50202d55e0a6b9bc062656137aa 100644 (file)
@@ -83,7 +83,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index d8f7e60b7dc220be7bfac13c4f76a004eff3f5f1..998c2866637811d3aa98f538029874b0283df305 100644 (file)
@@ -79,7 +79,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 688f43227b190b99e79aed52262e24319a95e220..0b9164e9bf98ea77c76253ebdccf9921b5b0d73b 100644 (file)
@@ -91,7 +91,7 @@
     with <function>sd_bus_message_enter_container()</function>. It behaves mostly the same as
     <function>sd_bus_message_close_container()</function>. Note that
     <function>sd_bus_message_exit_container()</function> may only be called after iterating through all
-    members of the container, i.e. reading or skipping them. Use
+    members of the container, i.e. reading or skipping over them. Use
     <citerefentry><refentrytitle>sd_bus_message_skip</refentrytitle><manvolnum>3</manvolnum></citerefentry>
     to skip over fields of a container in order to be able to exit the container with
     <function>sd_bus_message_exit_container()</function> without reading all members.</para>
           <constant>NULL</constant> or <parameter>type</parameter> is invalid.</para></listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><constant>-EBADMSG</constant></term>
+
+          <listitem><para>Message <parameter>m</parameter> has invalid structure.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term><constant>-ENXIO</constant></term>
+
+          <listitem><para>Message <parameter>m</parameter> does not have a container of type
+          <parameter>type</parameter> at the current position, or the contents do not match
+          <parameter>contents</parameter>.</para></listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><constant>-EPERM</constant></term>
 
index a4893b62e79facc3c0af62bdff03920fcb32daab..22b6817de34baaf73a2375336eeeff7c9f3d02fc 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index 47cc33670641016e56271056397e42d60239e0a2..b8fc7b399117acdd66e142fc4475c93d2008bbdb 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus of <parameter>m</parameter> was created in a different process.
+          <listitem><para>The bus of <parameter>m</parameter> was created in a different process, library or module instance.
           </para></listitem>
         </varlistentry>
 
index cf3bbae0b552c56d49bdf1e01e896db2465890ea..5ee34a934aea01ff020968e17d79cd317144336a 100644 (file)
@@ -84,7 +84,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection was created in a different process.</para>
+          <listitem><para>The bus connection was created in a different process, library or module instance.</para>
           </listitem>
         </varlistentry>
       </variablelist>
index b2dfcf0cf9cc15010d1143a303666ca8554c1641..09dcb5978a1b4aa2b9c346caa03c5a7683704ac3 100644 (file)
@@ -86,7 +86,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index 0c38c16128ff4bbf981a76e24b95961fc28fdcb2..783a0b7922b5d5ecc634a4a659f846aad13070f1 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus was created in a different process.</para></listitem>
+          <listitem><para>The bus was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index a694aef4f24e7181c05181a1d5c41470d2e48c83..aee5adffb34d3fc4ac9ec0948da969e8c49394b8 100644 (file)
@@ -92,7 +92,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection was created in a different process.</para></listitem>
+          <listitem><para>The bus connection was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index c6e07acc979737344c4039e307ed5ea1c7d7a307..2aedbfc01a2e8fd3ece4e1d1599c0c48d03264fb 100644 (file)
@@ -75,7 +75,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index e2cd74da90caa6f4901a3952110f2fd651b3c7f5..7c4b087628b6b02defcad9a81238fbda8d0f04e5 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 20f6f53a2c8a0a8235519c3db147d25f014ecebe..123cef48a678312fa412bd4ec169d21e9bd97fe9 100644 (file)
@@ -92,7 +92,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index dd3a9500cf3285c6e657b77d0b69f5dc919ac67a..3309147847e1f9cfb70ac08ae7e233c118b88cd3 100644 (file)
@@ -88,7 +88,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The bus connection has been created in a different process.</para></listitem>
+          <listitem><para>The bus connection has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index fec754d2ddec7847b9681f9f5eff63dc5f703931..31b9d7634026cba1807654487688a89b22fb3298 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index e56e16a0328d8ae6d247c28690b8f5c07e4578aa..3bac5d53d424baeea4f11a18686dc2ec59ae7368 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
       </variablelist>
index c7af7bdcd00e16504c4a77c5d4e3646c7f41e74c..1f31907b0db1be02c257acde7d34951565129c45 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index 383a58a074e354334d622eed471dd0193c4bbdbf..4201c68f0d0db624df35676821f8f13014f4ef1d 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 8df69fde3bb2be92e6a2ec0b29875a39d024f6b2..08a27d15c1b6f481bf53df5110d4f444c5a1dbf9 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 3e8536e96112bdcd60a6c1ff22cdd49860050ec3..29a782397404d3d68f6e1153950feb0f84b1b963 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 19f112b0f6033ca21ce12572730928ea72a61b1a..6031fee1b38b0cdd71a4cec3b4d52aa741d1ccb1 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index e13cbe18ab9d499942c47d3fac94931edd855fa4..343dd9066e148d30cf2e16b0165525ed95df489d 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop was created in a different process.</para></listitem>
+          <listitem><para>The event loop was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index a3b11e41ee3cd0af78ba3126b00ba0a429a293f0..698691fa0d200f96bf6d8c42a3b3ec73b631b266 100644 (file)
@@ -77,7 +77,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
       </variablelist>
index 789b9e028847ac6c4b3f9e5fa52bf6eb950cb692..70e980ab7b3ea0646287d89246d3bfc14567da3a 100644 (file)
@@ -91,7 +91,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop object was created in a different process.</para></listitem>
+          <listitem><para>The event loop object was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
       </variablelist>
     </refsect2>
index 81c51b7060393416f65a6f4bdf2fbf1d06c0aee6..c30f5eb4c231d2c073df52ed83af6e50cd2ab889 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index e5e675beec9ec761c73be93dfab4048e8b9d70d8..cdf49a54f9c58511e53e672da517d82f6288a6fd 100644 (file)
@@ -72,7 +72,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 28d647c7bcdea49f72e234871e793d15b4fa506f..954a1366cf6dfda90bbf5cf4a762058242e16910 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 5906930b3c3be64cc4f6b16c1df042115eeba570..7521ba6420c0e57bdea3114b76a5c6bb953e7f23 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index bea3e71914e571f71be5a0befb4e8389d6d14f46..9ad369817f87fa795312d2bf6201aa58ccb25c42 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index 5f13fc101d0d923465139a2f2b32482b033121f6..cab8667f0fe5c5e0669969b05f3d6350c38756b5 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index 7f3ed86a6585ff0bfe9a4b58f08485ef28379a56..db288094b67169006e45d99e6ddb5faed3760bff 100644 (file)
@@ -89,7 +89,7 @@
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index d52c55b3db950c42bb563a9d00cb1fb8d7f3a68d..e7526a9ff5aa51083a50f234c18879be51827122 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index 2616c12e3fc7aed942fef68d89439d0e1348f714..efe05b11d26643aed3f992b49f2918f72c7581c4 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index b8e6380ddd39ebc6e1170d9b4cb5bcdf3979d433..07ac18b7917fb1d173d9091caab37590ac5f0845 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 25e21b9d5cfbf52b00538343f171c65b42d2db61..0dc7105cf9cde7551630259476aed1dd1765fed3 100644 (file)
         <varlistentry>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The event loop has been created in a different process.</para></listitem>
+          <listitem><para>The event loop has been created in a different process, library or module instance.</para></listitem>
 
         </varlistentry>
 
index 2955cd264b66e551c01e5710cbfba039b71fb4d7..9a90f95d1ab64938db25815dc9c4d19095475e0d 100644 (file)
         <varlistentry id='ECHILD'>
           <term><constant>-ECHILD</constant></term>
 
-          <listitem><para>The journal object was created in a different process.</para></listitem>
+          <listitem><para>The journal object was created in a different process, library or module instance.</para></listitem>
         </varlistentry>
 
         <varlistentry id='EADDRNOTAVAIL'>
index 8a0911286570d2c8bb463cdc61388873da17fc1b..b82d145860abbe23fd6667f54c180203a8aa437d 100644 (file)
@@ -22,6 +22,7 @@
     <refname>sd_pid_notify</refname>
     <refname>sd_pid_notifyf</refname>
     <refname>sd_pid_notify_with_fds</refname>
+    <refname>sd_pid_notifyf_with_fds</refname>
     <refname>sd_notify_barrier</refname>
     <refpurpose>Notify service manager about start-up completion and other service status changes</refpurpose>
   </refnamediv>
         <paramdef>unsigned <parameter>n_fds</parameter></paramdef>
       </funcprototype>
 
+      <funcprototype>
+        <funcdef>int <function>sd_pid_notifyf_with_fds</function></funcdef>
+        <paramdef>pid_t <parameter>pid</parameter></paramdef>
+        <paramdef>int <parameter>unset_environment</parameter></paramdef>
+        <paramdef>const int *<parameter>fds</parameter></paramdef>
+        <paramdef>size_t <parameter>n_fds</parameter></paramdef>
+        <paramdef>const char *<parameter>format</parameter></paramdef>
+        <paramdef>…</paramdef>
+      </funcprototype>
+
       <funcprototype>
         <funcdef>int <function>sd_notify_barrier</function></funcdef>
         <paramdef>int <parameter>unset_environment</parameter></paramdef>
 
   <refsect1>
     <title>Description</title>
-    <para><function>sd_notify()</function> may be called by a service
-    to notify the service manager about state changes. It can be used
-    to send arbitrary information, encoded in an
-    environment-block-like string. Most importantly, it can be used for
-    start-up completion notification.</para>
-
-    <para>If the <parameter>unset_environment</parameter> parameter is
-    non-zero, <function>sd_notify()</function> will unset the
-    <varname>$NOTIFY_SOCKET</varname> environment variable before
-    returning (regardless of whether the function call itself
-    succeeded or not). Further calls to
-    <function>sd_notify()</function> will then fail, but the variable
-    is no longer inherited by child processes.</para>
-
-    <para>The <parameter>state</parameter> parameter should contain a
-    newline-separated list of variable assignments, similar in style
-    to an environment block. A trailing newline is implied if none is
-    specified. The string may contain any kind of variable
-    assignments, but the following shall be considered
+
+    <para><function>sd_notify()</function> may be called by a service to notify the service manager about
+    state changes. It can be used to send arbitrary information, encoded in an environment-block-like
+    string. Most importantly, it can be used for start-up completion notification.</para>
+
+    <para>If the <parameter>unset_environment</parameter> parameter is non-zero,
+    <function>sd_notify()</function> will unset the <varname>$NOTIFY_SOCKET</varname> environment variable
+    before returning (regardless of whether the function call itself succeeded or not). Further calls to
+    <function>sd_notify()</function> will then fail, but the variable is no longer inherited by child
+    processes.</para>
+
+    <para>The <parameter>state</parameter> parameter should contain a newline-separated list of variable
+    assignments, similar in style to an environment block. A trailing newline is implied if none is
+    specified. The string may contain any kind of variable assignments, but the following shall be considered
     well-known:</para>
 
     <variablelist>
       <varlistentry>
         <term>STOPPING=1</term>
 
-        <listitem><para>Tells the service manager that the service is
-        beginning its shutdown. This is useful to allow the service
-        manager to track the service's internal state, and present it
-        to the user.</para></listitem>
+        <listitem><para>Tells the service manager that the service is beginning its shutdown. This is useful
+        to allow the service manager to track the service's internal state, and present it to the
+        user.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>STATUS=…</term>
 
-        <listitem><para>Passes a single-line UTF-8 status string back
-        to the service manager that describes the service state. This
-        is free-form and can be used for various purposes: general
-        state feedback, fsck-like programs could pass completion
-        percentages and failing programs could pass a human-readable
-        error message. Example: <literal>STATUS=Completed 66% of file
-        system check…</literal></para></listitem>
+        <listitem><para>Passes a single-line UTF-8 status string back to the service manager that describes
+        the service state. This is free-form and can be used for various purposes: general state feedback,
+        fsck-like programs could pass completion percentages and failing programs could pass a human-readable
+        error message. Example: <literal>STATUS=Completed 66% of file system
+        check…</literal></para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>NOTIFYACCESS=…</term>
+
+        <listitem><para>Reset the access to the service status notification socket during runtime, overriding
+        <varname>NotifyAccess=</varname> setting in the service unit file. See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details, specifically <literal>NotifyAccess=</literal> for a list of accepted
+        values.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>ERRNO=…</term>
 
-        <listitem><para>If a service fails, the errno-style error
-        code, formatted as string. Example: <literal>ERRNO=2</literal>
-        for ENOENT.</para></listitem>
+        <listitem><para>If a service fails, the errno-style error code, formatted as string. Example:
+        <literal>ERRNO=2</literal> for ENOENT.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>BUSERROR=…</term>
 
-        <listitem><para>If a service fails, the D-Bus error-style
-        error code. Example:
+        <listitem><para>If a service fails, the D-Bus error-style error code. Example:
         <literal>BUSERROR=org.freedesktop.DBus.Error.TimedOut</literal></para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term>EXIT_STATUS=…</term>
+
+        <listitem><para>If a service exits, the return value of its <function>main()</function> function.
+        </para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term>MAINPID=…</term>
 
-        <listitem><para>The main process ID (PID) of the service, in
-        case the service manager did not fork off the process itself.
-        Example: <literal>MAINPID=4711</literal></para></listitem>
+        <listitem><para>The main process ID (PID) of the service, in case the service manager did not fork
+        off the process itself.  Example: <literal>MAINPID=4711</literal></para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>WATCHDOG=1</term>
 
-        <listitem><para>Tells the service manager to update the
-        watchdog timestamp. This is the keep-alive ping that services
-        need to issue in regular intervals if
-        <varname>WatchdogSec=</varname> is enabled for it. See
+        <listitem><para>Tells the service manager to update the watchdog timestamp. This is the keep-alive
+        ping that services need to issue in regular intervals if <varname>WatchdogSec=</varname> is enabled
+        for it. See
         <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
         for information how to enable this functionality and
         <citerefentry><refentrytitle>sd_watchdog_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>
-        for the details of how the service can check whether the
-        watchdog is enabled. </para></listitem>
+        for the details of how the service can check whether the watchdog is enabled. </para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>WATCHDOG=trigger</term>
 
-        <listitem><para>Tells the service manager that the service detected an internal error that should be handled by
-        the configured watchdog options. This will trigger the same behaviour as if <varname>WatchdogSec=</varname> is
-        enabled and the service did not send <literal>WATCHDOG=1</literal> in time. Note that
-        <varname>WatchdogSec=</varname> does not need to be enabled for <literal>WATCHDOG=trigger</literal> to trigger
-        the watchdog action. See
-        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
-        information about the watchdog behavior. </para></listitem>
+        <listitem><para>Tells the service manager that the service detected an internal error that should be
+        handled by the configured watchdog options. This will trigger the same behaviour as if
+        <varname>WatchdogSec=</varname> is enabled and the service did not send <literal>WATCHDOG=1</literal>
+        in time. Note that <varname>WatchdogSec=</varname> does not need to be enabled for
+        <literal>WATCHDOG=trigger</literal> to trigger the watchdog action. See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for information about the watchdog behavior. </para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>WATCHDOG_USEC=…</term>
 
-        <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.
-        Notice that this is not available when using <function>sd_event_set_watchdog()</function>
-        or <function>sd_watchdog_enabled()</function>.
-        Example : <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
+        <listitem><para>Reset <varname>watchdog_usec</varname> value during runtime.  Notice that this is not
+        available when using <function>sd_event_set_watchdog()</function> or
+        <function>sd_watchdog_enabled()</function>.  Example :
+        <literal>WATCHDOG_USEC=20000000</literal></para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>EXTEND_TIMEOUT_USEC=…</term>
 
         <listitem><para>Tells the service manager to extend the startup, runtime or shutdown service timeout
-        corresponding the current state. The value specified is a time in microseconds during which the service must
-        send a new message. A service timeout will occur if the message isn't received, but only if the runtime of the
-        current state is beyond the original maximum times of <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>,
-        and <varname>TimeoutStopSec=</varname>.
-        See <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        corresponding the current state. The value specified is a time in microseconds during which the
+        service must send a new message. A service timeout will occur if the message isn't received, but only
+        if the runtime of the current state is beyond the original maximum times of
+        <varname>TimeoutStartSec=</varname>, <varname>RuntimeMaxSec=</varname>, and
+        <varname>TimeoutStopSec=</varname>.  See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
         for effects on the service timeouts.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDSTORE=1</term>
 
-        <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this way will
-        be maintained per-service by the service manager and will later be handed back using the usual file descriptor
-        passing logic at the next invocation of the service (e.g. when it is restarted), see
-        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.  This is
-        useful for implementing services that can restart after an explicit request or a crash without losing
-        state. Any open sockets and other file descriptors which should not be closed during the restart may be stored
-        this way. Application state can either be serialized to a file in <filename>/run/</filename>, or better, stored
-        in a <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry> memory
-        file descriptor. Note that the service manager will accept messages for a service only if its
+        <listitem><para>Stores additional file descriptors in the service manager. File descriptors sent this
+        way will be maintained per-service by the service manager and will later be handed back using the
+        usual file descriptor passing logic at the next invocation of the service (e.g. when it is
+        restarted), see
+        <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+        This is useful for implementing services that can restart after an explicit request or a crash
+        without losing state. Any open sockets and other file descriptors which should not be closed during
+        the restart may be stored this way. Application state can either be serialized to a file in
+        <filename>/run/</filename>, or better, stored in a
+        <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+        memory file descriptor. Note that the service manager will accept messages for a service only if its
         <varname>FileDescriptorStoreMax=</varname> setting is non-zero (defaults to zero, see
         <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>). If
         <varname>FDPOLL=0</varname> is not set and the file descriptors sent are pollable (see
-        <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then any
-        <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in their
-        automatic removal from the store. Multiple arrays of file descriptors may be sent in separate messages, in
-        which case the arrays are combined. Note that the service manager removes duplicate (pointing to the same
-        object) file descriptors before passing them to the service. When a service is stopped, its file descriptor
-        store is discarded and all file descriptors in it are closed. Use <function>sd_pid_notify_with_fds()</function>
-        to send messages with <literal>FDSTORE=1</literal>, see below.</para></listitem>
+        <citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), then
+        any <constant>EPOLLHUP</constant> or <constant>EPOLLERR</constant> event seen on them will result in
+        their automatic removal from the store. Multiple arrays of file descriptors may be sent in separate
+        messages, in which case the arrays are combined. Note that the service manager removes duplicate
+        (pointing to the same object) file descriptors before passing them to the service. When a service is
+        stopped, its file descriptor store is discarded and all file descriptors in it are closed. Use
+        <function>sd_pid_notify_with_fds()</function> to send messages with <literal>FDSTORE=1</literal>, see
+        below. The service manager will set the <varname>$FDSTORE</varname> environment variable for services
+        that have the file descriptor store enabled.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDSTOREREMOVE=1</term>
 
-        <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be combined with
-        <varname>FDNAME=</varname> to specify the name of the file descriptors to remove.</para></listitem>
+        <listitem><para>Removes file descriptors from the file descriptor store. This field needs to be
+        combined with <varname>FDNAME=</varname> to specify the name of the file descriptors to
+        remove.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDNAME=…</term>
 
-        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the submitted
-        file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for the file
-        descriptors to remove. This name is passed to the service during activation, and may be queried using
+        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, specifies a name for the
+        submitted file descriptors. When used with <varname>FDSTOREREMOVE=1</varname>, specifies the name for
+        the file descriptors to remove. This name is passed to the service during activation, and may be
+        queried using
         <citerefentry><refentrytitle>sd_listen_fds_with_names</refentrytitle><manvolnum>3</manvolnum></citerefentry>. File
         descriptors submitted without this field set, will implicitly get the name <literal>stored</literal>
-        assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be assigned to
-        all of them. In order to assign different names to submitted file descriptors, submit them in separate
-        invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist of arbitrary ASCII
-        characters except control characters or <literal>:</literal>. It may not be longer than 255 characters. If a
-        submitted name does not follow these restrictions, it is ignored.</para></listitem>
+        assigned. Note that, if multiple file descriptors are submitted at once, the specified name will be
+        assigned to all of them. In order to assign different names to submitted file descriptors, submit
+        them in separate invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist
+        of arbitrary ASCII characters except control characters or <literal>:</literal>. It may not be longer
+        than 255 characters. If a submitted name does not follow these restrictions, it is
+        ignored.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>FDPOLL=0</term>
 
-        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the stored
-        file descriptors regardless of whether or not they are pollable. As this option disables automatic cleanup
-        of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to ensure proper manual cleanup.
-        Use of this option is not generally recommended except for when automatic cleanup has unwanted behavior such
-        as prematurely discarding file descriptors from the store.</para></listitem>
+        <listitem><para>When used in combination with <varname>FDSTORE=1</varname>, disables polling of the
+        stored file descriptors regardless of whether or not they are pollable. As this option disables
+        automatic cleanup of the stored file descriptors on EPOLLERR and EPOLLHUP, care must be taken to
+        ensure proper manual cleanup.  Use of this option is not generally recommended except for when
+        automatic cleanup has unwanted behavior such as prematurely discarding file descriptors from the
+        store.</para></listitem>
       </varlistentry>
 
       <varlistentry>
       </varlistentry>
     </variablelist>
 
-    <para>It is recommended to prefix variable names that are not
-    listed above with <varname>X_</varname> to avoid namespace
-    clashes.</para>
-
-    <para>Note that systemd will accept status data sent from a
-    service only if the <varname>NotifyAccess=</varname> option is
-    correctly set in the service definition file. See
-    <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-    for details.</para>
-
-    <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only if either
-    the sending process is still around at the time PID 1 processes the message, or if the sending process is
-    explicitly runtime-tracked by the service manager. The latter is the case if the service manager originally forked
-    off the process, i.e. on all processes that match <varname>NotifyAccess=</varname><option>main</option> or
-    <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit sends an
-    <function>sd_notify()</function> message and immediately exits, the service manager might not be able to properly
-    attribute the message to the unit, and thus will ignore it, even if
+    <para>It is recommended to prefix variable names that are not listed above with <varname>X_</varname> to
+    avoid namespace clashes.</para>
+
+    <para>Note that systemd will accept status data sent from a service only if the
+    <varname>NotifyAccess=</varname> option is correctly set in the service definition file. See
+    <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+    details.</para>
+
+    <para>Note that <function>sd_notify()</function> notifications may be attributed to units correctly only
+    if either the sending process is still around at the time PID 1 processes the message, or if the sending
+    process is explicitly runtime-tracked by the service manager. The latter is the case if the service
+    manager originally forked off the process, i.e. on all processes that match
+    <varname>NotifyAccess=</varname><option>main</option> or
+    <varname>NotifyAccess=</varname><option>exec</option>. Conversely, if an auxiliary process of the unit
+    sends an <function>sd_notify()</function> message and immediately exits, the service manager might not be
+    able to properly attribute the message to the unit, and thus will ignore it, even if
     <varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
 
-    <para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
-    to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as a synchronization point
-    and ensures all notifications sent before this call have been picked up by the service manager when it returns
-    successfully. Use of <function>sd_notify_barrier()</function> is needed for clients which are not invoked by the
-    service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
-    unit.</para>
-
-    <para><function>sd_notifyf()</function> is similar to
-    <function>sd_notify()</function> but takes a
-    <function>printf()</function>-like format string plus
-    arguments.</para>
-
-    <para><function>sd_pid_notify()</function> and
-    <function>sd_pid_notifyf()</function> are similar to
-    <function>sd_notify()</function> and
-    <function>sd_notifyf()</function> but take a process ID (PID) to
-    use as originating PID for the message as first argument. This is
-    useful to send notification messages on behalf of other processes,
-    provided the appropriate privileges are available. If the PID
-    argument is specified as 0, the process ID of the calling process
-    is used, in which case the calls are fully equivalent to
-    <function>sd_notify()</function> and
-    <function>sd_notifyf()</function>.</para>
-
-    <para><function>sd_pid_notify_with_fds()</function> is similar to
-    <function>sd_pid_notify()</function> but takes an additional array
-    of file descriptors. These file descriptors are sent along the
-    notification message to the service manager. This is particularly
-    useful for sending <literal>FDSTORE=1</literal> messages, as
-    described above. The additional arguments are a pointer to the
-    file descriptor array plus the number of file descriptors in the
-    array. If the number of file descriptors is passed as 0, the call
-    is fully equivalent to <function>sd_pid_notify()</function>, i.e.
-    no file descriptors are passed. Note that sending file descriptors
-    to the service manager on messages that do not expect them (i.e.
-    without <literal>FDSTORE=1</literal>) they are immediately closed
-    on reception.</para>
-
-    <para><function>sd_notify_barrier()</function> allows the caller to
-    synchronize against reception of previously sent notification messages
-    and uses the <varname>BARRIER=1</varname> command. It takes a relative
-    <varname>timeout</varname> value in microseconds which is passed to
+    <para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of
+    notifications to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as
+    a synchronization point and ensures all notifications sent before this call have been picked up by the
+    service manager when it returns successfully. Use of <function>sd_notify_barrier()</function> is needed
+    for clients which are not invoked by the service manager, otherwise this synchronization mechanism is
+    unnecessary for attribution of notifications to the unit.</para>
+
+    <para><function>sd_notifyf()</function> is similar to <function>sd_notify()</function> but takes a
+    <function>printf()</function>-like format string plus arguments.</para>
+
+    <para><function>sd_pid_notify()</function> and <function>sd_pid_notifyf()</function> are similar to
+    <function>sd_notify()</function> and <function>sd_notifyf()</function> but take a process ID (PID) to use
+    as originating PID for the message as first argument. This is useful to send notification messages on
+    behalf of other processes, provided the appropriate privileges are available. If the PID argument is
+    specified as 0, the process ID of the calling process is used, in which case the calls are fully
+    equivalent to <function>sd_notify()</function> and <function>sd_notifyf()</function>.</para>
+
+    <para><function>sd_pid_notify_with_fds()</function> is similar to <function>sd_pid_notify()</function>
+    but takes an additional array of file descriptors. These file descriptors are sent along the notification
+    message to the service manager. This is particularly useful for sending <literal>FDSTORE=1</literal>
+    messages, as described above. The additional arguments are a pointer to the file descriptor array plus
+    the number of file descriptors in the array. If the number of file descriptors is passed as 0, the call
+    is fully equivalent to <function>sd_pid_notify()</function>, i.e. no file descriptors are passed. Note
+    that file descriptors sent to the service manager on a message without <literal>FDSTORE=1</literal> are
+    immediately closed on reception.</para>
+
+    <para><function>sd_pid_notifyf_with_fds()</function> is a combination of
+    <function>sd_pid_notify_with_fds()</function> and <function>sd_notifyf()</function>, i.e. it accepts both
+    a PID and a set of file descriptors as input, and processes a format string to generate the state
+    string.</para>
+
+    <para><function>sd_notify_barrier()</function> allows the caller to synchronize against reception of
+    previously sent notification messages and uses the <varname>BARRIER=1</varname> command. It takes a
+    relative <varname>timeout</varname> value in microseconds which is passed to
     <citerefentry><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum>
     </citerefentry>. A value of UINT64_MAX is interpreted as infinite timeout.
     </para>
   <refsect1>
     <title>Return Value</title>
 
-    <para>On failure, these calls return a negative errno-style error code. If <varname>$NOTIFY_SOCKET</varname> was
-    not set and hence no status message could be sent, 0 is returned. If the status was sent, these functions return a
-    positive value. In order to support both service managers that implement this scheme and those which do not, it is
-    generally recommended to ignore the return value of this call. Note that the return value simply indicates whether
-    the notification message was enqueued properly, it does not reflect whether the message could be processed
+    <para>On failure, these calls return a negative errno-style error code. If
+    <varname>$NOTIFY_SOCKET</varname> was not set and hence no status message could be sent, 0 is
+    returned. If the status was sent, these functions return a positive value. In order to support both
+    service managers that implement this scheme and those which do not, it is generally recommended to ignore
+    the return value of this call. Note that the return value simply indicates whether the notification
+    message was enqueued properly, it does not reflect whether the message could be processed
     successfully. Specifically, no error is returned when a file descriptor is attempted to be stored using
-    <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file descriptors (see
-    above).</para>
+    <varname>FDSTORE=1</varname> but the service is not actually configured to permit storing of file
+    descriptors (see above).</para>
   </refsect1>
 
   <refsect1>
     <xi:include href="libsystemd-pkgconfig.xml" xpointer="pkgconfig-text"/>
     <xi:include href="threads-aware.xml" xpointer="getenv"/>
 
-    <para>These functions send a single datagram with the
-    state string as payload to the socket referenced in the
-    <varname>$NOTIFY_SOCKET</varname> environment variable. If the
-    first character of <varname>$NOTIFY_SOCKET</varname> is
-    <literal>/</literal> or <literal>@</literal>, the string is understood
-    as an <constant>AF_UNIX</constant> or Linux abstract namespace socket
-    (respectively), and in both cases the datagram is accompanied by the
-    process credentials of the sending service, using SCM_CREDENTIALS. If
-    the string starts with <literal>vsock:</literal> then the string is
-    understood as an <constant>AF_VSOCK</constant> address, which is useful
-    for hypervisors/VMMs or other processes on the host to receive a
-    notification when a virtual machine has finished booting. Note that in
-    case the hypervisor does not support <constant>SOCK_DGRAM</constant>
-    over <constant>AF_VSOCK</constant>, <constant>SOCK_SEQPACKET</constant>
-    will be used instead. The address should be in the form:
-    <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock,
-    the CID is mandatory and cannot be <literal>VMADDR_CID_ANY</literal>.
-    Note that PID1 will send the VSOCK packets from a privileged port
-    (i.e.: lower than 1024), as an attempt to address concerns that unprivileged
-    processes in the guest might try to send malicious notifications to the
-    host, driving it to make destructive decisions based on them.</para>
+    <para>These functions send a single datagram with the state string as payload to the socket referenced in
+    the <varname>$NOTIFY_SOCKET</varname> environment variable. If the first character of
+    <varname>$NOTIFY_SOCKET</varname> is <literal>/</literal> or <literal>@</literal>, the string is
+    understood as an <constant>AF_UNIX</constant> or Linux abstract namespace socket (respectively), and in
+    both cases the datagram is accompanied by the process credentials of the sending service, using
+    SCM_CREDENTIALS. If the string starts with <literal>vsock:</literal> then the string is understood as an
+    <constant>AF_VSOCK</constant> address, which is useful for hypervisors/VMMs or other processes on the
+    host to receive a notification when a virtual machine has finished booting. Note that in case the
+    hypervisor does not support <constant>SOCK_DGRAM</constant> over <constant>AF_VSOCK</constant>,
+    <constant>SOCK_SEQPACKET</constant> will be used instead. The address should be in the form:
+    <literal>vsock:CID:PORT</literal>. Note that unlike other uses of vsock, the CID is mandatory and cannot
+    be <literal>VMADDR_CID_ANY</literal>.  Note that PID1 will send the VSOCK packets from a privileged port
+    (i.e.: lower than 1024), as an attempt to address concerns that unprivileged processes in the guest might
+    try to send malicious notifications to the host, driving it to make destructive decisions based on
+    them.</para>
   </refsect1>
 
   <refsect1>
       <varlistentry>
         <term><varname>$NOTIFY_SOCKET</varname></term>
 
-        <listitem><para>Set by the service manager for supervised
-        processes for status and start-up completion notification.
-        This environment variable specifies the socket
-        <function>sd_notify()</function> talks to. See above for
-        details.</para></listitem>
+        <listitem><para>Set by the service manager for supervised processes for status and start-up
+        completion notification.  This environment variable specifies the socket
+        <function>sd_notify()</function> talks to. See above for details.</para></listitem>
       </varlistentry>
     </variablelist>
   </refsect1>
     <example>
       <title>Start-up Notification</title>
 
-      <para>When a service finished starting up, it might issue the
-      following call to notify the service manager:</para>
+      <para>When a service finished starting up, it might issue the following call to notify the service
+      manager:</para>
 
       <programlisting>sd_notify(0, "READY=1");</programlisting>
     </example>
     <example>
       <title>Extended Start-up Notification</title>
 
-      <para>A service could send the following after completing
-      initialization:</para>
+      <para>A service could send the following after completing initialization:</para>
 
       <programlisting>
 sd_notifyf(0, "READY=1\n"
@@ -459,9 +466,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>
@@ -469,12 +475,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 d42f3296cab4a0bcba0a8491f143f85e7d07f9c0..71c84958abe750742b4be2390d46955d56bf17a6 100644 (file)
       numerical signal numbers and the program will exit immediately.</para>
     </listitem>
   </varlistentry>
+
+  <varlistentry id='image-policy-open'>
+    <term><option>--image-policy=<replaceable>policy</replaceable></option></term>
+
+    <listitem><para>Takes an image policy string as argument, as per
+    <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+    policy is enforced when operating on the disk image specified via <option>--image=</option>, see
+    above. If not specified defaults to the <literal>*</literal> policy, i.e. all recognized file systems
+    in the image are used.</para></listitem>
+  </varlistentry>
+
 </variablelist>
index 7a302d5980fe1be4189dd30258e0d7f95e60db96..532c1ef64e28e94b43c3bed06b0fa3a28fe1e054 100644 (file)
@@ -8,9 +8,13 @@
 <refsect1>
 
 <para id="singular">This option is only available for system services, or for services running in per-user
- instances of the service manager when <varname>PrivateUsers=</varname> is enabled.</para>
+ instances of the service manager in which case <varname>PrivateUsers=</varname> is implicitly enabled
+ (requires unprivileged user namespaces support to be enabled in the kernel via the
+ <literal>kernel.unprivileged_userns_clone=</literal> sysctl).</para>
 
 <para id="plural">These options are only available for system services, or for services running in per-user
- instances of the service manager when <varname>PrivateUsers=</varname> is enabled.</para>
+ instances of the service manager in which case <varname>PrivateUsers=</varname> is implicitly enabled
+ (requires unprivileged user namespaces support to be enabled in the kernel via the
+ <literal>kernel.unprivileged_userns_clone=</literal> sysctl).</para>
 
 </refsect1>
index f930034cb1db6fd64ff62c03af5e8c459652fd22..639c88811097c1869185e26b18b6c61b4880ceef 100644 (file)
@@ -538,12 +538,16 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
             <varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>,
             <varname>LogsDirectory=</varname> and <varname>RuntimeDirectory=</varname>, see
             <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-            for details. For timer units this may be used to clear out the persistent timestamp data if
+            for details. It may also be used to clear the file descriptor store as enabled via
+            <varname>FileDescriptorStoreMax=</varname>, see
+            <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+            for details.  For timer units this may be used to clear out the persistent timestamp data if
             <varname>Persistent=</varname> is used and <option>--what=state</option> is selected, see
             <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This
             command only applies to units that use either of these settings. If <option>--what=</option> is
-            not specified, both the cache and runtime data are removed (as these two types of data are
-            generally redundant and reproducible on the next invocation of the unit).</para>
+            not specified, the cache and runtime data as well as the file descriptor store are removed (as
+            these three types of resources are generally redundant and reproducible on the next invocation of
+            the unit). Note that the specified units must be stopped to invoke this operation.</para>
           </listitem>
         </varlistentry>
         <varlistentry>
@@ -2193,13 +2197,17 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
 
         <listitem>
           <para>Select what type of per-unit resources to remove when the <command>clean</command> command is
-          invoked, see below. Takes one of <constant>configuration</constant>, <constant>state</constant>,
-          <constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant> to select the
-          type of resource. This option may be specified more than once, in which case all specified resource
-          types are removed. Also accepts the special value <constant>all</constant> as a shortcut for
-          specifying all five resource types. If this option is not specified defaults to the combination of
-          <constant>cache</constant> and <constant>runtime</constant>, i.e. the two kinds of resources that
-          are generally considered to be redundant and can be reconstructed on next invocation.</para>
+          invoked, see above. Takes one of <constant>configuration</constant>, <constant>state</constant>,
+          <constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant>,
+          <constant>fdstore</constant> to select the type of resource. This option may be specified more than
+          once, in which case all specified resource types are removed. Also accepts the special value
+          <constant>all</constant> as a shortcut for specifying all six resource types. If this option is not
+          specified defaults to the combination of <constant>cache</constant>, <constant>runtime</constant>
+          and <constant>fdstore</constant>, i.e. the three kinds of resources that are generally considered
+          to be redundant and can be reconstructed on next invocation. Note that the explicit removal of the
+          <constant>fdstore</constant> resource type is only useful if the
+          <varname>FileDescriptorStorePreserve=</varname> option is enabled, since the file descriptor store
+          is otherwise cleaned automatically when the unit is stopped.</para>
         </listitem>
       </varlistentry>
 
@@ -2276,6 +2284,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
         switch of the same name.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--runtime</option></term>
 
index ad6d691a1d1fd2ddb7d0a2c55dbd8ef56b6c463c..7176e3c046850f7c7f05fb5af285dd726243cb88 100644 (file)
       <arg choice="plain">malloc</arg>
       <arg choice="opt" rep="repeat"><replaceable>D-BUS SERVICE</replaceable></arg>
     </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-analyze</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">fdstore</arg>
+      <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-analyze</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">image-policy</arg>
+      <arg choice="plain" rep="repeat"><replaceable>POLICY</replaceable></arg>
+    </cmdsynopsis>
   </refsynopsisdiv>
 
   <refsect1>
@@ -803,7 +815,69 @@ $ systemd-analyze verify /tmp/source:alias.service
 }
         </programlisting>
       </example>
+    </refsect2>
+
+    <refsect2>
+      <title><command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command></title>
+
+      <para>Lists the current contents of the specified service unit's file descriptor store. This shows
+      names, inode types, device numbers, inode numbers, paths and open modes of the open file
+      descriptors. The specified units must have <varname>FileDescriptorStoreMax=</varname> enabled, see
+      <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+      details.</para>
 
+      <example>
+        <title>Table output</title>
+        <programlisting>$ systemd-analyze fdstore systemd-journald.service
+FDNAME TYPE DEVNO   INODE RDEVNO PATH             FLAGS
+stored sock 0:8   4218620 -      socket:[4218620] ro
+stored sock 0:8   4213198 -      socket:[4213198] ro
+stored sock 0:8   4213190 -      socket:[4213190] ro
+…</programlisting>
+      </example>
+
+      <para>Note: the "DEVNO" column refers to the major/minor numbers of the device node backing the file
+      system the file descriptor's inode is on. The "RDEVNO" column refers to the major/minor numbers of the
+      device node itself if the file descriptor refers to one. Compare with corresponding
+      <varname>.st_dev</varname> and <varname>.st_rdev</varname> fields in <type>struct stat</type> (see
+      <citerefentry
+      project='man-pages'><refentrytitle>stat</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+      details). The listed inode numbers in the "INODE" column are on the file system indicated by
+      "DEVNO".</para>
+    </refsect2>
+
+    <refsect2>
+      <title><command>systemd-analyze image-policy <optional><replaceable>POLICY</replaceable>…</optional></command></title>
+
+      <para>This command analyzes the specified image policy string, as per
+      <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+      policy is normalized and simplified. For each currently defined partition identifier (as per the <ulink
+      url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
+      Partitions Specification</ulink> the effect of the image policy string is shown in tabular form.</para>
+
+      <example>
+        <title>Example Output</title>
+
+        <programlisting>$ systemd-analyze image-policy swap=encrypted:usr=read-only-on+verity:root=encrypted
+Analyzing policy: root=encrypted:usr=verity+read-only-on:swap=encrypted
+       Long form: root=encrypted:usr=verity+read-only-on:swap=encrypted:=unused+absent
+
+PARTITION       MODE        READ-ONLY GROWFS
+root            encrypted   -         -
+usr             verity      yes       -
+home            ignore      -         -
+srv             ignore      -         -
+esp             ignore      -         -
+xbootldr        ignore      -         -
+swap            encrypted   -         -
+root-verity     ignore      -         -
+usr-verity      unprotected yes       -
+root-verity-sig ignore      -         -
+usr-verity-sig  ignore      -         -
+tmp             ignore      -         -
+var             ignore      -         -
+default         ignore      -         -</programlisting>
+      </example>
     </refsect2>
   </refsect1>
 
@@ -932,6 +1006,8 @@ $ systemd-analyze verify /tmp/source:alias.service
         operate on files inside the specified image path <replaceable>PATH</replaceable>.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--offline=<replaceable>BOOL</replaceable></option></term>
 
index 7044548206084c3f6cfc410918b85c7e8f870be7..c92a250ca22fc8bb12dc849b460b0e0faac8aede 100644 (file)
@@ -60,6 +60,9 @@
     files. Some metadata is attached to core files in the form of extended attributes, so the core files are
     useful for some purposes even without the full metadata available in the journal entry.</para>
 
+    <para>For further details see <ulink url="https://systemd.io/COREDUMP">systemd Coredump
+    Handling</ulink>.</para>
+
     <refsect2>
       <title>Invocation of <command>systemd-coredump</command></title>
 
@@ -431,7 +434,8 @@ user.coredump.exe="/usr/lib64/firefox/firefox"
       <citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry project='man-pages'><refentrytitle>core</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>sysctl.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
-      <citerefentry><refentrytitle>systemd-sysctl.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+      <citerefentry><refentrytitle>systemd-sysctl.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <ulink url="https://systemd.io/COREDUMP">systemd Coredump Handling</ulink>
     </para>
   </refsect1>
 </refentry>
index b16ee09ceec6b227b2dec9bc47988b18c56b6aa8..fbe62262af08555b950c27570ec7c8c46eed47ed 100644 (file)
 
         <listitem><para>When specified with <command>encrypt</command> controls whether to show the encrypted
         credential as <varname>SetCredentialEncrypted=</varname> setting that may be pasted directly into a
-        unit file.</para></listitem>
+        unit file. Has effect only when used together with <option>--name=</option> and <literal>-</literal>
+        as the output file.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 8af75d1523bcce49d134bf2dd8d89159e5a45e86..6ca850e0ef249ac81160dadebcc91a4ee124d4bd 100644 (file)
 
     <para>The tool supports only LUKS2 volumes, as it stores token meta-information in the LUKS2 JSON token
     area, which is not available in other encryption formats.</para>
+
+    <refsect2>
+      <title>TPM2 PCRs and policies</title>
+
+      <para>PCRs allow binding of the encryption of secrets to specific software versions and system state,
+      so that the enrolled key is only accessible (may be "unsealed") if specific trusted software and/or
+      configuration is used. Such bindings may be created with the option <option>--tpm2-pcrs=</option>
+      described below.</para>
+
+      <para>Secrets may also be bound indirectly: a signed policy for a state of some combination of PCR
+      values is provided, and the secret is bound to the public part of the key used to sign this policy.
+      This means that the owner of a key can generate a sequence of signed policies, for specific software
+      versions and system states, and the secret can be decrypted as long as the machine state matches one of
+      those policies. For example, a vendor may provide such a policy for each kernel+initrd update, allowing
+      users to encrypt secrets so that they can be decrypted when running any kernel+initrd signed by the
+      vendor. Such bindings may be created with the options <option>--tpm2-public-key=</option>,
+      <option>--tpm2-public-key-pcrs=</option>, <option>--tpm2-signature=</option> described below.
+      </para>
+
+      <para>See <ulink url="https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/">Linux TPM
+      PCR Registry</ulink> for an authoritative list of PCRs and how they are updated. The table below
+      contains a quick reference, describing in particular the PCRs modified by systemd.</para>
+
+      <table>
+        <title>Well-known PCR Definitions</title>
+
+        <!-- See: https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/ -->
+        <!-- See: https://github.com/rhboot/shim/blob/main/README.tpm -->
+        <!-- See: https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html -->
+        <!-- See: https://sourceforge.net/p/linux-ima/wiki/Home/ -->
+        <!-- See: https://github.com/tianocore-docs/edk2-TrustedBootChain/blob/main/4_Other_Trusted_Boot_Chains.md -->
+        <!-- See: https://wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers -->
+
+        <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+          <colspec colname="pcr" />
+          <colspec colname="name" />
+          <colspec colname="definition" />
+
+          <thead>
+            <row>
+              <entry>PCR</entry>
+              <entry>name</entry>
+              <entry>Explanation</entry>
+            </row>
+          </thead>
+
+          <tbody>
+            <row>
+              <entry>0</entry>
+              <entry>platform-code</entry>
+              <entry>Core system firmware executable code; changes on firmware updates</entry>
+            </row>
+
+            <row>
+              <entry>1</entry>
+              <entry>platform-config</entry>
+              <entry>Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements</entry>
+            </row>
+
+            <row>
+              <entry>2</entry>
+              <entry>external-code</entry>
+              <entry>Extended or pluggable executable code; includes option ROMs on pluggable hardware</entry>
+            </row>
+
+            <row>
+              <entry>3</entry>
+              <entry>external-config</entry>
+              <entry>Extended or pluggable firmware data; includes information about pluggable hardware</entry>
+            </row>
+
+            <row>
+              <entry>4</entry>
+              <entry>boot-loader-code</entry>
+              <entry>Boot loader and additional drivers, PE binaries invoked by the boot loader; changes on boot loader updates. <citerefentry><refentrytitle>sd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures system extension images read from the ESP here too (see <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>).</entry>
+            </row>
+
+            <row>
+              <entry>5</entry>
+              <entry>boot-loader-config</entry>
+              <entry>GPT/Partition table; changes when the partitions are added, modified, or removed</entry>
+            </row>
+
+            <row>
+              <entry>7</entry>
+              <entry>secure-boot-policy</entry>
+              <entry>Secure Boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes.</entry>
+            </row>
+
+            <row>
+              <entry>9</entry>
+              <entry>kernel-initrd</entry>
+              <entry>The Linux kernel measures all initrds it receives into this PCR.</entry>
+              <!-- Strictly speaking only Linux >= 5.17 using the LOAD_FILE2 protocol, see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f046fff8bc4c4d8f8a478022e76e40b818f692df -->
+            </row>
+
+            <row>
+              <entry>10</entry>
+              <entry>ima</entry>
+              <entry>The IMA project measures its runtime state into this PCR.</entry>
+            </row>
+
+            <row>
+              <entry>11</entry>
+              <entry>kernel-boot</entry>
+              <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. <citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures boot phase strings into this PCR at various milestones of the boot process.</entry>
+            </row>
+
+            <row>
+              <entry>12</entry>
+              <entry>kernel-config</entry>
+              <entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR.</entry>
+            </row>
+
+            <row>
+              <entry>13</entry>
+              <entry>sysexts</entry>
+              <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> images it passes to the booted kernel into this PCR.</entry>
+            </row>
+
+            <row>
+              <entry>14</entry>
+              <entry>shim-policy</entry>
+              <entry>The shim project measures its "MOK" certificates and hashes into this PCR.</entry>
+            </row>
+
+            <row>
+              <entry>15</entry>
+              <entry>system-identity</entry>
+              <entry><citerefentry><refentrytitle>systemd-cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> optionally measures the volume key of activated LUKS volumes into this PCR. <citerefentry><refentrytitle>systemd-pcrmachine.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures the <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> into this PCR. <citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures mount points, file system UUIDs, labels, partition UUIDs of the root and <filename>/var/</filename> filesystems into this PCR.</entry>
+            </row>
+
+            <row>
+              <entry>16</entry>
+              <entry>debug</entry>
+              <entry>Debug</entry>
+            </row>
+
+            <row>
+              <entry>23</entry>
+              <entry>application-support</entry>
+              <entry>Application Support</entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </table>
+
+      <para>In general, encrypted volumes would be bound to some combination of PCRs 7, 11, and 14 (if
+      shim/MOK is used). In order to allow firmware and OS version updates, it is typically not advisable to
+      use PCRs such as 0 and 2, since the program code they cover should already be covered indirectly
+      through the certificates measured into PCR 7. Validation through certificates hashes is typically
+      preferable over validation through direct measurements as it is less brittle in context of OS/firmware
+      updates: the measurements will change on every update, but signatures should remain unchanged. See the
+      <ulink url="https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/">Linux TPM PCR
+      Registry</ulink> for more discussion.</para>
+    </refsect2>
   </refsect1>
 
   <refsect1>
       <varlistentry>
         <term><option>--tpm2-pcrs=</option><arg rep="repeat">PCR</arg></term>
 
-        <listitem><para>Configures the TPM2 PCRs (Platform Configuration Registers) to bind the enrollment
-        requested via <option>--tpm2-device=</option> to. Takes a <literal>+</literal> separated list of
-        numeric PCR indexes in the range 0…23. If not used, defaults to PCR 7 only. If an empty string is
-        specified, binds the enrollment to no PCRs at all. PCRs allow binding the enrollment to specific
-        software versions and system state, so that the enrolled unlocking key is only accessible (may be
-        "unsealed") if specific trusted software and/or configuration is used.</para>
-
-        <table>
-          <title>Well-known PCR Definitions</title>
-
-          <!-- See: https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/ -->
-          <!-- See: https://github.com/rhboot/shim/blob/main/README.tpm -->
-          <!-- See: https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html -->
-          <!-- See: https://sourceforge.net/p/linux-ima/wiki/Home/ -->
-          <!-- See: https://github.com/tianocore-docs/edk2-TrustedBootChain/blob/main/4_Other_Trusted_Boot_Chains.md -->
-          <!-- See: https://wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers -->
-
-          <tgroup cols='2' align='left' colsep='1' rowsep='1'>
-            <colspec colname="pcr" />
-            <colspec colname="definition" />
-
-            <thead>
-              <row>
-                <entry>PCR</entry>
-                <entry>Explanation</entry>
-              </row>
-            </thead>
-
-            <tbody>
-              <row>
-                <entry>0</entry>
-                <entry>Core system firmware executable code; changes on firmware updates</entry>
-              </row>
-
-              <row>
-                <entry>1</entry>
-                <entry>Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements</entry>
-              </row>
-
-              <row>
-                <entry>2</entry>
-                <entry>Extended or pluggable executable code; includes option ROMs on pluggable hardware</entry>
-              </row>
-
-              <row>
-                <entry>3</entry>
-                <entry>Extended or pluggable firmware data; includes information about pluggable hardware</entry>
-              </row>
-
-              <row>
-                <entry>4</entry>
-                <entry>Boot loader and additional drivers; changes on boot loader updates. The shim project will measure the PE binary it chain loads into this PCR. If the Linux kernel is invoked as UEFI PE binary, it is measured here, too. <citerefentry><refentrytitle>sd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures system extension images read from the ESP here too (see <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>).</entry>
-              </row>
-
-              <row>
-                <entry>5</entry>
-                <entry>GPT/Partition table; changes when the partitions are added, modified or removed</entry>
-              </row>
-
-              <row>
-                <entry>6</entry>
-                <entry>Power state events; changes on system suspend/sleep</entry>
-              </row>
-
-              <row>
-                <entry>7</entry>
-                <entry>Secure Boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. The shim project will measure most of its (non-MOK) certificates and SBAT data into this PCR.</entry>
-              </row>
-
-              <!-- Grub measures all its commands and the kernel command line into PCR 8… -->
-              <!-- Grub measures all files it reads (including kernel image, initrd, …) into PCR 9… -->
-
-              <row>
-                <entry>9</entry>
-                <entry>The Linux kernel measures all initrds it receives into this PCR.</entry>
-                <!-- Strictly speaking only Linux >= 5.17 using the LOAD_FILE2 protocol, see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f046fff8bc4c4d8f8a478022e76e40b818f692df -->
-              </row>
-
-              <row>
-                <entry>10</entry>
-                <entry>The IMA project measures its runtime state into this PCR.</entry>
-              </row>
-
-              <row>
-                <entry>11</entry>
-                <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initrd. <citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures boot phase strings into this PCR at various milestones of the boot process.</entry>
-              </row>
-
-              <row>
-                <entry>12</entry>
-                <entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if <command>systemd-boot</command> and <command>systemd-stub</command> are used in combination the command line might be measured twice!)</entry>
-              </row>
-
-              <row>
-                <entry>13</entry>
-                <entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> images it loads and passed to the booted kernel into this PCR.</entry>
-              </row>
-
-              <row>
-                <entry>14</entry>
-                <entry>The shim project measures its "MOK" certificates and hashes into this PCR.</entry>
-              </row>
-
-              <row>
-                <entry>15</entry>
-                <entry><citerefentry><refentrytitle>systemd-cryptsetup</refentrytitle><manvolnum>7</manvolnum></citerefentry> optionally measures the volume key of activated LUKS volumes into this PCR.</entry>
-              </row>
-            </tbody>
-          </tgroup>
-        </table>
-
-        <para>For most applications it should be sufficient to bind against PCR 7 (and possibly PCR 14, if
-        shim/MOK is desired), as this includes measurements of the trusted certificates (and possibly hashes)
-        that are used to validate all components of the boot process up to and including the OS kernel. In
-        order to simplify firmware and OS version updates it's typically not advisable to include PCRs such
-        as 0 and 2 in the binding of the enrollment, since the program code they cover should already be
-        protected indirectly through the certificates measured into PCR 7. Validation through these
-        certificates is typically preferable over validation through direct measurements as it is less
-        brittle in context of OS/firmware updates: the measurements will change on every update, but code
-        signatures likely will validate against pre-existing certificates.</para></listitem>
+        <listitem><para>Configures the TPM2 PCRs (Platform Configuration Registers) to bind to when
+        enrollment is requested via <option>--tpm2-device=</option>. Takes a list of PCR names or numeric
+        indices in the range 0…23. Multiple PCR indexes are separated by <literal>+</literal>. If not
+        specified, the default is to use PCR 7 only. If an empty string is specified, binds the enrollment to
+        no PCRs at all. See the table above for a list of available PCRs.</para>
+
+        <para>Example: <option>--tpm2-pcrs=boot-loader-code+platform-config+boot-loader-config</option>
+        specifies that PCR registers 4, 1, and 5 should be used.</para>
+        </listitem>
       </varlistentry>
 
       <varlistentry>
         <option>--tpm2-public-key-pcrs=</option>: the former binds decryption to the current, specific PCR
         values; the latter binds decryption to any set of PCR values for which a signature by the specified
         public key can be provided. The latter is hence more useful in scenarios where software updates shell
-        be possible without losing access to all previously encrypted LUKS2 volumes.</para>
+        be possible without losing access to all previously encrypted LUKS2 volumes. Like with
+        <option>--tpm2-pcrs=</option>, names defined in the table above can also be used to specify the
+        registers, for instance
+        <option>--tpm2-public-key-pcrs=boot-loader-code+system-identity</option>.</para>
 
-        <para>The <option>--tpm2-signature=</option> option takes a path to a TPM2 PCR signature file
-        as generated by the
+        <para>The <option>--tpm2-signature=</option> option takes a path to a TPM2 PCR signature file as
+        generated by the
         <citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-        tool. If this is not specified explicitly a suitable signature file
+        tool. If this is not specified explicitly, a suitable signature file
         <filename>tpm2-pcr-signature.json</filename> is searched for in <filename>/etc/systemd/</filename>,
-        <filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and
-        used. If a signature file is specified or found it is used to verify if the volume can be unlocked
-        with it given the current PCR state, before the new slot is written to disk. This is intended as
-        safety net to ensure that access to a volume is not lost if a public key is enrolled for which no
-        valid signature for the current PCR state is available. If the supplied signature does not unlock the
+        <filename>/run/systemd/</filename>, <filename>/usr/lib/systemd/</filename> (in this order) and used.
+        If a signature file is specified or found it is used to verify if the volume can be unlocked with it
+        given the current PCR state, before the new slot is written to disk. This is intended as safety net
+        to ensure that access to a volume is not lost if a public key is enrolled for which no valid
+        signature for the current PCR state is available. If the supplied signature does not unlock the
         current PCR state and public key combination, no slot is enrolled and the operation will fail. If no
         signature file is specified or found no such safety verification is done.</para></listitem>
       </varlistentry>
index 06c57a22ecdbd52ee6be31460be94682daa9bfeb..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>
 
+      <varlistentry>
+        <term><option>--validate</option></term>
+
+        <listitem><para>Validates the partition arrangement of a disk image (DDI), and ensures it matches the
+        image policy specified via <option>--image-policy=</option>, if one is specified. This parses the
+        partition table and probes the file systems in the image, but does not attempt to mount them (nor to
+        set up disk encryption/authentication via LUKS/Verity). It does this taking the configured image
+        dissection policy into account. Since this operation does not mount file systems, this command –
+        unlike all other commands implemented by this tool – requires no privileges other than the ability to
+        access the specified file. Prints "OK" and returns zero if the image appears to be in order and
+        matches the specified image dissection policy. Otherwise prints an error message and returns
+        non-zero.</para></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
         <command>cfdisk /dev/loop/by-ref/quux</command>.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
       <xi:include href="standard-options.xml" xpointer="no-pager" />
       <xi:include href="standard-options.xml" xpointer="no-legend" />
       <xi:include href="standard-options.xml" xpointer="json" />
index cfce8a40ad58bcabe728d5a8750d036332b23a61..42666c96f8852be81b4ae621d03acd0a4c354354 100644 (file)
         <literal>root</literal> user instead of overwriting the entire file.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--reset</option></term>
+
+        <listitem><para>If specified, all existing files that are configured by
+        <command>systemd-firstboot</command> are removed. Note that the files are removed regardless of
+        whether they'll be configured with a new value or not. This operation ensures that the next boot of
+        the image will be considered a first boot, and <command>systemd-firstboot</command> will prompt again
+        to configure each of the removed files.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--delete-root-password</option></term>
 
index e928aebdb335c4e0d73ddbaedd3476c2807bba9e..403286829e1e7ec4efd7769c70df54d8c28308af 100644 (file)
     <para><filename>systemd-fsck</filename> does not know any details
     about specific filesystems, and simply executes file system
     checkers specific to each filesystem type
-    (<filename>/sbin/fsck.<replaceable>type</replaceable></filename>). These checkers will decide if
+    (<filename>fsck.<replaceable>type</replaceable></filename>). These checkers will decide if
     the filesystem should actually be checked based on the time since
     last check, number of mounts, unclean unmount, etc.</para>
 
     <para><filename>systemd-fsck-root.service</filename> and <filename>systemd-fsck-usr.service</filename>
-    will activate <filename>reboot.target</filename> if <filename>/sbin/fsck</filename> returns the "System
-    should reboot" condition, or <filename>emergency.target</filename> if <filename>/sbin/fsck</filename>
+    will activate <filename>reboot.target</filename> if <filename>fsck</filename> returns the "System
+    should reboot" condition, or <filename>emergency.target</filename> if <filename>fsck</filename>
     returns the "Filesystem errors left uncorrected" condition.</para>
 
     <para><filename>systemd-fsck@.service</filename> will fail if
-    <filename>/sbin/fsck</filename> returns with either "System should reboot"
+    <filename>fsck</filename> returns with either "System should reboot"
     or "Filesystem errors left uncorrected" conditions. For filesystems
     listed in <filename>/etc/fstab</filename> without <literal>nofail</literal>
     or <literal>noauto</literal> options, <literal>local-fs.target</literal>
index bd542cb7f721a9c9a53836db418b12a8889f7900..1730039b62c5024e8f0131fa2336310711ea5af0 100644 (file)
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>systemd.image_policy=</varname></term>
+        <term><varname>rd.systemd.image_policy=</varname></term>
+
+        <listitem><para>Takes an image dissection policy string as argument (as per
+        <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>),
+        and allows enforcing a policy on dissection and use of the automatically discovered GPT partition
+        table entries.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>root=</varname></term>
         <term><varname>rootfstype=</varname></term>
index e66e0f1e9c31db4794f1234e425e25b562a607dd..c8a702ad58dae10fb170c2cbc26b3f8e8570c0cf 100644 (file)
 
     <variablelist>
       <varlistentry>
+        <term><option>-o <replaceable>FILE</replaceable></option></term>
         <term><option>--output=<replaceable>FILE</replaceable></option></term>
 
         <listitem><para>Will write to this journal file. The filename
       </varlistentry>
 
       <varlistentry>
+        <term><option>-o <replaceable>DIR</replaceable></option></term>
         <term><option>--output=<replaceable>DIR</replaceable></option></term>
 
         <listitem><para>Will create journal files underneath directory
index f1695b6ddb2e51059a4f03de0049afdf261d9d00..c07a853418e7f7de0ec52a476e5c986f857983db 100644 (file)
@@ -95,6 +95,8 @@
         tree.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--commit</option></term>
         <listitem><para>Commit a transient machine ID to disk. This
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 5a154686f5f7b305ca9676db8c5de55720a70eef..1b469fe85c4a9a628130cd3e53ac2202768a874d 100644 (file)
         escaped as <literal>\;</literal>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--fd=</option></term>
+
+        <listitem><para>Send a file descriptor along with the notification message. This is useful when
+        invoked in services that have the <varname>FileDescriptorStoreMax=</varname> setting enabled, see
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details. The specified file descriptor must be passed to <command>systemd-notify</command> when
+        invoked. This option may be used multiple times to pass multiple file descriptors in a single
+        notification message.</para>
+
+        <para>To use this functionality from a <command>bash</command> shell, use an expression like the following:</para>
+        <programlisting>systemd-notify --fd=4 --fd=5 4&lt;/some/file 5&lt;/some/other/file</programlisting></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--fdname=</option></term>
+
+        <listitem><para>Set a name to assign to the file descriptors passed via <option>--fd=</option> (see
+        above). This controls the <literal>FDNAME=</literal> field. This setting may only be specified once,
+        and applies to all file descriptors passed. Invoke this tool multiple times in case multiple file
+        descriptors with different file descriptor names shall be submitted.</para></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
index e2c751692f14af9a74437280bc7693e093a2bf9a..ded8e3cd715ffdd0552108c9efbc0fd8da0e098c 100644 (file)
         together with <option>--directory=</option>, <option>--template=</option>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--image-policy=<replaceable>policy</replaceable></option></term>
+
+        <listitem><para>Takes an image policy string as argument, as per
+        <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+        policy is enforced when operating on the disk image specified via <option>--image=</option>, see
+        above. If not specified defaults to
+        <literal>root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent:home=encrypted+unprotected+absent:srv=encrypted+unprotected+absent:esp=unprotected+absent:xbootldr=unprotected+absent:tmp=encrypted+unprotected+absent:var=encrypted+unprotected+absent</literal>,
+        i.e. all recognized file systems in the image are used, but not the swap partition.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--oci-bundle=</option></term>
 
       <varlistentry>
         <term><option>--network-interface=</option></term>
 
-        <listitem><para>Assign the specified network interface to the container. This will remove the
-        specified interface from the calling namespace and place it in the container. When the container
-        terminates, it is moved back to the calling namespace.  Note that
-        <option>--network-interface=</option> implies <option>--private-network</option>. This option may be
-        used more than once to add multiple network interfaces to the container.</para>
+        <listitem><para>Assign the specified network interface to the container. Either takes a single
+        interface name, referencing the name on the host, or a colon-separated pair of interfaces, in which
+        case the first one references the name on the host, and the second one the name in the container.
+        When the container terminates, the interface is moved back to the calling namespace and renamed to
+        its original name.  Note that <option>--network-interface=</option> implies
+        <option>--private-network</option>. This option may be used more than once to add multiple network
+        interfaces to the container.</para>
 
         <para>Note that any network interface specified this way must already exist at the time the container
         is started. If the container shall be started automatically at boot via a
@@ -869,9 +882,12 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
         <term><option>--network-macvlan=</option></term>
 
         <listitem><para>Create a <literal>macvlan</literal> interface of the specified Ethernet network
-        interface and add it to the container. A <literal>macvlan</literal> interface is a virtual interface
-        that adds a second MAC address to an existing physical Ethernet link. The interface in the container
-        will be named after the interface on the host, prefixed with <literal>mv-</literal>. Note that
+        interface and add it to the container. Either takes a single interface name, referencing the name
+        on the host, or a colon-separated pair of interfaces, in which case the first one references the name
+        on the host, and the second one the name in the container. A <literal>macvlan</literal> interface is
+        a virtual interface that adds a second MAC address to an existing physical Ethernet link. If the
+        container interface name is not defined, the interface in the container will be named after the
+        interface on the host, prefixed with <literal>mv-</literal>. Note that
         <option>--network-macvlan=</option> implies <option>--private-network</option>. This option may be
         used more than once to add multiple network interfaces to the container.</para>
 
@@ -884,9 +900,13 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
         <term><option>--network-ipvlan=</option></term>
 
         <listitem><para>Create an <literal>ipvlan</literal> interface of the specified Ethernet network
-        interface and add it to the container. An <literal>ipvlan</literal> interface is a virtual interface,
+        interface and add it to the container. Either takes a single interface name, referencing the name on
+        the host, or a colon-separated pair of interfaces, in which case the first one references the name
+        on the host, and the second one the name in the container. An <literal>ipvlan</literal> interface is
+        a virtual interface,
         similar to a <literal>macvlan</literal> interface, which uses the same MAC address as the underlying
-        interface.  The interface in the container will be named after the interface on the host, prefixed
+        interface. If the container interface name is not defined, the interface in the container will be
+        named after the interface on the host, prefixed
         with <literal>iv-</literal>.  Note that <option>--network-ipvlan=</option> implies
         <option>--private-network</option>. This option may be used more than once to add multiple network
         interfaces to the container.</para>
@@ -1657,7 +1677,7 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
 
       <programlisting># dnf -y --releasever=&fedora_latest_version; --installroot=/var/lib/machines/f&fedora_latest_version; \
       --repo=fedora --repo=updates --setopt=install_weak_deps=False install \
-      passwd dnf fedora-release vim-minimal systemd systemd-networkd
+      passwd dnf fedora-release vim-minimal util-linux systemd systemd-networkd
 # systemd-nspawn -bD /var/lib/machines/f&fedora_latest_version;</programlisting>
 
       <para>This installs a minimal Fedora distribution into the
index 9adfcc5af063b580b777e1b3fe79e366fc73a5ec..98c20471da89fbef4fb16f62139a3061c83a4d32 100644 (file)
   <refsect1>
     <title>Description</title>
 
-    <para><filename>systemd-poweroff.service</filename> is a system
-    service that is pulled in by <filename>poweroff.target</filename> and
-    is responsible for the actual system power-off operation. Similarly,
-    <filename>systemd-halt.service</filename> is pulled in by
-    <filename>halt.target</filename>,
-    <filename>systemd-reboot.service</filename> by
-    <filename>reboot.target</filename> and
-    <filename>systemd-kexec.service</filename> by
-    <filename>kexec.target</filename> to execute the respective
-    actions.</para>
+    <para><filename>systemd-poweroff.service</filename> is a system service that is pulled in by
+    <filename>poweroff.target</filename> and is responsible for the actual system power-off
+    operation. Similarly, <filename>systemd-halt.service</filename> is pulled in by
+    <filename>halt.target</filename>, <filename>systemd-reboot.service</filename> by
+    <filename>reboot.target</filename> and <filename>systemd-kexec.service</filename> by
+    <filename>kexec.target</filename> to execute the respective actions.</para>
 
-    <para>When these services are run, they ensure that PID 1 is
-    replaced by the
-    <filename>/usr/lib/systemd/systemd-shutdown</filename> tool which
-    is then responsible for the actual shutdown. Before shutting down,
-    this binary will try to unmount all remaining file systems,
-    disable all remaining swap devices, detach all remaining storage
-    devices and kill all remaining processes.</para>
+    <para>When these services are run, they ensure that PID 1 is replaced by the
+    <filename>/usr/lib/systemd/systemd-shutdown</filename> tool which is then responsible for the actual
+    shutdown. Before shutting down, this binary will try to unmount all remaining file systems (or at least
+    remount them read-only), disable all remaining swap devices, detach all remaining storage devices and
+    kill all remaining processes.</para>
 
-    <para>It is necessary to have this code in a separate binary
-    because otherwise rebooting after an upgrade might be broken — the
-    running PID 1 could still depend on libraries which are not
-    available any more, thus keeping the file system busy, which then
-    cannot be re-mounted read-only.</para>
+    <para>It is necessary to have this code in a separate binary because otherwise rebooting after an upgrade
+    might be broken — the running PID 1 could still depend on libraries which are not available any more,
+    thus keeping the file system busy, which then cannot be re-mounted read-only.</para>
 
-    <para>Immediately before executing the actual system
-    power-off/halt/reboot/kexec <filename>systemd-shutdown</filename>
-    will run all executables in
-    <filename>/usr/lib/systemd/system-shutdown/</filename> and pass
-    one arguments to them: either <literal>poweroff</literal>,
-    <literal>halt</literal>, <literal>reboot</literal>, or
-    <literal>kexec</literal>, depending on the chosen action. All
-    executables in this directory are executed in parallel, and
-    execution of the action is not continued before all executables
-    finished.</para>
+    <para>Shortly before executing the actual system power-off/halt/reboot/kexec
+    <filename>systemd-shutdown</filename> will run all executables in
+    <filename>/usr/lib/systemd/system-shutdown/</filename> and pass one arguments to them: either
+    <literal>poweroff</literal>, <literal>halt</literal>, <literal>reboot</literal>, or
+    <literal>kexec</literal>, depending on the chosen action. All executables in this directory are executed
+    in parallel, and execution of the action is not continued before all executables finished. Note that
+    these executables are run <emphasis>after</emphasis> all services have been shut down, and after most
+    mounts have been detached (the root file system as well as <filename>/run/</filename> and various API
+    file systems are still around though). This means any programs dropped into this directory must be
+    prepared to run in such a limited execution environment and not rely on external services or hierarchies
+    such as <filename>/var/</filename> to be around (or writable).</para>
 
     <para>Note that <filename>systemd-poweroff.service</filename> (and the related units) should never be
     executed directly. Instead, trigger system shutdown with a command such as <literal>systemctl
index bc8cf50a39fa7b075c862374f74e2a11c98d736b..59bb66e935267e9d978366bb9c788818eb17b3bd 100644 (file)
@@ -23,7 +23,7 @@
 
   <refsynopsisdiv>
     <para><filename>systemd-random-seed.service</filename></para>
-    <para><filename>/usr/lib/systemd/random-seed</filename></para>
+    <para><filename>/usr/lib/systemd/systemd-random-seed</filename></para>
   </refsynopsisdiv>
 
   <refsect1>
index 9033ef76d6923a2533316b9914ff3d00766c1d93..98ca1c431a658ebef2909874e8672d156c5d9338 100644 (file)
         <option>--root=</option>, see above.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--seed=</option></term>
 
index cd9e50d5b8940238529faeee0b61901483ffc588..73adbfb9276cf66df12ec8acdeb757165599a296 100644 (file)
     Consider using the <option>exec</option> service type (i.e. <option>--property=Type=exec</option>) to
     ensure that <command>systemd-run</command> returns successfully only if the specified command line has
     been successfully started.</para>
+
+    <para>After <command>systemd-run</command> passes the command to the service manager, the manager
+    performs variable expansion. This means that dollar characters (<literal>$</literal>) which should not be
+    expanded need to be escaped as <literal>$$</literal>. Expansion can also be disabled using
+    <varname>--expand-environment=no</varname>.</para>
   </refsect1>
 
   <refsect1>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--expand-environment=<replaceable>BOOL</replaceable></option></term>
+
+        <listitem><para>Expand environment variables in command arguments. If enabled (the default),
+        environment variables specified as <literal>${<replaceable>VARIABLE</replaceable>}</literal> will be
+        expanded in the same way as in commands specified via <varname>ExecStart=</varname> in units. With
+        <varname>--scope</varname>, this expansion is performed by <command>systemd-run</command> itself, and
+        in other cases by the service manager that spawns the command. Note that this is similar to, but not
+        the same as variable expansion in
+        <citerefentry project='man-pages'><refentrytitle>bash</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        and other shells.</para>
+
+        <para>See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for a description of variable expansion. Disabling variable expansion is useful if the specified
+        command includes or may include a <literal>$</literal> sign.</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>-r</option></term>
         <term><option>--remain-after-exit</option></term>
@@ -528,12 +552,49 @@ There is a screen on:
       <programlisting>$ loginctl enable-linger</programlisting>
     </example>
 
+    <example>
+      <title>Variable expansion by the manager</title>
+
+      <programlisting>$ systemd-run -t echo "&lt;${INVOCATION_ID}>" '&lt;${INVOCATION_ID}>'
+      &lt;> &lt;5d0149bfa2c34b79bccb13074001eb20>
+      </programlisting>
+
+      <para>The first argument is expanded by the shell (double quotes), but the second one is not expanded
+      by the shell (single quotes). <command>echo</command> is called with [<literal>/usr/bin/echo</literal>,
+      <literal>[]</literal>, <literal>[${INVOCATION_ID}]</literal>] as the argument array, and then
+      <command>systemd</command> generates <varname>${INVOCATION_ID}</varname> and substitutes it in the
+      command-line. This substitution could not be done on the client side, because the target ID that will
+      be set for the service isn't known before the call is made.</para>
+    </example>
+
+    <example>
+      <title>Variable expansion and output redirection using a shell</title>
+
+      <para>Variable expansion by <command>systemd</command> can be disabled with
+      <varname>--expand-environment=no</varname>.</para>
+
+      <para>Disabling variable expansion can be useful if the command to execute contains dollar characters
+      and escaping them would be inconvenient. For example, when a shell is used:</para>
+
+      <programlisting>$ systemd-run --expand-environment=no -t bash \
+      -c 'echo $SHELL $$ >/dev/stdout'
+/bin/bash 12345
+      </programlisting>
+
+      <para>The last argument is passed verbatim to the <command>bash</command> shell which is started by the
+      service unit. The shell expands <literal>$SHELL</literal> to the path of the shell, and
+      <literal>$$</literal> to its process number, and then those strings are passed to the
+      <command>echo</command> built-in and printed to standard output (which in this case is connected to the
+      calling terminal).</para>
+    </example>
+
     <example>
       <title>Return value</title>
 
       <programlisting>$ systemd-run --user --wait true
 $ systemd-run --user --wait -p SuccessExitStatus=11 bash -c 'exit 11'
-$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 bash -c 'kill -SIGUSR1 $$$$'</programlisting>
+$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no \
+      bash -c 'kill -SIGUSR1 $$'</programlisting>
 
       <para>Those three invocations will succeed, i.e. terminate with an exit code of 0.</para>
     </example>
index 39a16d8e8fb8035728728ec2314d6d103ec70b6f..5e8d11ef3d14eeaaaa526c10d4fc999dc4ff0ce9 100644 (file)
@@ -19,6 +19,8 @@
   <refnamediv>
     <refname>systemd-sysext</refname>
     <refname>systemd-sysext.service</refname>
+    <refname>systemd-confext</refname>
+    <refname>systemd-confext.service</refname>
     <refpurpose>Activates System Extension Images</refpurpose>
   </refnamediv>
 
 
     <para><literallayout><filename>systemd-sysext.service</filename></literallayout></para>
 
+    <cmdsynopsis>
+      <command>systemd-confext</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">COMMAND</arg>
+    </cmdsynopsis>
+
+    <para><literallayout><filename>systemd-confext.service</filename></literallayout></para>
+
   </refsynopsisdiv>
 
   <refsect1>
     service manager supports via <option>RootDirectory=</option>/<option>RootImage=</option>. Similar to
     them they may optionally carry Verity authentication information.</para>
 
-    <para>System extensions are automatically looked for in the directories
-    <filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename>,
-    <filename>/var/lib/extensions/</filename>, <filename>/usr/lib/extensions/</filename> and
-    <filename>/usr/local/lib/extensions/</filename>. The first two listed directories are not suitable for
+    <para>System extensions are searched for in the directories
+    <filename>/etc/extensions/</filename>, <filename>/run/extensions/</filename> and
+    <filename>/var/lib/extensions/</filename>. The first two listed directories are not suitable for
     carrying large binary images, however are still useful for carrying symlinks to them. The primary place
     for installing system extensions is <filename>/var/lib/extensions/</filename>. Any directories found in
-    these search directories are considered directory based extension images, any files with the
-    <filename>.raw</filename> suffix are considered disk image based extension images.</para>
+    these search directories are considered directory based extension images; any files with the
+    <filename>.raw</filename> suffix are considered disk image based extension images. When invoked in the
+    initrd, the additional directory <filename>/.extra/sysext/</filename> is included in the directories that
+    are searched for extension images. Note however, that by default a tighter image policy applies to images
+    found there, though, see below. This directory is populated by
+    <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> with
+    extension images found in the system's EFI System Partition.</para>
 
     <para>During boot OS extension images are activated automatically, if the
     <filename>systemd-sysext.service</filename> is enabled. Note that this service runs only after the
     The <filename>extension-release</filename> file follows the same format and semantics, and carries the same
     content, as the <filename>os-release</filename> file of the OS, but it describes the resources carried
     in the extension image.</para>
+
+    <para>The <command>systemd-confext</command> concept follows the same principle as the
+    <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    functionality but instead of working on <filename>/usr</filename> and <filename>/opt</filename>,
+    <command>confext</command> will extend only <filename>/etc</filename>. Files and directories contained
+    in the confext images outside of the <filename>/etc/</filename> hierarchy are <emphasis>not</emphasis>
+    merged, and hence have no effect when included in the image. Formats for these images are of the
+    same as sysext images. The merged hierarchy will be mounted with <literal>nosuid</literal> and
+    (if not disabled via <option>--noexec=false</option>) <literal>noexec</literal>.</para>
+
+    <para>Confexts are looked for in the directories <filename>/run/confexts/</filename>,
+    <filename>/var/lib/confexts/</filename>, <filename>/usr/lib/confexts/</filename> and
+    <filename>/usr/local/lib/confexts/</filename>. The first two listed directories are not suitable for
+    carrying large binary images, however are still useful for carrying symlinks to them. The primary place
+    for installing system extensions is <filename>/var/lib/confexts/</filename>. Any directories found in
+    these search directories are considered directory based confext images, any files with the
+    <filename>.raw</filename> suffix are considered disk image based confext images.</para>
+
+    <para>Again, just like sysext images, the confext images will contain a
+    <filename>/etc/extension-release.d/extension-release.<replaceable>$name</replaceable></filename>
+    file, which must match the image name (with the usual escape hatch of xattr), and again with content
+    being one or more of <varname>ID=</varname>, <varname>VERSION_ID=</varname>, and
+    <varname>CONFEXT_LEVEL</varname>. Confext images will then be checked and matched against the
+    base OS layer.</para>
   </refsect1>
 
   <refsect1>
     <filename>/usr/</filename> as if it was installed in the OS image itself.) This case works regardless if
     the underlying host <filename>/usr/</filename> is managed as immutable disk image or is a traditional
     package manager controlled (i.e. writable) tree.</para>
-  </refsect1>
+
+    <para>For the confext case, the OSConfig project aims to perform runtime reconfiguration of OS services.
+    Sometimes, there is a need to swap certain configuration parameter values or restart only a specific
+    service without deployment of new code or a complete OS deployment. In other words, we want to be able
+    to tie the most frequently configured options to runtime updateable flags that can be changed without a
+    system reboot. This will help reduce servicing times when there is a need for changing the OS configuration.</para></refsect1>
 
   <refsect1>
     <title>Commands</title>
 
-    <para>The following commands are understood:</para>
+    <para>The following commands are understood by both the sysext and confext concepts:</para>
 
     <variablelist>
       <varlistentry>
         <term><option>status</option></term>
 
         <listitem><para>When invoked without any command verb, or when <option>status</option> is specified
-        the current merge status is shown, separately for both <filename>/usr/</filename> and
-        <filename>/opt/</filename>.</para></listitem>
+        the current merge status is shown, separately (for both <filename>/usr/</filename> and
+        <filename>/opt/</filename> of sysext and for <filename>/etc/</filename> of confext).</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <listitem><para>Merges all currently installed system extension images into
         <filename>/usr/</filename> and <filename>/opt/</filename>, by overmounting these hierarchies with an
         <literal>overlayfs</literal> file system combining the underlying hierarchies with those included in
-        the extension images. This command will fail if the hierarchies are already merged.</para></listitem>
+        the extension images. This command will fail if the hierarchies are already merged. For confext, the merge
+        happens into the <filename>/etc/</filename> directory instead.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>unmerge</option></term>
         <listitem><para>Unmerges all currently installed system extension images from
-        <filename>/usr/</filename> and <filename>/opt/</filename>, by unmounting the
-        <literal>overlayfs</literal> file systems created by <option>merge</option>
+        <filename>/usr/</filename> and <filename>/opt/</filename> for sysext and <filename>/etc/</filename>,
+        for confext, by unmounting the <literal>overlayfs</literal> file systems created by <option>merge</option>
         prior.</para></listitem>
       </varlistentry>
 
         mounted the existing <literal>overlayfs</literal> instance is unmounted temporarily, and then
         replaced by a new version. This command is useful after installing/removing system extension images,
         in order to update the <literal>overlayfs</literal> file system accordingly. If no system extensions
-        are installed when this command is executed, the equivalent of <option>unmerge</option> is
-        executed, without establishing any new <literal>overlayfs</literal> instance. Note that currently
-        there's a brief moment where neither the old nor the new <literal>overlayfs</literal> file system is
-        mounted. This implies that all resources supplied by a system extension will briefly disappear — even
-        if it exists continuously during the refresh operation.</para></listitem>
+        are installed when this command is executed, the equivalent of <option>unmerge</option> is executed,
+        without establishing any new <literal>overlayfs</literal> instance.
+        Note that currently there's a brief moment where neither the old nor the new <literal>overlayfs</literal>
+        file system is mounted. This implies that all resources supplied by a system extension will briefly
+        disappear — even if it exists continuously during the refresh operation.</para></listitem>
       </varlistentry>
 
       <varlistentry>
 
         <listitem><para>Operate relative to the specified root directory, i.e. establish the
         <literal>overlayfs</literal> mount not on the top-level host <filename>/usr/</filename> and
-        <filename>/opt/</filename> hierarchies, but below some specified root directory.</para></listitem>
+        <filename>/opt/</filename> hierarchies for sysext or <filename>/etc/</filename> for confext,
+        but below some specified root directory.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>--force</option></term>
 
         <listitem><para>When merging system extensions into <filename>/usr/</filename> and
-        <filename>/opt/</filename>, ignore version incompatibilities, i.e. force merging regardless of
-        whether the version information included in the extension images matches the host or
-        not.</para></listitem>
+        <filename>/opt/</filename> for sysext and <filename>/etc/</filename> for confext,
+        ignore version incompatibilities, i.e. force merging regardless of
+        whether the version information included in the images matches the host or not.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--image-policy=<replaceable>policy</replaceable></option></term>
+
+        <listitem><para>Takes an image policy string as argument, as per
+        <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
+        policy is enforced when operating on system extension disk images. If not specified defaults to
+        <literal>root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent</literal>
+        for system extensions, i.e. only the root and <filename>/usr/</filename> file systems in the image
+        are used. For configuration extensions defaults to
+        <literal>root=verity+signed+encrypted+unprotected+absent</literal>. When run in the initrd and
+        operating on a system extension image stored in the <filename>/.extra/sysext/</filename> directory a
+        slightly stricter policy is used by default: <literal>root=signed+absent:usr=signed+absent</literal>,
+        see above for details.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--noexec=</option><replaceable>BOOL</replaceable></term>
+
+        <listitem><para>When merging configuration extensions into <filename>/etc/</filename> the
+        <literal>MS_NOEXEC</literal> mount flag is used by default. This option can be used to disable
+        it.</para></listitem>
       </varlistentry>
 
       <xi:include href="standard-options.xml" xpointer="no-pager" />
     <title>See Also</title>
     <para>
       <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
-      <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+      <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>
     </para>
   </refsect1>
 
index 8711be26e34939ae32088cfb58dab911ca915517..1611a71550e70f462ed4bd955ac955b0ad38eb0e 100644 (file)
@@ -30,7 +30,7 @@
     <para><filename>systemd-system-update-generator</filename> is a
     generator that automatically redirects the boot process to
     <filename>system-update.target</filename>, if
-    <filename>/system-update</filename> exists. This is required to
+    <filename>/system-update</filename> or <filename>/etc/system-update</filename> exists. This is required to
     implement the logic explained in the
     <citerefentry><refentrytitle>systemd.offline-updates</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
     </para>
index 77c1635b9d9817e361ff5d9dfbe8070cbe295635..409281c19fa2116ba3996496799aefc23931f3f5 100644 (file)
         inside the specified disk image.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--instances-max=</option></term>
         <term><option>-m</option></term>
index aba275024f96257c60e921dddd241cfce71bd4c4..f7ee5e79d91fe083dd5b29febd72759818e9c4f1 100644 (file)
@@ -80,6 +80,8 @@
         switch of the same name.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--replace=<replaceable>PATH</replaceable></option></term>
         <listitem><para>When this option is given, one or more positional arguments
index c2e32f9f3df4760e2c06b4607e025a85b9ad5c6c..5612b4803d839729ffdb560fd06079cd804b68bd 100644 (file)
         directories marked with <varname>D</varname> or
         <varname>R</varname>, and files or directories themselves
         marked with <varname>r</varname> or <varname>R</varname> are
-        removed.</para></listitem>
+        removed unless an exclusive or shared BSD lock is taken on them (see <citerefentry
+        project='man-pages'><refentrytitle>flock</refentrytitle><manvolnum>2</manvolnum></citerefentry>).
+        </para></listitem>
       </varlistentry>
 
       <varlistentry>
         <para>Implies <option>-E</option>.</para></listitem>
       </varlistentry>
 
+      <xi:include href="standard-options.xml" xpointer="image-policy-open" />
+
       <varlistentry>
         <term><option>--replace=<replaceable>PATH</replaceable></option></term>
         <listitem><para>When this option is given, one or more positional arguments
index 9d8b62c45248aa0af0eebcb02f65d3c3af1cb26c..63673d9dc018552c61925a75b5dadf7aa60e5f39 100644 (file)
@@ -57,7 +57,7 @@
     records as acquired with APIs such as <citerefentry
     project='man-pages'><refentrytitle>getpwnam</refentrytitle><manvolnum>1</manvolnum></citerefentry> to
     JSON user/group records, thus hiding the differences between the services as much as
-    possible. <constant>io.systemd.Dropin</constant> makes JSON user/group records from the aforementioned
+    possible. <constant>io.systemd.DropIn</constant> makes JSON user/group records from the aforementioned
     drop-in directories available.</para>
   </refsect1>
 
index 37ded91a936dd5f8898f09fb30aca778a5ef86c0..71bc1fda64b81a90d3d13ec4965de870741985de 100644 (file)
         <term><varname>systemd.verity_root_options=</varname></term>
 
         <listitem><para>Takes a comma-separated list of dm-verity options. Expects the following options
+        <option>superblock=<replaceable>BOOLEAN</replaceable></option>,
+        <option>format=<replaceable>NUMBER</replaceable></option>,
+        <option>data-block-size=<replaceable>BYTES</replaceable></option>,
+        <option>hash-block-size=<replaceable>BYTES</replaceable></option>,
+        <option>data-blocks=<replaceable>BLOCKS</replaceable></option>,
+        <option>hash-offset=<replaceable>BYTES</replaceable></option>,
+        <option>salt=<replaceable>HEX</replaceable></option>, <option>uuid=<replaceable>UUID</replaceable></option>,
         <option>ignore-corruption</option>, <option>restart-on-corruption</option>, <option>ignore-zero-blocks</option>,
-        <option>check-at-most-once</option>, <option>panic-on-corruption</option> and
+        <option>check-at-most-once</option>, <option>panic-on-corruption</option>,
+        <option>hash=<replaceable>HASH</replaceable></option>, <option>fec-device=<replaceable>PATH</replaceable></option>,
+        <option>fec-offset=<replaceable>BYTES</replaceable></option>, <option>fec-roots=<replaceable>NUM</replaceable></option> and
         <option>root-hash-signature=<replaceable>PATH</replaceable>|base64:<replaceable>HEX</replaceable></option>. See
         <citerefentry project='die-net'><refentrytitle>veritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> for more
         details.</para></listitem>
index 4001123a96242abfe9bc5b11ed1846a0c3b4ed5c..795e26e792a854cb59459ada1a33c665714bd71f 100644 (file)
         <xi:include href="system-only.xml" xpointer="singular"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>RootImagePolicy=</varname></term>
+        <term><varname>MountImagePolicy=</varname></term>
+        <term><varname>ExtensionImagePolicy=</varname></term>
+
+        <listitem><para>Takes an image policy string as per
+        <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+        to use when mounting the disk images (DDI) specified in <varname>RootImage=</varname>,
+        <varname>MountImage=</varname>, <varname>ExtensionImage=</varname>, respectively. If not specified
+        the following policy string is the default for <varname>RootImagePolicy=</varname> and <varname>MountImagePolicy</varname>:</para>
+
+        <programlisting>root=verity+signed+encrypted+unprotected+absent: \
+        usr=verity+signed+encrypted+unprotected+absent: \
+        home=encrypted+unprotected+absent: \
+        srv=encrypted+unprotected+absent: \
+        tmp=encrypted+unprotected+absent: \
+        var=encrypted+unprotected+absent</programlisting>
+
+        <para>The default policy for <varname>ExtensionImagePolicy=</varname> is:</para>
+
+        <programlisting>root=verity+signed+encrypted+unprotected+absent: \
+        usr=verity+signed+encrypted+unprotected+absent</programlisting></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>MountAPIVFS=</varname></term>
 
@@ -2350,6 +2374,10 @@ RestrictNamespaces=~cgroup net</programlisting>
                 <entry>@obsolete</entry>
                 <entry>Unusual, obsolete or unimplemented (<citerefentry project='man-pages'><refentrytitle>create_module</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>gtty</refentrytitle><manvolnum>2</manvolnum></citerefentry>, …)</entry>
               </row>
+              <row>
+                <entry>@pkey</entry>
+                <entry>System calls that deal with memory protection keys (<citerefentry project='man-pages'><refentrytitle>pkeys</refentrytitle><manvolnum>7</manvolnum></citerefentry>)</entry>
+              </row>
               <row>
                 <entry>@privileged</entry>
                 <entry>All system calls which need super-user capabilities (<citerefentry project='man-pages'><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>)</entry>
@@ -2370,6 +2398,10 @@ RestrictNamespaces=~cgroup net</programlisting>
                 <entry>@resources</entry>
                 <entry>System calls for changing resource limits, memory and scheduling parameters (<citerefentry project='man-pages'><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setpriority</refentrytitle><manvolnum>2</manvolnum></citerefentry>, …)</entry>
               </row>
+              <row>
+                <entry>@sandbox</entry>
+                <entry>System calls for sandboxing programs (<citerefentry project='man-pages'><refentrytitle>seccomp</refentrytitle><manvolnum>2</manvolnum></citerefentry>, Landlock system calls, …)</entry>
+              </row>
               <row>
                 <entry>@setuid</entry>
                 <entry>System calls for changing user ID and group ID credentials, (<citerefentry project='man-pages'><refentrytitle>setuid</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setgid</refentrytitle><manvolnum>2</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setresuid</refentrytitle><manvolnum>2</manvolnum></citerefentry>, …)</entry>
@@ -3468,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>
@@ -3791,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
diff --git a/man/systemd.image-policy.xml b/man/systemd.image-policy.xml
new file mode 100644 (file)
index 0000000..5ea9e46
--- /dev/null
@@ -0,0 +1,191 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd.image-policy">
+
+  <refentryinfo>
+    <title>systemd.image-policy</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd.image-policy</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd.image-policy</refname>
+    <refpurpose>Disk Image Dissection Policy</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para>In systemd, whenever a disk image (DDI) implementing the <ulink
+    url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
+    Partitions Specification</ulink> is activated, a policy may be specified controlling which partitions to
+    mount and what kind of cryptographic protection to require. Such a disk image dissection policy is a
+    string that contains per-partition-type rules, separated by colons (<literal>:</literal>). The individual
+    rules consist of a partition identifier, an equal sign (<literal>=</literal>), and one or more flags
+    which may be set per partition. If multiple flags are specified per partition they are separated by a
+    plus sign (<literal>+</literal>).</para>
+
+    <para>The partition identifiers currently defined are: <option>root</option>, <option>usr</option>,
+    <option>home</option>, <option>srv</option>, <option>esp</option>, <option>xbootldr</option>,
+    <option>swap</option>, <option>root-verity</option>, <option>root-verity-sig</option>,
+    <option>usr-verity</option>, <option>usr-verity-sig</option>, <option>tmp</option>,
+    <option>var</option>. These identifiers match the relevant partition types in the Discoverable Partitions
+    Specification, but are agnostic to CPU architectures. If the partition identifier is left empty it
+    defines the <emphasis>default</emphasis> policy for partitions defined in the Discoverable Partitions
+    Specification for which no policy flags are explicitly listed in the policy string.</para>
+
+    <para>The following partition policy flags are defined that dictate the existence/absence, the use, and
+    the protection level of partitions:</para>
+
+    <itemizedlist>
+      <listitem><para><option>unprotected</option> for partitions that shall exist and be used, but shall
+      come without cryptographic protection, lacking both Verity authentication and LUKS
+      encryption.</para></listitem>
+
+      <listitem><para><option>verity</option> for partitions that shall exist and be used, with Verity
+      authentication. (Note: if a DDI image carries a data partition, along with a Verity partition and a
+      signature partition for it, and only the <option>verity</option> flag is set – and
+      <option>signed</option> is not –, then the image will be set up with Verity, but the signature data will
+      not be used. Or in other words: any DDI with a set of partitions that qualify for
+      <option>signature</option> also implicitly qualifies for <option>verity</option>, and in fact
+      <option>unprotected</option>).</para></listitem>
+
+      <listitem><para><option>signed</option> for partitions that shall exist and be used, with Verity
+      authentication, which are also accompanied by a PKCS#7 signature of the Verity root
+      hash.</para></listitem>
+
+      <listitem><para><option>encrypted</option> for partitions which shall exist and be used and are
+      encrypted with LUKS.</para></listitem>
+
+      <listitem><para><option>unused</option> for partitions that shall exist but shall not be
+      used.</para></listitem>
+
+      <listitem><para><option>absent</option> for partitions that shall not exist on the
+      image.</para></listitem>
+    </itemizedlist>
+
+    <para>By setting a combination of the flags above, alternatives can be declared. For example the
+    combination <literal>unused+absent</literal> means: the partition may exist (in which case it shall not
+    be used) or may be absent. The combination of
+    <literal>unprotected+verity+signed+encrypted+unused+absent</literal> may be specified via the special
+    shortcut <literal>open</literal>, and indicates that the partition may exist or may be absent, but if it
+    exists is used, regardless of the protection level.</para>
+
+    <para>As special rule: if none of the flags above are set for a listed partition identifier, the default
+    policy of <option>open</option> is implied, i.e. setting none of these flags listed above means
+    effectively all flags listed above will be set.</para>
+
+    <para>The following partition policy flags are defined that dictate the state of specific GPT partition
+    flags:</para>
+
+    <itemizedlist>
+      <listitem><para><option>read-only-off</option>, <option>read-only-on</option> to require that the
+      partitions have the read-only partition flag off or on.</para></listitem>
+
+      <listitem><para><option>growfs-off</option>, <option>growfs-on</option> to require that the
+      partitions have the growfs partition flag off or on.</para></listitem>
+    </itemizedlist>
+
+    <para>If both <option>read-only-off</option> and <option>read-only-on</option> are set for a partition,
+    then the state of the read-only flag on the partition is not dictated by the policy. Setting neither flag
+    is equivalent to setting both, i.e. setting neither of these two flags means effectively both will be
+    set. A similar logic applies to <option>growfs-off</option>/<option>growfs-on</option>.</para>
+
+    <para>If partitions are not listed within an image policy string, the default policy flags are applied
+    (configurable via an empty partition identifier, see above). If no default policy flags are configured in
+    the policy string, it is implied to be <literal>absent+unused</literal>, except for the Verity partition
+    and their signature partitions where the policy is automatically derived from minimal protection level of
+    the data partition they protect, as encoded in the policy.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Special Policies</title>
+
+    <para>The special image policy string <literal>*</literal> is short for "use everything", i.e. is
+    equivalent to:</para>
+
+    <programlisting>=verity+signed+encrypted+unprotected+unused+absent</programlisting>
+
+    <para>The special image policy string <literal>-</literal> is short for "use nothing", i.e. is equivalent
+    to:</para>
+
+    <programlisting>=unused+absent</programlisting>
+
+    <para>The special image policy string <literal>~</literal> is short for "everything must be absent",
+    i.e. is equivalent to:</para>
+
+    <programlisting>=absent</programlisting>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Use</title>
+
+    <para>Most systemd components that support operating with disk images support a
+    <option>--image-policy=</option> command line option to specify the image policy to use, and default to
+    relatively open policies by default (typically the <literal>*</literal> policy, as described above),
+    under the assumption that trust in disk images is established before the images are passed to the program
+    in question.</para>
+
+    <para>For the host image itself
+    <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    is responsible for processing the GPT partition table and making use of the included discoverable
+    partitions. It accepts an image policy via the kernel command line option
+    <option>systemd.image-policy=</option>.</para>
+
+    <para>Note that image policies do not dictate how the components will mount and use disk images — they
+    only dictate which parts to avoid and which protection level and arrangement to require while
+    mounting/using them. For example,
+    <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> only
+    cares for the <filename>/usr/</filename> and <filename>/opt/</filename> trees inside a disk image, and
+    thus ignores any <filename>/home/</filename> partitions (and similar) in all cases, which might be
+    included in the image, regardless whether the configured image policy would allow access to it or
+    not. Similar,
+    <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> is not
+    going to make use of any discovered swap device, regardless if the policy would allow that or not.</para>
+
+    <para>Use the <command>image-policy</command> command of the
+    <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>8</manvolnum></citerefentry> tool
+    to analyze image policy strings, and determine what a specific policy string means for a specific
+    partition.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Examples</title>
+
+    <para>The following image policy string dictates one read-only Verity-enabled <filename>/usr/</filename>
+    partition must exist, plus encrypted root and swap partitions. All other partitions are ignored:</para>
+
+    <programlisting>usr=verity+read-only-on:root=encrypted:swap=encrypted</programlisting>
+
+    <para>The following image policy string dictates an encrypted, writable root file system, and optional
+    <filename>/srv/</filename> file system that must be encrypted if it exists and no swap partition may
+    exist:</para>
+
+    <programlisting>root=encrypted+read-only-off:srv=encrypted+absent:swap=absent</programlisting>
+
+    <para>The following image policy string dictates a single root partition that may be encrypted, but
+    doesn't have to be, and ignores swap partitions, and uses all other partitions if they are available, possibly with encryption.</para>
+
+    <programlisting>root=unprotected+encrypted:swap=absent+unused:=unprotected+encrypted+absent</programlisting>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index 546a6a006b20e1ba333f454abc71e307a31a98fd..cc851d31f98a710b276f17d971ea897059a4f9d7 100644 (file)
@@ -1143,7 +1143,7 @@ Name=dmz0</programlisting>
     <example>
       <title>(Re-)applying a .link file to an interface</title>
 
-      <para>After a new .link file has been created, or an exisiting .link file modified, the new settings
+      <para>After a new .link file has been created, or an existing .link file modified, the new settings
       may be applied to the matching interface with the following commands:</para>
 
       <programlisting>$ sudo udevadm control --reload
index b71cedfa269e87ea983e1a021eab5d68ffc18779..7e6a52c711d05ace1a08f982bcac277babac90e3 100644 (file)
       <varlistentry>
         <term><varname>InheritInnerProtocol=</varname></term>
         <listitem>
-          <para>Takes a boolean. When true, inner Layer 3 protcol is set as Protocol Type in the GENEVE
+          <para>Takes a boolean. When true, inner Layer 3 protocol is set as Protocol Type in the GENEVE
           header instead of Ethernet. Defaults to false.</para>
         </listitem>
       </varlistentry>
         <term><varname>Endpoint=</varname></term>
         <listitem>
           <para>Sets an endpoint IP address or hostname, followed by a colon, and then
-          a port number. This endpoint will be updated automatically once to
+          a port number. IPv6 address must be in the square brackets. For example,
+          <literal>111.222.333.444:51820</literal> for IPv4 and <literal>[1111:2222::3333]:51820</literal>
+          for IPv6 address. This endpoint will be updated automatically once to
           the most recent source IP address and port of correctly
           authenticated packets from the peer at configuration time.</para>
         </listitem>
index c1eef7853b642dcb136e26cbb16f312ad3eba4de..ec94176c01879fb7e730ae54172d1ba7430e4d24 100644 (file)
       <varlistentry>
         <term><varname>Interface=</varname></term>
 
-        <listitem><para>Takes a space-separated list of interfaces to
-        add to the container. This option corresponds to the
+        <listitem><para>Takes a space-separated list of interfaces to add to the container.
+        The interface object is defined either by a single interface name, referencing the name on the host,
+        or a colon-separated pair of interfaces, in which case the first one references the name on the host,
+        and the second one the name in the container.
+        This option corresponds to the
         <option>--network-interface=</option> command line switch and
         implies <varname>Private=yes</varname>. This option is
         privileged (see above).</para></listitem>
 
         <listitem><para>Takes a space-separated list of interfaces to
         add MACLVAN or IPVLAN interfaces to, which are then added to
-        the container. These options correspond to the
+        the container. The interface object is defined either by a single interface name, referencing the name
+        on the host, or a colon-separated pair of interfaces, in which case the first one references the name
+        on the host, and the second one the name in the container. These options correspond to the
         <option>--network-macvlan=</option> and
         <option>--network-ipvlan=</option> command line switches and
         imply <varname>Private=yes</varname>. These options are
index 67064517666bd040c7db5cdc1dc3bd8c96287634..7285f9e2638598483989ac5d58bb32fe17f4bd01 100644 (file)
       </listitem>
 
       <listitem>
-        <para>When the user OK'ed the update, the symlink <filename>/system-update</filename> is
-        created that points to <filename index="false">/var/lib/system-update</filename> (or
-        wherever the directory with the upgrade files is located) and the system is rebooted. This
-        symlink is in the root directory, since we need to check for it very early at boot, at a
-        time where <filename>/var/</filename> is not available yet.</para>
+        <para>When the user OK'ed the update, the symlink <filename>/system-update</filename> or
+        <filename>/etc/system-update</filename> is created that points to
+        <filename index="false">/var/lib/system-update</filename> (or wherever the directory with
+        the upgrade files is located) and the system is rebooted. This symlink is in the root
+        directory, since we need to check for it very early at boot, at a time where
+        <filename>/var/</filename> is not available yet.</para>
       </listitem>
 
       <listitem>
         <para>Very early in the new boot
         <citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-        checks whether <filename>/system-update</filename> exists. If so, it (temporarily and for
-        this boot only) redirects (i.e. symlinks) <filename>default.target</filename> to
+        checks whether <filename>/system-update</filename> or
+        <filename>/etc/system-update</filename> exists. If so, it (temporarily and for this boot
+        only) redirects (i.e. symlinks) <filename>default.target</filename> to
         <filename>system-update.target</filename>, a special target that pulls in the base system
         (i.e. <filename>sysinit.target</filename>, so that all file systems are mounted but little
         else) and the system update units.</para>
 
       <listitem>
         <para>As the first step, an update service should check if the
-        <filename>/system-update</filename> symlink points to the location used by that update
-        service. In case it does not exist or points to a different location, the service must exit
-        without error. It is possible for multiple update services to be installed, and for multiple
-        update services to be launched in parallel, and only the one that corresponds to the tool
-        that <emphasis>created</emphasis> the symlink before reboot should perform any actions. It
-        is unsafe to run multiple updates in parallel.</para>
+        <filename>/system-update</filename> or <filename>/etc/system-update</filename> symlink
+        points to the location used by that update service. In case it does not exist or points to a
+        different location, the service must exit without error. It is possible for multiple update
+        services to be installed, and for multiple update services to be launched in parallel, and
+        only the one that corresponds to the tool that <emphasis>created</emphasis> the symlink
+        before reboot should perform any actions. It is unsafe to run multiple updates in
+        parallel.</para>
       </listitem>
 
       <listitem>
         <para>The update scripts should exit only after the update is finished. It is expected
         that the service which performs the update will cause the machine to reboot after it
         is done. If the <filename>system-update.target</filename> is successfully reached, i.e.
-        all update services have run, and the <filename>/system-update</filename> symlink still
-        exists, it will be removed and the machine rebooted as a safety measure.</para>
+        all update services have run, and the <filename>/system-update</filename> or
+        <filename>/etc/system-update</filename> symlink still exists, it will be removed and
+        the machine rebooted as a safety measure.</para>
       </listitem>
 
       <listitem>
-        <para>After a reboot, now that the <filename>/system-update</filename> symlink is gone,
-        the generator won't redirect <filename>default.target</filename> anymore and the system
-        now boots into the default target again.</para>
+        <para>After a reboot, now that the <filename>/system-update</filename> and
+        <filename>/etc/system-update</filename> symlink is gone, the generator won't redirect
+        <filename>default.target</filename> anymore and the system now boots into the default
+        target again.</para>
       </listitem>
     </orderedlist>
   </refsect1>
       </listitem>
 
       <listitem>
-        <para>Make sure to remove the <filename>/system-update</filename> symlink as early as
-        possible in the update script to avoid reboot loops in case the update fails.</para>
+        <para>Make sure to remove the <filename>/system-update</filename> and
+        <filename>/etc/system-update</filename> symlinks as early as possible in the update
+        script to avoid reboot loops in case the update fails.</para>
       </listitem>
 
       <listitem>
index ab730d2cc21504a9033b5675e3da6dfac4d29e7a..5d46a88c3e94f41c158727d3f2e6c8260ea01c64 100644 (file)
   <refsect1>
     <title>Preset File Format</title>
 
-    <para>The preset files contain a list of directives consisting of
-    either the word <literal>enable</literal> or
-    <literal>disable</literal> followed by a space and a unit name
-    (possibly with shell style wildcards), separated by newlines.
-    Empty lines and lines whose first non-whitespace character is <literal>#</literal> or
-    <literal>;</literal> are ignored. Multiple instance names for unit
-    templates may be specified as a space separated list at the end of
-    the line instead of the customary position between <literal>@</literal>
-    and the unit suffix.</para>
+    <para>The preset files contain a list of directives, one per line. Empty lines and lines whose first
+    non-whitespace character is <literal>#</literal> or <literal>;</literal> are ignored.  Each directive
+    consists of one of the words <literal>enable</literal>, <literal>disable</literal>, or
+    <literal>ignore</literal>, followed by whitespace and a unit name. The unit name may contain shell-style
+    wildcards.</para>
+
+    <para>For the enable directive for template units, one or more instance names may be specified as a
+    space-separated list after the unit name. In this case, those instances will be enabled instead of the
+    instance specified via DefaultInstance= in the unit.</para>
 
     <para>Presets must refer to the "real" unit file, and not to any aliases. See
     <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
     for a description of unit aliasing.</para>
 
-    <para>Two different directives are understood:
-    <literal>enable</literal> may be used to enable units by default,
-    <literal>disable</literal> to disable units by default.</para>
+    <para>Three different directives are understood: <literal>enable</literal> may be used to enable units by
+    default, <literal>disable</literal> to disable units by default, and <literal>ignore</literal> to ignore
+    units and leave existing configuration intact.</para>
 
     <para>If multiple lines apply to a unit name, the first matching
     one takes precedence over all others.</para>
index f24822e605f8d6b8dc93cb11199561914cb11c11..f4e4a492a000e86a0136e234e9862b47295d5a17 100644 (file)
@@ -112,7 +112,7 @@ CPUWeight=20   DisableControllers=cpu              /          \
         configuration for <filename>system.slice</filename> and <filename>user.slice</filename>, CPU
         resources will be split equally between them. Similarly, resources are allocated equally between
         children of <filename>user.slice</filename> and between the child slices beneath
-        <filename>user@1000.service</filename>. Assuming that there is no futher configuration of resources
+        <filename>user@1000.service</filename>. Assuming that there is no further configuration of resources
         or delegation below slices <filename>app.slice</filename> or <filename>session.slice</filename>, the
         <option>cpu</option> controller would not be enabled for units in those slices and CPU resources
         would be further allocated using other mechanisms, e.g. based on nice levels. The manager for user
index a1a32e8cd905469ccfd1ecf0c385231a94b06017..7de1350a5935c2e102761d2427a298c4c46e0d93 100644 (file)
 
       <varlistentry>
         <term><varname>ExecStart=</varname></term>
-        <listitem><para>Commands with their arguments that are
-        executed when this service is started. The value is split into
-        zero or more command lines according to the rules described
-        below (see section "Command Lines" below).
-        </para>
+        <listitem><para>Commands that are executed when this service is started. The value is split into zero
+        or more command lines according to the rules described in the section "Command Lines" below.</para>
 
         <para>Unless <varname>Type=</varname> is <option>oneshot</option>, exactly one command must be given. When
         <varname>Type=oneshot</varname> is used, zero or more commands may be specified. Commands may be specified by
         <varname>ExecStop=</varname> line set. (Services lacking both <varname>ExecStart=</varname> and
         <varname>ExecStop=</varname> are not valid.)</para>
 
-        <para>For each of the specified commands, the first argument must be either an absolute path to an executable
-        or a simple file name without any slashes. Optionally, this filename may be prefixed with a number of special
-        characters:</para>
-
-        <table>
-          <title>Special executable prefixes</title>
-
-          <tgroup cols='2'>
-            <colspec colname='prefix'/>
-            <colspec colname='meaning'/>
-
-            <thead>
-              <row>
-                <entry>Prefix</entry>
-                <entry>Effect</entry>
-              </row>
-            </thead>
-            <tbody>
-              <row>
-                <entry><literal>@</literal></entry>
-                <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <literal>argv[0]</literal> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
-              </row>
-
-              <row>
-                <entry><literal>-</literal></entry>
-                <entry>If the executable path is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success.</entry>
-              </row>
-
-              <row>
-                <entry><literal>:</literal></entry>
-                <entry>If the executable path is prefixed with <literal>:</literal>, environment variable substitution (as described by the "Command Lines" section below) is not applied.</entry>
-              </row>
-
-              <row>
-                <entry><literal>+</literal></entry>
-                <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
-              </row>
-
-              <row>
-                <entry><literal>!</literal></entry>
-
-                <entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
-              </row>
-
-              <row>
-                <entry><literal>!!</literal></entry>
-
-                <entry>This prefix is very similar to <literal>!</literal>, however it only has an effect on systems lacking support for ambient process capabilities, i.e. without support for <varname>AmbientCapabilities=</varname>. It's intended to be used for unit files that take benefit of ambient capabilities to run processes with minimal privileges wherever possible while remaining compatible with systems that lack ambient capabilities support. Note that when <literal>!!</literal> is used, and a system lacking ambient capability support is detected any configured <varname>SystemCallFilter=</varname> and <varname>CapabilityBoundingSet=</varname> stanzas are implicitly modified, in order to permit spawned processes to drop credentials and capabilities themselves, even if this is configured to not be allowed. Moreover, if this prefix is used and a system lacking ambient capability support is detected <varname>AmbientCapabilities=</varname> will be skipped and not be applied. On systems supporting ambient capabilities, <literal>!!</literal> has no effect and is redundant.</entry>
-              </row>
-            </tbody>
-          </tgroup>
-        </table>
-
-        <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
-        <literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
-        order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
-        time. Note that these prefixes are also supported for the other command line settings,
-        i.e. <varname>ExecStartPre=</varname>, <varname>ExecStartPost=</varname>, <varname>ExecReload=</varname>,
-        <varname>ExecStop=</varname> and <varname>ExecStopPost=</varname>.</para>
-
         <para>If more than one command is specified, the commands are
         invoked sequentially in the order they appear in the unit
         file. If one of the commands fails (and is not prefixed with
         as "5min 20s". Defaults to 100ms.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>RestartSteps=</varname></term>
+        <listitem><para>Configures the number of steps to take to increase the interval
+        of auto-restarts from <varname>RestartSec=</varname> to <varname>RestartSecMax=</varname>.
+        Takes a positive integer or 0 to disable it. Defaults to 0.</para>
+
+        <para>This setting is effective only if <varname>RestartSecMax=</varname> is also set.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>RestartSecMax=</varname></term>
+        <listitem><para>Configures the longest time to sleep before restarting a service
+        as the interval goes up with <varname>RestartSteps=</varname>. Takes a value
+        in the same format as <varname>RestartSec=</varname>, or <literal>infinity</literal>
+        to disable the setting. Defaults to <literal>infinity</literal>.</para>
+
+        <para>This setting is effective only if <varname>RestartSteps=</varname> is also set.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>TimeoutStartSec=</varname></term>
         <listitem><para>Configures the time to wait for start-up. If a daemon service does not signal
         <literal>FDSTORE=1</literal> messages. This is useful for implementing services that can restart
         after an explicit request or a crash without losing state. Any open sockets and other file
         descriptors which should not be closed during the restart may be stored this way. Application state
-        can either be serialized to a file in <filename>/run/</filename>, or better, stored in a
+        can either be serialized to a file in <varname>RuntimeDirectory=</varname>, or stored in a
         <citerefentry><refentrytitle>memfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry>
         memory file descriptor. Defaults to 0, i.e. no file descriptors may be stored in the service
         manager. All file descriptors passed to the service manager from a specific service are passed back
         details about the precise protocol used and the order in which the file descriptors are passed). Any
         file descriptors passed to the service manager are automatically closed when
         <constant>POLLHUP</constant> or <constant>POLLERR</constant> is seen on them, or when the service is
-        fully stopped and no job is queued or being executed for it. If this option is used,
+        fully stopped and no job is queued or being executed for it (the latter can be tweaked with
+        <varname>FileDescriptorStorePreserve=</varname>, see below). If this option is used,
         <varname>NotifyAccess=</varname> (see above) should be set to open access to the notification socket
         provided by systemd. If <varname>NotifyAccess=</varname> is not set, it will be implicitly set to
-        <option>main</option>.</para></listitem>
+        <option>main</option>.</para>
+
+        <para>The <command>fdstore</command> command of
+        <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        may be used to list the current contents of a service's file descriptor store.</para>
+
+        <para>Note that the service manager will only pass file descriptors contained in the file descriptor
+        store to the service's own processes, never to other clients via IPC or similar. However, it does
+        allow unprivileged clients to query the list of currently open file descriptors of a
+        service. Sensitive data may hence be safely placed inside the referenced files, but should not be
+        attached to the metadata (e.g. included in filenames) of the stored file
+        descriptors.</para>
+
+        <para>If this option is set to a non-zero value the <varname>$FDSTORE</varname> environment variable
+        will be set for processes invoked for this service. See
+        <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+        details.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>FileDescriptorStorePreserve=</varname></term>
+        <listitem><para>Takes one of <constant>no</constant>, <constant>yes</constant>,
+        <constant>restart</constant> and controls when to release the service's file descriptor store
+        (i.e. when to close the contained file descriptors, if any). If set to <constant>no</constant> the
+        file descriptor store is automatically released when the service is stopped; if
+        <constant>restart</constant> (the default) it is kept around as long as the unit is neither inactive
+        nor failed, or a job is queued for the service, or the service is expected to be restarted. If
+        <constant>yes</constant> the file descriptor store is kept around until the unit is removed from
+        memory (i.e. is not referenced anymore and inactive). The latter is useful to keep entries in the
+        file descriptor store pinned until the service manage exits.</para>
+
+        <para>Use <command>systemctl clean --what=fdstore …</command> to release the file descriptor store
+        explicitly.</para></listitem>
       </varlistentry>
 
       <varlistentry>
 
     <para>The command to execute may contain spaces, but control characters are not allowed.</para>
 
+    <para>Each command may be prefixed with a number of special characters:</para>
+
+    <table>
+      <title>Special executable prefixes</title>
+
+      <tgroup cols='2'>
+        <colspec colname='prefix'/>
+        <colspec colname='meaning'/>
+
+        <thead>
+          <row>
+            <entry>Prefix</entry>
+            <entry>Effect</entry>
+          </row>
+        </thead>
+        <tbody>
+          <row>
+            <entry><literal>@</literal></entry>
+            <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <constant>argv[0]</constant> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
+          </row>
+
+          <row>
+            <entry><literal>-</literal></entry>
+            <entry>If the executable path is prefixed with <literal>-</literal>, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success.</entry>
+          </row>
+
+          <row>
+            <entry><literal>:</literal></entry>
+            <entry>If the executable path is prefixed with <literal>:</literal>, environment variable substitution (as described by the "Command Lines" section below) is not applied.</entry>
+          </row>
+
+          <row>
+            <entry><literal>+</literal></entry>
+            <entry>If the executable path is prefixed with <literal>+</literal> then the process is executed with full privileges. In this mode privilege restrictions configured with <varname>User=</varname>, <varname>Group=</varname>, <varname>CapabilityBoundingSet=</varname> or the various file system namespacing options (such as <varname>PrivateDevices=</varname>, <varname>PrivateTmp=</varname>) are not applied to the invoked command line (but still affect any other <varname>ExecStart=</varname>, <varname>ExecStop=</varname>, … lines). However, note that this will not bypass options that apply to the whole control group, such as <varname>DevicePolicy=</varname>, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the full list.</entry>
+          </row>
+
+          <row>
+            <entry><literal>!</literal></entry>
+
+            <entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
+          </row>
+
+          <row>
+            <entry><literal>!!</literal></entry>
+
+            <entry>This prefix is very similar to <literal>!</literal>, however it only has an effect on systems lacking support for ambient process capabilities, i.e. without support for <varname>AmbientCapabilities=</varname>. It's intended to be used for unit files that take benefit of ambient capabilities to run processes with minimal privileges wherever possible while remaining compatible with systems that lack ambient capabilities support. Note that when <literal>!!</literal> is used, and a system lacking ambient capability support is detected any configured <varname>SystemCallFilter=</varname> and <varname>CapabilityBoundingSet=</varname> stanzas are implicitly modified, in order to permit spawned processes to drop credentials and capabilities themselves, even if this is configured to not be allowed. Moreover, if this prefix is used and a system lacking ambient capability support is detected <varname>AmbientCapabilities=</varname> will be skipped and not be applied. On systems supporting ambient capabilities, <literal>!!</literal> has no effect and is redundant.</entry>
+          </row>
+        </tbody>
+      </tgroup>
+    </table>
+
+    <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
+    <literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
+    order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
+    time.</para>
+
+    <para>For each command, the first argument must be either an absolute path to an executable or a simple
+    file name without any slashes. If the command is not a full (absolute) path, it will be resolved to a
+    full path using a fixed search path determined at compilation time. Searched directories include
+    <filename>/usr/local/bin/</filename>, <filename>/usr/bin/</filename>, <filename>/bin/</filename> on
+    systems using split <filename>/usr/bin/</filename> and <filename>/bin/</filename> directories, and their
+    <filename>sbin/</filename> counterparts on systems using split <filename>bin/</filename> and
+    <filename>sbin/</filename>. It is thus safe to use just the executable name in case of executables
+    located in any of the "standard" directories, and an absolute path must be used in other cases. Using an
+    absolute path is recommended to avoid ambiguity. Hint: this search path may be queried using
+    <command>systemd-path search-binaries-default</command>.</para>
+
     <para>The command line accepts <literal>%</literal> specifiers as described in
     <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
 
     For this type of expansion, quotes are respected when splitting
     into words, and afterwards removed.</para>
 
-    <para>If the command is not a full (absolute) path, it will be resolved to a full path using a
-    fixed search path determined at compilation time. Searched directories include
-    <filename>/usr/local/bin/</filename>, <filename>/usr/bin/</filename>, <filename>/bin/</filename>
-    on systems using split <filename>/usr/bin/</filename> and <filename>/bin/</filename>
-    directories, and their <filename>sbin/</filename> counterparts on systems using split
-    <filename>bin/</filename> and <filename>sbin/</filename>. It is thus safe to use just the
-    executable name in case of executables located in any of the "standard" directories, and an
-    absolute path must be used in other cases. Using an absolute path is recommended to avoid
-    ambiguity. Hint: this search path may be queried using
-    <command>systemd-path search-binaries-default</command>.</para>
-
     <para>Example:</para>
 
     <programlisting>Environment="ONE=one" 'TWO=two two'
@@ -1341,6 +1386,17 @@ ExecStart=/bin/echo $ONE $TWO $THREE</programlisting>
 
     <para>Example:</para>
 
+    <programlisting>Type=oneshot
+ExecStart=:echo $USER ; -false ; +:@true $TEST</programlisting>
+
+    <para>This will execute <command>/usr/bin/echo</command> with the literal argument
+    <literal>$USER</literal> (<literal>:</literal> suppresses variable expansion), and then
+    <command>/usr/bin/false</command> (the return value will be ignored because <literal>-</literal>
+    suppresses checking of the return value), and <command>/usr/bin/true</command> (with elevated privileges,
+    with <literal>$TEST</literal> as <constant>argv[0]</constant>).</para>
+
+    <para>Example:</para>
+
     <programlisting>ExecStart=echo / &gt;/dev/null &amp; \; \
 ls</programlisting>
 
index 85eb8ad0762ab3dcc03eb7894b60b07bee58f59b..16208955110cbd0b71f264372dc1fbf9d7913bfb 100644 (file)
           <listitem>
             <para>A special target unit that is used for offline system updates.
             <citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-            will redirect the boot process to this target if <filename>/system-update</filename>
-            exists. For more information see
+            will redirect the boot process to this target if <filename>/system-update</filename> or
+            <filename>/etc/system-update</filename> exists. For more information see
             <citerefentry><refentrytitle>systemd.offline-updates</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
             </para>
 
             <filename>system-update-pre.target</filename> but not pull it in. Services which want to
             run during system updates only, but before the actual system update is executed should
             order themselves before this unit and pull it in. As a safety measure, if this does not
-            happen, and <filename>/system-update</filename> still exists after
+            happen, and <filename>/system-update</filename> or
+            <filename>/etc/system-update</filename> still exists after
             <filename>system-update.target</filename> is reached,
-            <filename>system-update-cleanup.service</filename> will remove this symlink and reboot
+            <filename>system-update-cleanup.service</filename> will remove the symlinks and reboot
             the machine.</para>
           </listitem>
         </varlistentry>
index 556fcad3a44c801a511e0d045e3769ea3321b06d..c618e403f71ed8b9a62de7f71af33316174ad7ff 100644 (file)
     <para>Unit files are loaded from a set of paths determined during compilation, described in the next
     section.</para>
 
-    <para>Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type. The
-    "unit prefix" must consist of one or more valid characters (ASCII letters, digits, <literal>:</literal>,
-    <literal>-</literal>, <literal>_</literal>, <literal>.</literal>, and <literal>\</literal>). The total
-    length of the unit name including the suffix must not exceed 256 characters. The type suffix must be one
-    of <literal>.service</literal>, <literal>.socket</literal>, <literal>.device</literal>,
-    <literal>.mount</literal>, <literal>.automount</literal>, <literal>.swap</literal>,
-    <literal>.target</literal>, <literal>.path</literal>, <literal>.timer</literal>,
-    <literal>.slice</literal>, or <literal>.scope</literal>.</para>
-
-    <para>Units names can be parameterized by a single argument called the "instance name". The unit is then
+    <para>Valid unit names consist of a "unit name prefix", and a suffix specifying the unit type which
+    begins with a dot. The "unit name prefix" must consist of one or more valid characters (ASCII letters,
+    digits, <literal>:</literal>, <literal>-</literal>, <literal>_</literal>, <literal>.</literal>, and
+    <literal>\</literal>). The total length of the unit name including the suffix must not exceed 255
+    characters. The unit type suffix must be one of <literal>.service</literal>, <literal>.socket</literal>,
+    <literal>.device</literal>, <literal>.mount</literal>, <literal>.automount</literal>,
+    <literal>.swap</literal>, <literal>.target</literal>, <literal>.path</literal>,
+    <literal>.timer</literal>, <literal>.slice</literal>, or <literal>.scope</literal>.</para>
+
+    <para>Unit names can be parameterized by a single argument called the "instance name". The unit is then
     constructed based on a "template file" which serves as the definition of multiple services or other
-    units. A template unit must have a single <literal>@</literal> at the end of the name (right before the
-    type suffix). The name of the full unit is formed by inserting the instance name between
+    units. A template unit must have a single <literal>@</literal> at the end of the unit name prefix (right
+    before the type suffix). The name of the full unit is formed by inserting the instance name between
     <literal>@</literal> and the unit type suffix. In the unit file itself, the instance parameter may be
     referred to using <literal>%i</literal> and other specifiers, see below.</para>
 
           <term><varname>ConditionControlGroupController=</varname></term>
 
           <listitem><para>Check whether given cgroup controllers (e.g. <literal>cpu</literal>) are available
-          for use on the system.</para>
+          for use on the system or whether the legacy v1 cgroup or the modern v2 cgroup hierarchy is used.
+          </para>
 
           <para>Multiple controllers may be passed with a space separating them; in this case the condition
           will only pass if all listed controllers are available for use. Controllers unknown to systemd are
-          ignored. Valid controllers are <literal>cpu</literal>, <literal>cpuset</literal>,
-          <literal>io</literal>, <literal>memory</literal>, and <literal>pids</literal>. Even if available in
-          the kernel, a particular controller may not be available if it was disabled on the kernel command
-          line with <varname>cgroup_disable=controller</varname>.</para></listitem>
+          ignored. Valid controllers are <literal>cpu</literal>, <literal>io</literal>,
+          <literal>memory</literal>, and <literal>pids</literal>. Even if available in the kernel, a
+          particular controller may not be available if it was disabled on the kernel command line with
+          <varname>cgroup_disable=controller</varname>.</para>
+
+          <para>Alternatively, two special strings <literal>v1</literal> and <literal>v2</literal> may be
+          specified (without any controller names). <literal>v2</literal> will pass if the unified v2 cgroup
+          hierarchy is used, and <literal>v1</literal> will pass if the legacy v1 hierarchy or the hybrid
+          hierarchy are used. Note that legacy or hybrid hierarchies have been deprecated. See
+          <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+          more information.</para>
+          </listitem>
         </varlistentry>
 
         <varlistentry>
index 1a68301d50b806ea136502af3e5c78534ef8a49b..ca9e4e998809c09ce66a5c554a021aba2ddd8964 100644 (file)
         <para>This can be overridden with <option>--log-target=</option>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>$SYSTEMD_LOG_RATELIMIT_KMSG</varname></term>
+        <listitem><xi:include href="common-variables.xml" xpointer="log-ratelimit-kmsg" /></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>$XDG_CONFIG_HOME</varname></term>
         <term><varname>$XDG_CONFIG_DIRS</varname></term>
         <term><varname>systemd.log_target=</varname></term>
         <term><varname>systemd.log_time</varname></term>
         <term><varname>systemd.log_tid</varname></term>
+        <term><varname>systemd.log_ratelimit_kmsg</varname></term>
 
         <listitem><para>Controls log output, with the same effect as the
         <varname>$SYSTEMD_LOG_COLOR</varname>, <varname>$SYSTEMD_LOG_LEVEL</varname>,
         <varname>$SYSTEMD_LOG_LOCATION</varname>, <varname>$SYSTEMD_LOG_TARGET</varname>,
-        <varname>$SYSTEMD_LOG_TIME</varname>, and <varname>$SYSTEMD_LOG_TID</varname> environment variables
-        described above. <varname>systemd.log_color</varname>, <varname>systemd.log_location</varname>,
-        <varname>systemd.log_time</varname>, and <varname>systemd.log_tid=</varname> can be specified without
+        <varname>$SYSTEMD_LOG_TIME</varname>, <varname>$SYSTEMD_LOG_TID</varname> and
+        <varname>$SYSTEMD_LOG_RATELIMIT_KMSG</varname> environment variables described above.
+        <varname>systemd.log_color</varname>, <varname>systemd.log_location</varname>,
+        <varname>systemd.log_time</varname>, <varname>systemd.log_tid</varname> and
+        <varname>systemd.log_ratelimit_kmsg</varname> can be specified without
         an argument, with the same effect as a positive boolean.</para></listitem>
       </varlistentry>
 
index b50423dc7759c75d5f69438000de38c0f24a8160..a23b9c8946e92a90c8afe301408cfeaf32141187 100644 (file)
@@ -647,13 +647,13 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para>
 # an hour ago in "/tmp/foo/bar", are subject to time-based cleanup.
 d /tmp/foo/bar - - - - bmA:1h -</programlisting></para>
 
-      <para>Note that while the aging algorithm is run a 'shared' BSD file lock (see <citerefentry
+      <para>Note that while the aging algorithm is run an exclusive BSD file lock (see <citerefentry
       project='man-pages'><refentrytitle>flock</refentrytitle><manvolnum>2</manvolnum></citerefentry>) is
-      taken on each directory the algorithm descends into (and each directory below that, and so on). If the
-      aging algorithm finds a lock is already taken on some directory, it (and everything below it) is
-      skipped. Applications may use this to temporarily exclude certain directory subtrees from the aging
-      algorithm: the applications can take a BSD file lock themselves, and as long as they keep it aging of
-      the directory and everything below it is disabled.</para>
+      taken on each directory/file the algorithm decides to remove. If the aging algorithm finds a lock (
+      shared or exclusive) is already taken on some directory/file, it (and everything below it) is skipped.
+      Applications may use this to temporarily exclude certain directory subtrees from the aging algorithm:
+      the applications can take a BSD file lock themselves, and as long as they keep it aging of the
+      directory/file and everything below it is disabled.</para>
     </refsect2>
 
     <refsect2>
index ce40ded8f3f9df9ef90700572a8b5eb8d6733501..734e38cd05108c8fa61ef35bebd124917d1923e7 100644 (file)
         </varlistentry>
 
         <xi:include href="standard-options.xml" xpointer="help" />
+        <xi:include href="standard-options.xml" xpointer="version" />
       </variablelist>
     </refsect2>
 
index 0cf74da8c344d6b2b5f4a3109605e1d2aab35f08..c3c0d3f2dfb2c1d29b6a4761b05f6a14856d1e42 100644 (file)
       <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
-      <citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
      </para>
   </refsect1>
index dc2f11c31e26e7f85d66930b4f987fdd13124eb4..557d13e1ed9ddfcb9dca5dab70f72976ff0c7d05 100644 (file)
@@ -60,6 +60,62 @@ This is based on crypttab(5).
 
     <variablelist class='fstab-options'>
 
+      <varlistentry>
+        <term><option>superblock=<replaceable>BOOL</replaceable></option></term>
+
+        <listitem><para>Use dm-verity with or without permanent on-disk superblock.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>format=<replaceable>NUMBER</replaceable></option></term>
+
+        <listitem><para>Specifies the hash version type. Format type 0 is original Chrome OS version. Format type 1 is
+        modern version.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>data-block-size=<replaceable>BYTES</replaceable></option></term>
+
+        <listitem><para>Used block size for the data device. (Note kernel supports only page-size as maximum
+        here; Multiples of 512 bytes.) </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>hash-block-size=<replaceable>BYTES</replaceable></option></term>
+
+        <listitem><para>Used block size for the hash device. (Note kernel supports only page-size as maximum
+        here; Multiples of 512 bytes.)</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>data-blocks=<replaceable>BLOCKS</replaceable></option></term>
+
+        <listitem><para>Number of blocks of data device used in verification. If not specified, the whole device is
+        used.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>hash-offset=<replaceable>BYTES</replaceable></option></term>
+
+        <listitem><para>Offset of hash area/superblock on <literal>hash-device</literal>. (Multiples of 512 bytes.)
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>salt=<replaceable>HEX</replaceable></option></term>
+
+        <listitem><para>Salt used for format or verification. Format is a hexadecimal string; 256 bytes long maximum;
+        <literal>-</literal>is the special value for empty.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>uuid=<replaceable>UUID</replaceable></option></term>
+
+        <listitem><para>Use the provided UUID for format command instead of generating new one. The UUID must be
+        provided in standard UUID format, e.g. 12345678-1234-1234-1234-123456789abc.</para></listitem>
+        <listitem><para></para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>ignore-corruption</option></term>
         <term><option>restart-on-corruption</option></term>
@@ -94,6 +150,37 @@ This is based on crypttab(5).
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>hash=<replaceable>HASH</replaceable></option></term>
+
+        <listitem><para>Hash algorithm for dm-verity. This should be the name of the algorithm, like "sha1". For default
+        see <command>veritysetup --help</command>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>fec-device=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>Use forward error correction (FEC) to recover from corruption if hash verification fails. Use
+        encoding data from the specified device. The fec device argument can be block device or file image. For format,
+        if fec device path doesn't exist, it will be created as file. Note: block sizes for data and hash devices must
+        match. Also, if the verity data_device is encrypted the fec_device should be too.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>fec-offset=<replaceable>BYTES</replaceable></option></term>
+
+        <listitem><para>This is the offset, in bytes, from the start of the FEC device to the beginning of the encoding
+        data. (Aligned on 512 bytes.)</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>fec-roots=<replaceable>NUM</replaceable></option></term>
+
+        <listitem><para>Number of generator roots. This equals to the number of parity bytes in the encoding data. In
+        RS(M, N) encoding, the number of roots is M-N. M is 255 and M-N is between 2 and 24 (including).</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>root-hash-signature=<replaceable>PATH</replaceable>|base64:<replaceable>HEX</replaceable></option></term>
 
index 6770deed4ff11a41d4e8cc68386d802a50b982eb..40db638cebaff46e6b77fe0417e69b660e246f70 100644 (file)
@@ -201,6 +201,8 @@ kernelinstalldir = kerneldir / 'install.d'
 factorydir = datadir / 'factory'
 bootlibdir = prefixdir / 'lib/systemd/boot/efi'
 testsdir = prefixdir / 'lib/systemd/tests'
+unittestsdir = testsdir / 'unit-tests'
+testdata_dir = testsdir / 'testdata'
 systemdstatedir = localstatedir / 'lib/systemd'
 catalogstatedir = systemdstatedir / 'catalog'
 randomseeddir = localstatedir / 'lib/systemd'
@@ -274,7 +276,7 @@ conf.set_quoted('SYSTEMD_LANGUAGE_FALLBACK_MAP',              pkgdatadir / 'lang
 conf.set_quoted('SYSTEMD_MAKEFS_PATH',                        rootlibexecdir / 'systemd-makefs')
 conf.set_quoted('SYSTEMD_PULL_PATH',                          rootlibexecdir / 'systemd-pull')
 conf.set_quoted('SYSTEMD_SHUTDOWN_BINARY_PATH',               rootlibexecdir / 'systemd-shutdown')
-conf.set_quoted('SYSTEMD_TEST_DATA',                          testsdir / 'testdata')
+conf.set_quoted('SYSTEMD_TEST_DATA',                          testdata_dir)
 conf.set_quoted('SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH', rootbindir / 'systemd-tty-ask-password-agent')
 conf.set_quoted('SYSTEMD_UPDATE_HELPER_PATH',                 rootlibexecdir / 'systemd-update-helper')
 conf.set_quoted('SYSTEMD_USERWORK_PATH',                      rootlibexecdir / 'systemd-userwork')
@@ -1964,6 +1966,118 @@ pymod = import('python')
 python = pymod.find_installation('python3', required : true, modules : ['jinja2'])
 python_39 = python.language_version().version_compare('>=3.9')
 
+############################################################
+
+if conf.get('BPF_FRAMEWORK') == 1
+        bpf_clang_flags = [
+                '-std=gnu11',
+                '-Wno-compare-distinct-pointer-types',
+                '-fno-stack-protector',
+                '-O2',
+                '-target',
+                'bpf',
+                '-g',
+                '-c',
+        ]
+
+        bpf_gcc_flags = [
+                '-std=gnu11',
+                '-fno-stack-protector',
+                '-O2',
+                '-mkernel=5.2',
+                '-mcpu=v3',
+                '-mco-re',
+                '-gbtf',
+                '-c',
+        ]
+
+        # Generate defines that are appropriate to tell the compiler what architecture
+        # we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__.
+        # This dictionary contains the exceptions where this doesn't work.
+        #
+        # C.f. https://mesonbuild.com/Reference-tables.html#cpu-families
+        # and src/basic/missing_syscall_def.h.
+        cpu_arch_defines = {
+                'ppc'     : ['-D__powerpc__'],
+                'ppc64'   : ['-D__powerpc64__', '-D_CALL_ELF=2'],
+                'riscv32' : ['-D__riscv', '-D__riscv_xlen=32'],
+                'riscv64' : ['-D__riscv', '-D__riscv_xlen=64'],
+                'x86'     : ['-D__i386__'],
+
+                # For arm, assume hardware fp is available.
+                'arm'     : ['-D__arm__', '-D__ARM_PCS_VFP'],
+        }
+
+        bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(),
+                                              ['-D__@0@__'.format(host_machine.cpu_family())])
+        if bpf_compiler == 'gcc'
+                bpf_arch_flags += ['-m' + host_machine.endian() + '-endian']
+        endif
+
+        libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir')
+
+        bpf_o_unstripped_cmd = []
+        if bpf_compiler == 'clang'
+                bpf_o_unstripped_cmd += [
+                        clang,
+                        bpf_clang_flags,
+                        bpf_arch_flags,
+                ]
+        elif bpf_compiler == 'gcc'
+                bpf_o_unstripped_cmd += [
+                        bpf_gcc,
+                        bpf_gcc_flags,
+                        bpf_arch_flags,
+                ]
+        endif
+
+        bpf_o_unstripped_cmd += ['-I.']
+
+        if not meson.is_cross_build() and bpf_compiler == 'clang'
+                target_triplet_cmd = run_command('gcc', '-dumpmachine', check: false)
+                if target_triplet_cmd.returncode() == 0
+                        target_triplet = target_triplet_cmd.stdout().strip()
+                        bpf_o_unstripped_cmd += [
+                                '-isystem',
+                                '/usr/include/@0@'.format(target_triplet)
+                        ]
+                endif
+        endif
+
+        bpf_o_unstripped_cmd += [
+                '-idirafter',
+                libbpf_include_dir,
+                '@INPUT@',
+                '-o',
+                '@OUTPUT@'
+        ]
+
+        if bpftool_strip
+                bpf_o_cmd = [
+                        bpftool,
+                        'gen',
+                        'object',
+                        '@OUTPUT@',
+                        '@INPUT@'
+                ]
+        elif bpf_compiler == 'clang'
+                bpf_o_cmd = [
+                        llvm_strip,
+                        '-g',
+                        '@INPUT@',
+                        '-o',
+                        '@OUTPUT@'
+                ]
+        endif
+
+        skel_h_cmd = [
+                bpftool,
+                'gen',
+                'skeleton',
+                '@INPUT@'
+        ]
+endif
+
 #####################################################################
 
 efi_arch = {
@@ -3181,24 +3295,14 @@ if conf.get('ENABLE_HOSTNAMED') == 1
 endif
 
 if conf.get('ENABLE_LOCALED') == 1
-        if conf.get('HAVE_XKBCOMMON') == 1
-                # logind will load libxkbcommon.so dynamically on its own, but we still
-                # need to specify where the headers are
-                deps = [libdl,
-                        libxkbcommon.partial_dependency(compile_args: true),
-                        userspace,
-                        versiondep]
-        else
-                deps = [userspace,
-                        versiondep]
-        endif
-
         dbus_programs += executable(
                 'systemd-localed',
                 systemd_localed_sources,
                 include_directories : includes,
                 link_with : [libshared],
-                dependencies : deps,
+                dependencies : libxkbcommon_deps +
+                               [userspace,
+                                versiondep],
                 install_rpath : rootpkglibdir,
                 install : true,
                 install_dir : rootlibexecdir)
@@ -4330,7 +4434,7 @@ foreach test : tests
                 build_by_default : want_tests != 'false',
                 install_rpath : rootpkglibdir,
                 install : install_tests,
-                install_dir : testsdir / type,
+                install_dir : unittestsdir / type,
                 link_depends : runtest_env)
 
         if type == 'manual'
@@ -4354,7 +4458,7 @@ exe = executable(
         dependencies : userspace,
         build_by_default : want_tests != 'false',
         install : install_tests,
-        install_dir : testsdir)
+        install_dir : unittestsdir)
 if want_tests != 'false'
         test('test-libsystemd-sym', exe)
 endif
@@ -4372,7 +4476,7 @@ exe = executable(
         ],
         build_by_default : want_tests != 'false' and static_libsystemd_pic,
         install : install_tests and static_libsystemd_pic,
-        install_dir : testsdir)
+        install_dir : unittestsdir)
 if want_tests != 'false' and static_libsystemd_pic
         test('test-libsystemd-static-sym', exe)
 endif
@@ -4386,7 +4490,7 @@ exe = executable(
         dependencies : userspace,
         build_by_default : want_tests != 'false',
         install : install_tests,
-        install_dir : testsdir)
+        install_dir : unittestsdir)
 if want_tests != 'false'
         test('test-libudev-sym', exe)
 endif
@@ -4400,7 +4504,7 @@ exe = executable(
         dependencies : userspace,
         build_by_default : want_tests != 'false' and static_libudev_pic,
         install : install_tests and static_libudev_pic,
-        install_dir : testsdir)
+        install_dir : unittestsdir)
 if want_tests != 'false' and static_libudev_pic
         test('test-libudev-static-sym', exe)
 endif
index 24114af4ecf4c42543bb5355a0e6b35982fbab4d..7ad3371ee7887c2f7006ce486cb3206733362a65 100755 (executable)
@@ -178,14 +178,15 @@ if [ -d mkosi.kernel/ ]; then
             tools/testing/selftests/bpf/config.x86_64 \
             tools/testing/selftests/bpf/config
 
-    make O="$BUILDDIR" -j "$(nproc)"
+    # Make sure systemd-boot boots this kernel and not the distro provided one by overriding the version.
+    make O="$BUILDDIR" VERSION=99 -j "$(nproc)"
 
-    KERNEL_RELEASE=$(make O="$BUILDDIR" -s kernelrelease)
+    KERNEL_RELEASE=$(make O="$BUILDDIR" VERSION=99 -s kernelrelease)
     mkdir -p "$DESTDIR/usr/lib/modules/$KERNEL_RELEASE"
-    make O="$BUILDDIR" INSTALL_MOD_PATH="$DESTDIR/usr" modules_install
-    make O="$BUILDDIR" INSTALL_PATH="$DESTDIR/usr/lib/modules/$KERNEL_RELEASE" install
+    make O="$BUILDDIR" VERSION=99 INSTALL_MOD_PATH="$DESTDIR/usr" modules_install
+    make O="$BUILDDIR" VERSION=99 INSTALL_PATH="$DESTDIR/usr/lib/modules/$KERNEL_RELEASE" install
     mkdir -p "$DESTDIR/usr/lib/kernel/selftests"
-    make -C tools/testing/selftests -j "$(nproc)" O="$BUILDDIR" KSFT_INSTALL_PATH="$DESTDIR/usr/lib/kernel/selftests" SKIP_TARGETS="" install
+    make -C tools/testing/selftests -j "$(nproc)" O="$BUILDDIR" VERSION=99 KSFT_INSTALL_PATH="$DESTDIR/usr/lib/kernel/selftests" SKIP_TARGETS="" install
 
     ln -sf /usr/lib/kernel/selftests/bpf/bpftool "$DESTDIR/usr/bin/bpftool"
 fi
index dbc50793a7a5aa824c638b0705b4d0551fda7318..2b02eba0d63ca5b05b32f391c925f9450a79de60 100644 (file)
@@ -1,9 +1,6 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-
 [Output]
-Bootable=yes
 # Prevent ASAN warnings when building the image and ship the real ASAN options prefixed with MKOSI_.
 Environment=ASAN_OPTIONS=verify_asan_link_order=false
             MKOSI_ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:disable_coredump=0:use_madv_dontdump=1
@@ -12,7 +9,8 @@ OutputDirectory=mkosi.output
 
 [Content]
 BuildDirectory=mkosi.builddir
-Cache=mkosi.cache
+CacheDirectory=mkosi.cache
+ExtraTrees=src:/root/src
 Packages=
         acl
         bash-completion
@@ -20,6 +18,7 @@ Packages=
         diffutils
         dnsmasq
         dosfstools
+        dracut
         e2fsprogs
         findutils
         gcc # For sanitizer libraries
@@ -37,7 +36,9 @@ Packages=
         qrencode
         sed
         strace
+        systemd
         tree
+        udev
         util-linux
         valgrind
         wireguard-tools
@@ -65,15 +66,17 @@ BuildPackages=
         zstd
 
 [Host]
-QemuHeadless=yes
-Netdev=yes
+Acl=yes
 QemuMem=2G
 ExtraSearchPaths=build/
 KernelCommandLineExtra=systemd.crash_shell
                        systemd.log_level=debug
+                       systemd.log_ratelimit_kmsg=0
                        systemd.journald.forward_to_console
                        systemd.journald.max_level_console=warning
                        systemd.mask=auditd
+                       # Tell the kernel to only log warning and up to the console.
+                       loglevel=4
 
 [Validation]
 Password=
similarity index 77%
rename from mkosi.conf.d/arch/10-arch.conf
rename to mkosi.conf.d/20-arch.conf
index e1c75b3996f8d36d4a04d463ee0be3a10d9c4f00..6dcbb9f6ab69c47aceaea66e3ca92ebc5a45f4bd 100644 (file)
@@ -2,15 +2,13 @@
 #
 # Copyright © 2016 Zeal Jagannatha
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
+[Match]
 Distribution=arch
 
 [Content]
 Packages=
         alsa-lib
+        base
         btrfs-progs
         compsize
         dhcp
@@ -25,12 +23,16 @@ Packages=
         libmnl
         libpwquality
         libxkbcommon
+        linux
         man-db
         numactl
         openbsd-netcat
+        openssh
         polkit
         popt
         python-pefile
+        python-psutil
+        python-pytest
         quota-tools
         shadow
         tpm2-tss
@@ -47,4 +49,3 @@ BuildPackages=
         python-jinja
         python-lxml
         python-pyelftools
-        python-pytest
similarity index 72%
rename from mkosi.conf.d/fedora/10-fedora.conf
rename to mkosi.conf.d/20-centos-fedora.conf
index b4c641cd67e6f4b210587004f660df18596bfb93..d78b924304e5628bfe3dc6cc9292e5f50002a240 100644 (file)
@@ -1,27 +1,23 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
-Distribution=fedora
-Release=37
+[Match]
+Distribution=centos fedora
 
 [Content]
 Packages=
         alsa-lib
-        btrfs-progs
-        compsize
+        audit-libs
         cryptsetup
         dhcp-server
         dnf
-        f2fs-tools
         fuse
         glib2
         glibc-minimal-langpack
+        glibc.i686
         gnutls
         iproute
         iproute-tc
+        kernel-core
         libasan
         libbpf
         libcap-ng
@@ -35,31 +31,38 @@ Packages=
         libxkbcommon
         netcat
         numactl-libs
+        openssh-server
+        p11-kit
         pam
         passwd
         polkit
         popt
         procps-ng
-        python3dist(pefile)
         quota
         tpm2-tss
+        util-linux
         vim-common
 
 BuildPackages=
+        /usr/bin/pkg-config
         bpftool
         docbook-xsl
         dwarves
+        glibc-devel.i686
         glibc-static
-        libcap-static
+        glibc-static.i686
+        libxslt
         pam-devel
-        pkgconfig # pkgconf shim to provide /usr/bin/pkg-config
+        perl-interpreter
         pkgconfig(alsa)
         pkgconfig(audit)
         pkgconfig(blkid)
+        pkgconfig(bzip2)
         pkgconfig(dbus-1)
         pkgconfig(fdisk)
         pkgconfig(fuse)
         pkgconfig(glib-2.0)
+        pkgconfig(gnutls)
         pkgconfig(libacl)
         pkgconfig(libbpf)
         pkgconfig(libcap-ng)
@@ -68,7 +71,6 @@ BuildPackages=
         pkgconfig(libcurl)
         pkgconfig(libdw)
         pkgconfig(libfido2)
-        pkgconfig(libgcrypt)
         pkgconfig(libidn2)
         pkgconfig(libkmod)
         pkgconfig(libmicrohttpd)
@@ -88,10 +90,5 @@ BuildPackages=
         pkgconfig(tss2-mu)
         pkgconfig(tss2-rc)
         pkgconfig(valgrind)
-        pkgconfig(xencontrol)
         pkgconfig(xkbcommon)
-        python3dist(docutils)
-        python3dist(jinja2)
-        python3dist(lxml)
-        python3dist(pyelftools)
-        python3dist(pytest)
+        python3-docutils
diff --git a/mkosi.conf.d/20-centos.conf b/mkosi.conf.d/20-centos.conf
new file mode 100644 (file)
index 0000000..4181f03
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=centos
+
+[Distribution]
+Release=9
+Repositories=epel
similarity index 86%
rename from mkosi.conf.d/debian/10-debian.conf
rename to mkosi.conf.d/20-debian-ubuntu/mkosi.conf
index e9b5775a3733874afd724f398762eafdf52c4c5d..584536ef6b90f7108b4f00dc3c9b09ab0d7235fa 100644 (file)
@@ -1,16 +1,14 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
-Distribution=debian
-Release=testing
+[Match]
+Distribution=debian ubuntu
 
 [Content]
 Packages=
         btrfs-progs
         cryptsetup-bin
+        dbus-broker
+        default-dbus-session-bus
         f2fs-tools
         fdisk
         fuse
@@ -18,10 +16,10 @@ Packages=
         iproute2
         isc-dhcp-server
         libasound2
-        libbpf1
         libc6-i386
         libcap-ng-utils
         libcap-ng0
+        libfdisk1
         libfido2-1
         libglib2.0-0
         libgnutls30
@@ -36,15 +34,19 @@ Packages=
         libqrencode4
         libtss2-dev # Use the -dev package to avoid churn in updating version numbers
         netcat-openbsd
+        openssh-server
         passwd
         policykit-1
         procps
         python3-pefile
+        python3-psutil
+        python3-pytest
         quota
+        systemd-sysv
+        tzdata
         xxd
 
 BuildPackages=
-        bpftool
         docbook-xsl
         dpkg-dev
         g++
@@ -92,5 +94,4 @@ BuildPackages=
         python3-jinja2
         python3-lxml
         python3-pyelftools
-        python3-pytest
         xsltproc
diff --git a/mkosi.conf.d/20-debian-ubuntu/mkosi.extra/usr/lib/systemd/system-preset/99-ignore.preset b/mkosi.conf.d/20-debian-ubuntu/mkosi.extra/usr/lib/systemd/system-preset/99-ignore.preset
new file mode 100644 (file)
index 0000000..43f2553
--- /dev/null
@@ -0,0 +1 @@
+ignore *
diff --git a/mkosi.conf.d/20-debian-ubuntu/mkosi.extra/usr/lib/tmpfiles.d/locale.conf b/mkosi.conf.d/20-debian-ubuntu/mkosi.extra/usr/lib/tmpfiles.d/locale.conf
new file mode 100644 (file)
index 0000000..e1a8e81
--- /dev/null
@@ -0,0 +1 @@
+L /etc/default/locale - - - - ../locale.conf
diff --git a/mkosi.conf.d/20-debian.conf b/mkosi.conf.d/20-debian.conf
new file mode 100644 (file)
index 0000000..c251ab6
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=debian
+
+[Distribution]
+Release=testing
+
+[Content]
+Packages=
+        libbpf1
+        linux-image-cloud-amd64
+
+BuildPackages=
+        bpftool
diff --git a/mkosi.conf.d/20-fedora.conf b/mkosi.conf.d/20-fedora.conf
new file mode 100644 (file)
index 0000000..1574420
--- /dev/null
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=fedora
+
+[Distribution]
+Release=38
+
+[Content]
+Packages=
+        btrfs-progs
+        compsize
+        f2fs-tools
+        python3dist(pefile)
+        python3dist(psutil)
+        python3dist(pytest)
+
+BuildPackages=
+        libcap-static
+        pkgconfig(libgcrypt)
+        pkgconfig(xencontrol)
+        python3dist(jinja2)
+        python3dist(lxml)
+        python3dist(pyelftools)
similarity index 91%
rename from mkosi.conf.d/opensuse/10-opensuse.conf
rename to mkosi.conf.d/20-opensuse.conf
index ae0486850c896f9b49ee28f2b5bbe53831516cf7..caf84ba0ab52599d60b427c4a9300fcfb57f7190 100644 (file)
@@ -1,10 +1,9 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
+[Match]
+Distribution=opensuse
 
 [Distribution]
-Distribution=opensuse
 Release=tumbleweed
 
 [Content]
@@ -16,6 +15,7 @@ Packages=
         gcc # Provides libasan/libubsan
         glibc-32bit
         glibc-locale-base
+        kernel-default
         libasound2
         libbpf1
         libcap-ng-utils
@@ -37,8 +37,11 @@ Packages=
         libqrencode4
         libseccomp2
         libxkbcommon0
+        openssh-server
         pam
         python3-pefile
+        python3-psutil
+        python3-pytest
         shadow
         tpm2-0-tss
         vim
@@ -95,7 +98,6 @@ BuildPackages=
         python3-Jinja2
         python3-lxml
         python3-pyelftools
-        python3-pytest
         qrencode-devel
         shadow
         system-group-obsolete
diff --git a/mkosi.conf.d/20-ubuntu.conf b/mkosi.conf.d/20-ubuntu.conf
new file mode 100644 (file)
index 0000000..036c173
--- /dev/null
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Distribution=ubuntu
+
+[Distribution]
+Release=jammy
+Repositories=universe
+
+[Content]
+Packages=
+        libbpf0
+        linux-virtual
+
+BuildPackages=
+        linux-tools-common
+        linux-tools-generic
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)
diff --git a/mkosi.conf.d/21-centos-8/mkosi.reposdir/powertools.repo b/mkosi.conf.d/21-centos-8/mkosi.reposdir/powertools.repo
new file mode 100644 (file)
index 0000000..1462257
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[powertools-hotfixes]
+name=powertools-hotfixes
+mirrorlist=http://mirrorlist.centos.org/?release=$stream&arch=$basearch&repo=PowerTools
+gpgkey=https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Official
+gpgcheck=1
+enabled=1
+module_hotfixes=1
+skip_if_unavailable=1
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)
diff --git a/mkosi.conf.d/centos/10-centos.conf b/mkosi.conf.d/centos/10-centos.conf
deleted file mode 100644 (file)
index 6069422..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-# We use python3*dist() throughout this file because we need to make sure the python3.9dis() packages are
-# installed on CentOS Stream 8. mkosi doesn't support release specific configuration yet so we use the globs
-# to get the necessary packages on both CentOS Stream 8 and CentOS Stream 9.
-
-[Distribution]
-Distribution=centos
-Repositories=epel
-
-[Content]
-Packages=
-        alsa-lib
-        audit
-        cryptsetup
-        dhcp-server
-        dnf
-        fuse
-        glib2
-        glibc-minimal-langpack
-        glibc.i686
-        gnutls
-        iproute
-        iproute-tc
-        kernel-modules-extra
-        libasan
-        libbpf
-        libcap-ng
-        libcap-ng-utils
-        libfido2
-        libmicrohttpd
-        libmnl
-        libubsan
-        libxcrypt
-        libxkbcommon
-        netcat
-        numactl-libs
-        p11-kit
-        pam
-        passwd
-        polkit
-        popt
-        procps-ng
-        python3*dist(pefile)
-        python3*dist(pluggy) # python39-pluggy is a pytest dependency that's not installed for some reason.
-        python3*dist(pytest)
-        python39
-        quota
-        tpm2-tss
-        vim-common
-
-BuildPackages=
-        bpftool
-        docbook-xsl
-        dwarves
-        glibc-devel.i686
-        glibc-static
-        glibc-static.i686
-        libgcrypt-devel # CentOS Stream 8 libgcrypt-devel doesn't ship a pkg-config file.
-        libxslt
-        pam-devel
-        perl-interpreter
-        pkgconfig(alsa)
-        pkgconfig(audit)
-        pkgconfig(blkid)
-        pkgconfig(bzip2)
-        pkgconfig(dbus-1)
-        pkgconfig(fdisk)
-        pkgconfig(fuse)
-        pkgconfig(glib-2.0)
-        pkgconfig(gnutls)
-        pkgconfig(libacl)
-        pkgconfig(libbpf)
-        pkgconfig(libcap-ng)
-        pkgconfig(libcap)
-        pkgconfig(libcryptsetup)
-        pkgconfig(libcurl)
-        pkgconfig(libdw)
-        pkgconfig(libfido2)
-        pkgconfig(libidn2)
-        pkgconfig(libkmod)
-        pkgconfig(libmicrohttpd)
-        pkgconfig(libmnl)
-        pkgconfig(libpcre2-8)
-        pkgconfig(libqrencode)
-        pkgconfig(libseccomp)
-        pkgconfig(libselinux)
-        pkgconfig(libzstd)
-        pkgconfig(mount)
-        pkgconfig(numa)
-        pkgconfig(openssl)
-        pkgconfig(p11-kit-1)
-        pkgconfig(popt)
-        pkgconfig(pwquality)
-        pkgconfig(tss2-esys)
-        pkgconfig(tss2-mu)
-        pkgconfig(tss2-rc)
-        pkgconfig(valgrind)
-        pkgconfig(xkbcommon)
-        python3*dist(docutils)
-        python3*dist(jinja2)
-        python3*dist(lxml)
-        python3*dist(pyelftools)
diff --git a/mkosi.conf.d/ubuntu/10-ubuntu.conf b/mkosi.conf.d/ubuntu/10-ubuntu.conf
deleted file mode 100644 (file)
index ffc1d54..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
-# Symlink this file to mkosi.default in the project root directory and invoke "mkosi" to build an OS image.
-
-[Distribution]
-Distribution=ubuntu
-Release=jammy
-Repositories=main,universe
-
-[Content]
-Packages=
-        btrfs-progs
-        cryptsetup-bin
-        f2fs-tools
-        fdisk
-        fuse
-        gcc # Provides libasan/libubsan
-        iproute2
-        isc-dhcp-server
-        libasound2
-        libbpf0
-        libc6-i386
-        libcap-ng-utils
-        libcap-ng0
-        libfdisk1
-        libfido2-1
-        libglib2.0-0
-        libidn2-0
-        libmicrohttpd12
-        libmnl0
-        libnuma1
-        libp11-kit0
-        libpopt0
-        libpwquality1
-        libqrencode4
-        libtss2-dev # Use the -dev package to avoid churn in updating version numbers
-        linux-tools-common
-        linux-tools-generic
-        netcat-openbsd
-        passwd
-        policykit-1
-        procps
-        python3-pefile
-        quota
-        xxd
-
-BuildPackages=
-        docbook-xsl
-        dpkg-dev
-        g++
-        gcc-multilib
-        libacl1-dev
-        libasound-dev
-        libaudit-dev
-        libblkid-dev
-        libbpf-dev
-        libbz2-dev
-        libc6-dev
-        libc6-dev-i386
-        libcap-dev
-        libcap-ng-dev
-        libcryptsetup-dev
-        libcurl4-openssl-dev
-        libdbus-1-dev
-        libdw-dev
-        libfdisk-dev
-        libfido2-dev
-        libfuse-dev
-        libgcrypt20-dev
-        libglib2.0-dev
-        libgnutls28-dev
-        libidn2-dev
-        libiptc-dev
-        libkmod-dev
-        libmicrohttpd-dev
-        libmnl-dev
-        libmount-dev
-        libnuma-dev
-        libp11-kit-dev
-        libpam0g-dev
-        libpopt-dev
-        libpwquality-dev
-        libqrencode-dev
-        libseccomp-dev
-        libsmartcols-dev
-        libssl-dev
-        libxen-dev
-        libxkbcommon-dev
-        libzstd-dev
-        pahole
-        python3-docutils
-        python3-jinja2
-        python3-lxml
-        python3-pyelftools
-        python3-pytest
-        xsltproc
index 522e1fecb658790af77fb9923e5073019e797aee..1a2163e3a54274525036357ab68a826ac24522d6 100644 (file)
@@ -1,2 +1,3 @@
 set debuginfod enabled off
 set build-id-verbose 0
+set substitute-path ../src /root/src
diff --git a/mkosi.extra/usr/lib/systemd/system-preset/00-mkosi.preset b/mkosi.extra/usr/lib/systemd/system-preset/00-mkosi.preset
new file mode 100644 (file)
index 0000000..e214931
--- /dev/null
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+disable ssh.service
+disable sshd.service
+disable dnsmasq.service
+disable isc-dhcp-server.service
+disable isc-dhcp-server6.service
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 43cc818393a245ac592e335f5920a14fb2ef6ea1..82c23cf87a64b4a13b9e311a6557d807747e0b6c 100755 (executable)
@@ -47,9 +47,6 @@ EOF
     systemctl mask systemd-hwdb-update.service
 fi
 
-# Make sure dnsmasq.service doesn't start on boot on Debian/Ubuntu.
-rm -f /etc/systemd/system/multi-user.target.wants/dnsmasq.service
-
 if [ -n "$IMAGE_ID" ] ; then
     sed -n \
         -i \
@@ -65,3 +62,14 @@ if [ -n "$IMAGE_VERSION" ] ; then
         -e "\$aIMAGE_VERSION=$IMAGE_VERSION" \
         /usr/lib/os-release
 fi
+
+if command -v authselect >/dev/null; then
+    authselect select minimal
+
+    if authselect list-features minimal | grep -q "with-homed"; then
+        authselect enable-feature with-homed
+    fi
+fi
+
+# Let tmpfiles.d/systemd-resolve.conf handle the symlink
+rm -f /etc/resolv.conf
index 096777ce8e4c4a98f7fd49a2acef1eb1e8a065af..1c5880619dfe80004fe91d18d9ad3555e3cd722c 100644 (file)
--- a/po/gl.po
+++ b/po/gl.po
@@ -1,19 +1,21 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 #
 # Fran Dieguez <frandieguez@gnome.org>, 2015.
+# Fran Diéguez <frandieguez@gnome.org>, 2023.
 msgid ""
 msgstr ""
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2022-10-20 10:35+0200\n"
-"PO-Revision-Date: 2019-12-29 22:30+0100\n"
+"PO-Revision-Date: 2023-04-14 18:20+0000\n"
 "Last-Translator: Fran Diéguez <frandieguez@gnome.org>\n"
-"Language-Team: gnome-l10n-gl@gnome.org\n"
+"Language-Team: Galician <https://translate.fedoraproject.org/projects/"
+"systemd/master/gl/>\n"
 "Language: gl\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 2.2.4\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.15.2\n"
 
 #: src/core/org.freedesktop.systemd1.policy.in:22
 msgid "Send passphrase back to system"
@@ -70,63 +72,58 @@ msgstr "Requírese autenticación para recargar o estado de systemd."
 
 #: src/home/org.freedesktop.home1.policy:13
 msgid "Create a home area"
-msgstr ""
+msgstr "Crear un área persoal"
 
 #: src/home/org.freedesktop.home1.policy:14
-#, fuzzy
 msgid "Authentication is required to create a user's home area."
-msgstr "Requírese autenticación para estabelecer os servidores NTP."
+msgstr "Requírese autenticación para crear un área persoal."
 
 #: src/home/org.freedesktop.home1.policy:23
 msgid "Remove a home area"
-msgstr ""
+msgstr "Eliminar un área persoal"
 
 #: src/home/org.freedesktop.home1.policy:24
-#, fuzzy
 msgid "Authentication is required to remove a user's home area."
-msgstr "Requírese autenticación para estabelecer os servidores NTP."
+msgstr "Requírese autenticación para eliminar un área persoal."
 
 #: src/home/org.freedesktop.home1.policy:33
 msgid "Check credentials of a home area"
-msgstr ""
+msgstr "Comprobar as credenciais dun área persoal"
 
 #: src/home/org.freedesktop.home1.policy:34
-#, fuzzy
 msgid ""
 "Authentication is required to check credentials against a user's home area."
 msgstr ""
-"Requírese autenticación para anexar ou desanexar unha imaxe de servizo "
-"portábel."
+"Requírese autenticación para comprobar as credenciais do espazo persoal dun "
+"usuario."
 
 #: src/home/org.freedesktop.home1.policy:43
 msgid "Update a home area"
-msgstr ""
+msgstr "Actualizar un espazo persoal"
 
 #: src/home/org.freedesktop.home1.policy:44
-#, fuzzy
 msgid "Authentication is required to update a user's home area."
-msgstr "Requírese autenticación para anexar un dispositivo a un asento."
+msgstr "Requírese autenticación para actualizar o espazo persoal dun usuario."
 
 #: src/home/org.freedesktop.home1.policy:53
 msgid "Resize a home area"
-msgstr ""
+msgstr "Redimensionar un espazo persoal"
 
 #: src/home/org.freedesktop.home1.policy:54
-#, fuzzy
 msgid "Authentication is required to resize a user's home area."
-msgstr "Requírese autenticación para estabelecer os servidores NTP."
+msgstr ""
+"Requírese autenticación para redimensionar o espazo persoal dun usuario."
 
 #: src/home/org.freedesktop.home1.policy:63
 msgid "Change password of a home area"
-msgstr ""
+msgstr "Cambiar contrasinal dun espazo persoal"
 
 #: src/home/org.freedesktop.home1.policy:64
-#, fuzzy
 msgid ""
 "Authentication is required to change the password of a user's home area."
 msgstr ""
-"Requírese autenticación para xestionar as sesións, usuarios e asentos "
-"activos."
+"Requírese autenticación para cambiar o contrasinal dun espazo persoal dun "
+"usuario."
 
 #: src/hostname/org.freedesktop.hostname1.policy:20
 msgid "Set hostname"
@@ -168,22 +165,19 @@ msgstr "Requírese autenticación para obter o UUID de produto."
 
 #: src/hostname/org.freedesktop.hostname1.policy:61
 msgid "Get hardware serial number"
-msgstr ""
+msgstr "Obter o número de serie do hardware"
 
 #: src/hostname/org.freedesktop.hostname1.policy:62
-#, fuzzy
 msgid "Authentication is required to get hardware serial number."
-msgstr "Requírese autenticación para estabelecer a hora do sistema."
+msgstr "Requírese autenticación para obter o número serie do hardware."
 
 #: src/hostname/org.freedesktop.hostname1.policy:71
-#, fuzzy
 msgid "Get system description"
-msgstr "Estabelecer o fuso horario"
+msgstr "Obter a descrición do sistema"
 
 #: src/hostname/org.freedesktop.hostname1.policy:72
-#, fuzzy
 msgid "Authentication is required to get system description."
-msgstr "Requírese autenticación para estabelecer o fuso horario do sistema."
+msgstr "Requírese autenticación para obter a descrición do sistema."
 
 #: src/import/org.freedesktop.import1.policy:22
 msgid "Import a VM or container image"
@@ -334,13 +328,11 @@ msgstr ""
 "do sistema do interruptor da tapa."
 
 #: src/login/org.freedesktop.login1.policy:117
-#, fuzzy
 msgid "Allow applications to inhibit system handling of the reboot key"
 msgstr ""
-"Permitir ás aplicacións inhibir a xestión do sistema da tecla de acendido"
+"Permitir ás aplicacións inhibir a xestión do sistema da tecla de reinicio"
 
 #: src/login/org.freedesktop.login1.policy:118
-#, fuzzy
 msgid ""
 "Authentication is required for an application to inhibit system handling of "
 "the reboot key."
@@ -475,7 +467,6 @@ msgid "Halt the system while an application is inhibiting this"
 msgstr "Deter o sistema cando unha aplicación solicitou a súa inhibición"
 
 #: src/login/org.freedesktop.login1.policy:258
-#, fuzzy
 msgid ""
 "Authentication is required to halt the system while an application is "
 "inhibiting this."
@@ -623,12 +614,11 @@ msgstr "Requírese autenticación para estabelecer unha mensaxe de muro"
 
 #: src/login/org.freedesktop.login1.policy:406
 msgid "Change Session"
-msgstr ""
+msgstr "Cambiar sesión"
 
 #: src/login/org.freedesktop.login1.policy:407
-#, fuzzy
 msgid "Authentication is required to change the virtual terminal."
-msgstr "Requírese autenticación para deter o sistema."
+msgstr "Requírese autenticación para cambiar o terminal virtual."
 
 #: src/machine/org.freedesktop.machine1.policy:22
 msgid "Log into a local container"
@@ -813,12 +803,11 @@ msgstr "Requírese autenticación para restabelecer as preferencias de DNS."
 
 #: src/network/org.freedesktop.network1.policy:143
 msgid "DHCP server sends force renew message"
-msgstr ""
+msgstr "O servidor DHCP envía un mensaxe de renovación forzada"
 
 #: src/network/org.freedesktop.network1.policy:144
-#, fuzzy
 msgid "Authentication is required to send force renew message."
-msgstr "Requírese autenticación para estabelecer unha mensaxe de muro"
+msgstr "Requírese autenticación para enviar o mensaxe de renovación forzada."
 
 #: src/network/org.freedesktop.network1.policy:154
 msgid "Renew dynamic addresses"
@@ -983,12 +972,9 @@ msgstr ""
 "'$(unit)'."
 
 #: src/core/dbus-unit.c:762
-#, fuzzy
 msgid ""
 "Authentication is required to freeze or thaw the processes of '$(unit)' unit."
-msgstr ""
-"Requírese autenticación para enviarlle un sinal UNIX aos procesos de "
-"'$(unit)'."
+msgstr "Requírese autenticación para conxelar o proceso da unidade '$(unit)'."
 
 #~ msgid ""
 #~ "Authentication is required to halt the system while an application asked "
index 26b0f9575e31ba442817a48cf7667024c8c52939..f66601e3ea2fd8653335ac3f3dd4b3772525aec0 100644 (file)
--- a/po/ru.po
+++ b/po/ru.po
@@ -7,21 +7,22 @@
 # Vladimir Yerilov <openmindead@gmail.com>, 2020.
 # Alexey Rubtsov <rushills@gmail.com>, 2021.
 # Olga Smirnova <mistresssilvara@hotmail.com>, 2022.
+# Andrei Stepanov <adem4ik@gmail.com>, 2023.
 msgid ""
 msgstr ""
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2022-10-20 10:35+0200\n"
-"PO-Revision-Date: 2022-10-27 23:19+0000\n"
-"Last-Translator: Olga Smirnova <mistresssilvara@hotmail.com>\n"
+"PO-Revision-Date: 2023-04-02 02:20+0000\n"
+"Last-Translator: Andrei Stepanov <adem4ik@gmail.com>\n"
 "Language-Team: Russian <https://translate.fedoraproject.org/projects/systemd/"
 "master/ru/>\n"
 "Language: ru\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-"X-Generator: Weblate 4.14.1\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 4.15.2\n"
 
 #: src/core/org.freedesktop.systemd1.policy.in:22
 msgid "Send passphrase back to system"
@@ -980,14 +981,14 @@ msgstr ""
 
 #: src/timedate/org.freedesktop.timedate1.policy:53
 msgid "Turn network time synchronization on or off"
-msgstr "Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c Ð¸Ð»Ð¸ Ð²Ñ\8bключить синхронизацию времени по сети"
+msgstr "Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c Ð¸Ð»Ð¸ Ð¾Ñ\82ключить синхронизацию времени по сети"
 
 #: src/timedate/org.freedesktop.timedate1.policy:54
 msgid ""
 "Authentication is required to control whether network time synchronization "
 "shall be enabled."
 msgstr ""
-"ЧÑ\82обÑ\8b Ð²ÐºÐ»Ñ\8eÑ\87иÑ\82Ñ\8c Ð¸Ð»Ð¸ Ð²Ñ\8bключить синхронизацию времени по сети, необходимо "
+"ЧÑ\82обÑ\8b Ð²ÐºÐ»Ñ\8eÑ\87иÑ\82Ñ\8c Ð¸Ð»Ð¸ Ð¾Ñ\82ключить синхронизацию времени по сети, необходимо "
 "пройти аутентификацию."
 
 #: src/core/dbus-unit.c:359
index 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 43c2060d5955f36807a55c3f54f6c0569b137629..88f88a83edb81a0cdb9eb98130bf6fa0d345a50e 100644 (file)
@@ -37,14 +37,30 @@ KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{serial}=="?*", ENV{ID_S
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{wwid}=="?*", ENV{ID_WWN}="$attr{wwid}"
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatibility
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+  ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+  ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
+# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
 KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
   OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
+  OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
 
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{nsid}=="?*", ENV{ID_NSID}="$attr{nsid}"
+# obsolete symlink with non-escaped characters, kept for backward compatibility
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+  ENV{ID_MODEL}!="*/*", ENV{ID_SERIAL_SHORT}!="*/*", \
+  ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
+# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
 KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
   OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
+  OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
 
 # virtio-blk
 KERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"
@@ -63,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 b1053f2cbbfe23528a035ad2cc977d38d3511e63..b87096ece0a8d088048fc6c0e32c25866ee5dcf3 100644 (file)
@@ -2,7 +2,7 @@
 
 ACTION=="remove", GOTO="camera_end"
 
-SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb" , \
+SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb", \
   IMPORT{builtin}="hwdb 'camera:usb:v$env{ID_VENDOR_ID}p$env{ID_MODEL_ID}:name:$attr{name}:'", \
   GOTO="camera_end"
 
diff --git a/rules.d/90-iocost.rules b/rules.d/90-iocost.rules
new file mode 100644 (file)
index 0000000..50f778a
--- /dev/null
@@ -0,0 +1,20 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+SUBSYSTEM!="block", GOTO="iocost_end"
+
+ENV{DEVTYPE}=="partition", GOTO="iocost_end"
+
+ACTION=="remove", GOTO="iocost_end"
+
+ENV{ID_MODEL}!="", IMPORT{builtin}="hwdb 'block::name:$env{ID_MODEL}:fwrev:$env{ID_REVISION}:'"
+
+ENV{IOCOST_SOLUTIONS}!="", RUN+="iocost apply $env{DEVNAME}"
+
+LABEL="iocost_end"
index 09edd58da2a765fb0594fe6944719cb490aba253..7280f5b995d6d9b395843c775299990e50fc0d38 100644 (file)
@@ -28,6 +28,7 @@ rules = [
                '78-sound-card.rules',
                '80-net-setup-link.rules',
                '81-net-dhcp.rules',
+               '90-iocost.rules',
               )],
 
         [files('80-drivers.rules'),
index cebd25a87180e89f7235979625974db27cbc7612..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'
     )
@@ -139,7 +139,7 @@ _busctl() {
         fi
     done
 
-    n=$(($COMP_CWORD - $i))
+    n=$((COMP_CWORD - i))
 
     if [[ -z ${verb-} ]]; then
         comps=${VERBS[*]}
index 71789277b09f8fced1e59c7d930be4eba5269d80..30e5da4aa28e06a023bd4d13a9a983b385496513 100644 (file)
@@ -36,7 +36,7 @@ _portablectl() {
     local -A OPTS=(
         [STANDALONE]='-q --quiet --runtime --no-reload --cat --no-pager --no-legend
                               --no-ask-password --enable --now -h --help --version'
-        [ARG]='-p --profile --copy -H --host -M --machine'
+        [ARG]='-p --profile --copy -H --host -M --machine --extension'
     )
 
     local -A VERBS=(
@@ -60,6 +60,10 @@ _portablectl() {
             --machine|-M)
                 comps=$( __get_machines )
                 ;;
+            --extension)
+                comps=$( compgen -A file -- "$cur" )
+                compopt -o filenames
+                ;;
         esac
         COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
         return 0
@@ -78,7 +82,7 @@ _portablectl() {
         fi
     done
 
-    n=$(($COMP_CWORD - $i))
+    n=$((COMP_CWORD - i))
 
     if [[ -z ${verb-} ]]; then
         comps=${VERBS[*]}
diff --git a/shell-completion/bash/systemd-confext b/shell-completion/bash/systemd-confext
new file mode 100644 (file)
index 0000000..c8eca3b
--- /dev/null
@@ -0,0 +1,85 @@
+# systemd-confext(8) completion                        -*- shell-script -*-
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <https://www.gnu.org/licenses/>.
+
+__contains_word() {
+    local w word=$1; shift
+    for w in "$@"; do
+        [[ $w = "$word" ]] && return
+    done
+}
+
+_systemd-confext() {
+    local i verb comps
+    local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
+    local -A OPTS=(
+        [STANDALONE]='-h --help --version
+                     --no-pager
+                     --no-legend
+                     --force'
+        [ARG]='--root
+               --json'
+    )
+
+    local -A VERBS=(
+        [STANDALONE]='status
+                      merge
+                      unmerge
+                      refresh
+                      list'
+    )
+
+    _init_completion || return
+
+    if __contains_word "$prev" ${OPTS[ARG]}; then
+        case $prev in
+            --root)
+                comps=$(compgen -A directory -- "$cur" )
+                compopt -o dirnames
+                ;;
+            --json)
+                comps='pretty short off'
+                ;;
+        esac
+        COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+        return 0
+    fi
+
+    if [[ "$cur" = -* ]]; then
+        COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+        return 0
+    fi
+
+    for ((i=0; i < COMP_CWORD; i++)); do
+        if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
+                ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
+            verb=${COMP_WORDS[i]}
+            break
+        fi
+    done
+
+    if [[ -z ${verb-} ]]; then
+        comps=${VERBS[*]}
+    elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
+        comps=''
+    fi
+
+    COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+    return 0
+}
+
+complete -F _systemd-confext systemd-confext
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 ea141c54bdf65ee48d6ae29d63aacea645402624..1caa30d7d474f7058588124f301b4b0dd11007e5 100644 (file)
@@ -81,6 +81,7 @@ static int open_sockets(int *ret_epoll_fd, bool accept) {
 
                 log_close();
                 log_set_open_when_needed(true);
+                log_settle_target();
 
                 r = close_all_fds(except, n);
                 if (r < 0)
@@ -346,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;
 
diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c
new file mode 100644 (file)
index 0000000..13db7f5
--- /dev/null
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "analyze-fdstore.h"
+#include "analyze.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "fd-util.h"
+#include "format-table.h"
+
+static int dump_fdstore(sd_bus *bus, const char *arg) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        _cleanup_free_ char *unit = NULL;
+        int r;
+
+        assert(bus);
+        assert(arg);
+
+        r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_GLOB, ".service", &unit);
+        if (r < 0)
+                return log_error_errno(r, "Failed to mangle name '%s': %m", arg);
+
+        r = bus_call_method(
+                        bus,
+                        bus_systemd_mgr,
+                        "DumpUnitFileDescriptorStore",
+                        &error,
+                        &reply,
+                        "s", unit);
+        if (r < 0)
+                return log_error_errno(r, "Failed to call DumpUnitFileDescriptorStore: %s",
+                                       bus_error_message(&error, r));
+
+        r = sd_bus_message_enter_container(reply, 'a', "(suuutuusu)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        table = table_new("fdname", "type", "devno", "inode", "rdevno", "path", "flags");
+        if (!table)
+                return log_oom();
+
+        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+        (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
+
+        for (;;) {
+                uint32_t mode, major, minor, rmajor, rminor, flags;
+                const char *fdname, *path;
+                uint64_t inode;
+
+                r = sd_bus_message_read(
+                                reply,
+                                "(suuutuusu)",
+                                &fdname,
+                                &mode,
+                                &major, &minor,
+                                &inode,
+                                &rmajor, &rminor,
+                                &path,
+                                &flags);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                r = table_add_many(
+                                table,
+                                TABLE_STRING, fdname,
+                                TABLE_MODE_INODE_TYPE, mode,
+                                TABLE_DEVNUM, makedev(major, minor),
+                                TABLE_UINT64, inode,
+                                TABLE_DEVNUM, makedev(rmajor, rminor),
+                                TABLE_PATH, path,
+                                TABLE_STRING, accmode_to_string(flags));
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_get_rows(table) <= 0)
+                log_info("No file descriptors in fdstore of '%s'.", unit);
+        else {
+                r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to output table: %m");
+        }
+
+        return EXIT_SUCCESS;
+}
+
+int verb_fdstore(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        r = acquire_bus(&bus, NULL);
+        if (r < 0)
+                return bus_log_connect_error(r, arg_transport);
+
+        STRV_FOREACH(arg, strv_skip(argv, 1)) {
+                r = dump_fdstore(bus, *arg);
+                if (r < 0)
+                        return r;
+        }
+
+        return EXIT_SUCCESS;
+}
diff --git a/src/analyze/analyze-fdstore.h b/src/analyze/analyze-fdstore.h
new file mode 100644 (file)
index 0000000..0b990db
--- /dev/null
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+
+int verb_fdstore(int argc, char *argv[], void *userdata);
diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c
new file mode 100644 (file)
index 0000000..0262166
--- /dev/null
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "analyze-image-policy.h"
+#include "analyze.h"
+#include "format-table.h"
+#include "terminal-util.h"
+
+static int table_add_designator_line(Table *table, PartitionDesignator d, PartitionPolicyFlags f) {
+        _cleanup_free_ char *q = NULL;
+        const char *color;
+        int r;
+
+        assert(table);
+        assert(f >= 0);
+
+        if (partition_policy_flags_to_string(f & _PARTITION_POLICY_USE_MASK, /* simplify= */ true, &q) < 0)
+                return log_oom();
+
+        color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() :
+                ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ==
+                   (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() :
+                (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT ? ansi_highlight_red() :
+                !(f & PARTITION_POLICY_UNPROTECTED) ? ansi_highlight_green() : NULL;
+
+        if (d < 0)
+                r = table_add_many(table,
+                                   TABLE_STRING, "default",
+                                   TABLE_SET_COLOR, ansi_highlight_green(),
+                                   TABLE_STRING, q,
+                                   TABLE_SET_COLOR, color);
+        else
+                r = table_add_many(table,
+                                   TABLE_STRING, partition_designator_to_string(d),
+                                   TABLE_SET_COLOR, ansi_normal(),
+                                   TABLE_STRING, q,
+                                   TABLE_SET_COLOR, color);
+        if (r < 0)
+                return table_log_add_error(r);
+
+        switch (f & _PARTITION_POLICY_READ_ONLY_MASK) {
+
+        case PARTITION_POLICY_READ_ONLY_ON:
+                r = table_add_many(table, TABLE_BOOLEAN, true);
+                break;
+
+        case PARTITION_POLICY_READ_ONLY_OFF:
+                r = table_add_many(table, TABLE_BOOLEAN, false);
+                break;
+
+        default:
+                r = table_add_many(table, TABLE_EMPTY);
+                break;
+        }
+        if (r < 0)
+                return table_log_add_error(r);
+
+        switch (f & _PARTITION_POLICY_GROWFS_MASK) {
+
+        case PARTITION_POLICY_GROWFS_ON:
+                r = table_add_many(table, TABLE_BOOLEAN, true);
+                break;
+
+        case PARTITION_POLICY_GROWFS_OFF:
+                r = table_add_many(table, TABLE_BOOLEAN, false);
+                break;
+
+        default:
+                r = table_add_many(table, TABLE_EMPTY);
+                break;
+        }
+
+        if (r < 0)
+                return table_log_add_error(r);
+
+        return 0;
+}
+
+int verb_image_policy(int argc, char *argv[], void *userdata) {
+        int r;
+
+        for (int i = 1; i < argc; i++) {
+                _cleanup_(table_unrefp) Table *table = NULL;
+                _cleanup_(image_policy_freep) ImagePolicy *pbuf = NULL;
+                _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL;
+                const ImagePolicy *p;
+
+                /* NB: The magic '@' strings are not officially documented for now, since we might change
+                 * around defaults (and in particular where precisely to reuse policy). We should document
+                 * them once the dust has settled a bit. For now it's just useful for debugging and
+                 * introspect our own defaults without guaranteeing API safety. */
+                if (streq(argv[i], "@sysext"))
+                        p = &image_policy_sysext;
+                else if (streq(argv[i], "@sysext-strict"))
+                        p = &image_policy_sysext_strict;
+                else if (streq(argv[i], "@container"))
+                        p = &image_policy_container;
+                else if (streq(argv[i], "@service"))
+                        p = &image_policy_service;
+                else if (streq(argv[i], "@host"))
+                        p = &image_policy_host;
+                else {
+                        r = image_policy_from_string(argv[i], &pbuf);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse image policy '%s': %m", argv[i]);
+
+                        p = pbuf;
+                }
+
+                r = image_policy_to_string(p, /* simplify= */ false, &as_string);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]);
+
+                r = image_policy_to_string(p, /* simplify= */ true, &as_string_simplified);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]);
+
+                pager_open(arg_pager_flags);
+
+                if (streq(as_string, as_string_simplified))
+                        printf("Analyzing policy: %s%s%s\n", ansi_highlight_magenta_underline(), as_string, ansi_normal());
+                else
+                        printf("Analyzing policy: %s%s%s\n"
+                               "       Long form: %s%s%s\n",
+                               ansi_highlight(), as_string_simplified, ansi_normal(),
+                               ansi_grey(), as_string, ansi_normal());
+
+                table = table_new("partition", "mode", "read-only", "growfs");
+                if (!table)
+                        return log_oom();
+
+                (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+                for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+                        PartitionPolicyFlags f = image_policy_get_exhaustively(p, d);
+                        assert(f >= 0);
+
+                        r = table_add_designator_line(table, d, f);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = table_add_designator_line(table, _PARTITION_DESIGNATOR_INVALID, image_policy_default(p));
+                if (r < 0)
+                        return r;
+
+                putc('\n', stdout);
+
+                r = table_print(table, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return EXIT_SUCCESS;
+}
diff --git a/src/analyze/analyze-image-policy.h b/src/analyze/analyze-image-policy.h
new file mode 100644 (file)
index 0000000..fa08447
--- /dev/null
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_image_policy(int argc, char *argv[], void *userdata);
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 7662bf1827d356c3566707b73c4735000f251c8f..56bc3627767bfec3d7c9c3200f25ff877f1a71a0 100644 (file)
@@ -2753,7 +2753,7 @@ static int offline_security_checks(
                                 profile = profile_path;
                         }
 
-                        r = copy_file(profile, dropin, 0, 0644, 0, 0, 0);
+                        r = copy_file(profile, dropin, 0, 0644, 0);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to copy: %m");
                 }
index 50662da0627095abec1e141798d28991f52d00c3..66a52da8976d504bf7d50daf353f1921253e24e2 100644 (file)
@@ -58,15 +58,35 @@ static int load_kernel_syscalls(Set **ret) {
         return 0;
 }
 
+static int syscall_set_add(Set **s, const SyscallFilterSet *set) {
+        int r;
+
+        assert(s);
+
+        if (!set)
+                return 0;
+
+        NULSTR_FOREACH(sc, set->value) {
+                if (sc[0] == '@')
+                        continue;
+
+                r = set_put_strdup(s, sc);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
         if (!set)
                 return;
 
-        NULSTR_FOREACH(syscall, set->value) {
-                if (syscall[0] == '@')
+        NULSTR_FOREACH(sc, set->value) {
+                if (sc[0] == '@')
                         continue;
 
-                free(set_remove(s, syscall));
+                free(set_remove(s, sc));
         }
 }
 
@@ -84,6 +104,7 @@ static void dump_syscall_filter(const SyscallFilterSet *set) {
 
 int verb_syscall_filters(int argc, char *argv[], void *userdata) {
         bool first = true;
+        int r;
 
         pager_open(arg_pager_flags);
 
@@ -91,9 +112,9 @@ int verb_syscall_filters(int argc, char *argv[], void *userdata) {
                 _cleanup_set_free_ Set *kernel = NULL, *known = NULL;
                 int k = 0;  /* explicit initialization to appease gcc */
 
-                NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
-                        if (set_put_strdup(&known, sys) < 0)
-                                return log_oom();
+                r = syscall_set_add(&known, syscall_filter_sets + SYSCALL_FILTER_SET_KNOWN);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to prepare set of known system calls: %m");
 
                 if (!arg_quiet)
                         k = load_kernel_syscalls(&kernel);
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 98d48f627665344f4ce415d1da7ab18f179397b6..3b463f2ac7731d711c90c25a00bbecd58acaeac6 100644 (file)
@@ -40,7 +40,7 @@ static int process_aliases(char *argv[], char *tempdir, char ***ret) {
                 if (!dst)
                         return -ENOMEM;
 
-                r = copy_file(src, dst, 0, 0644, 0, 0, COPY_REFLINK);
+                r = copy_file(src, dst, 0, 0644, COPY_REFLINK);
                 if (r < 0)
                         return r;
 
index 12c0bd653fa68d6dbcfe6ededeae2d4f8b78508c..09a38e7930ee07d39c6b21a937e97348e72c4d5c 100644 (file)
 #include "analyze-calendar.h"
 #include "analyze-capability.h"
 #include "analyze-cat-config.h"
+#include "analyze-compare-versions.h"
 #include "analyze-condition.h"
 #include "analyze-critical-chain.h"
 #include "analyze-dot.h"
 #include "analyze-dump.h"
 #include "analyze-exit-status.h"
+#include "analyze-fdstore.h"
 #include "analyze-filesystems.h"
 #include "analyze-inspect-elf.h"
 #include "analyze-log-control.h"
@@ -36,8 +38,8 @@
 #include "analyze-timestamp.h"
 #include "analyze-unit-files.h"
 #include "analyze-unit-paths.h"
-#include "analyze-compare-versions.h"
 #include "analyze-verify.h"
+#include "analyze-image-policy.h"
 #include "build.h"
 #include "bus-error.h"
 #include "bus-locator.h"
@@ -108,6 +110,7 @@ bool arg_quiet = false;
 char *arg_profile = NULL;
 bool arg_legend = true;
 bool arg_table = false;
+ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
@@ -116,6 +119,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 int acquire_bus(sd_bus **bus, bool *use_full_bus) {
         int r;
@@ -229,6 +233,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  security [UNIT...]         Analyze security of unit\n"
                "  inspect-elf FILE...        Parse and print ELF package metadata\n"
                "  malloc [D-BUS SERVICE...]  Dump malloc stats of a D-Bus service\n"
+               "  fdstore SERVICE...         Show file descriptor store contents of service\n"
                "\nOptions:\n"
                "     --recursive-errors=MODE Control which units are verified\n"
                "     --offline=BOOL          Perform a security review on unit file(s)\n"
@@ -266,6 +271,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  -q --quiet                 Do not emit hints\n"
                "     --root=PATH             Operate on an alternate filesystem root\n"
                "     --image=PATH            Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY   Specify disk image dissection policy\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                ansi_highlight(),
@@ -286,6 +292,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_REQUIRE,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_SYSTEM,
                 ARG_USER,
                 ARG_GLOBAL,
@@ -315,6 +322,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "require",          no_argument,       NULL, ARG_REQUIRE          },
                 { "root",             required_argument, NULL, ARG_ROOT             },
                 { "image",            required_argument, NULL, ARG_IMAGE            },
+                { "image-policy",     required_argument, NULL, ARG_IMAGE_POLICY     },
                 { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
                 { "offline",          required_argument, NULL, ARG_OFFLINE          },
                 { "threshold",        required_argument, NULL, ARG_THRESHOLD        },
@@ -382,6 +390,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_SYSTEM:
                         arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
                         break;
@@ -531,9 +545,9 @@ static int parse_argv(int argc, char *argv[]) {
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Option --offline= is only supported for security right now.");
 
-        if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot"))
+        if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore"))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Option --json= is only supported for security, inspect-elf, and plot right now.");
+                                       "Option --json= is only supported for security, inspect-elf, plot, and fdstore right now.");
 
         if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -620,6 +634,8 @@ static int run(int argc, char *argv[]) {
                 { "security",          VERB_ANY, VERB_ANY, 0,            verb_security          },
                 { "inspect-elf",       2,        VERB_ANY, 0,            verb_elf_inspection    },
                 { "malloc",            VERB_ANY, VERB_ANY, 0,            verb_malloc            },
+                { "fdstore",           2,        VERB_ANY, 0,            verb_fdstore           },
+                { "image-policy",      2,        2,        0,            verb_image_policy      },
                 {}
         };
 
@@ -640,6 +656,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK |
                                 DISSECT_IMAGE_READ_ONLY,
index 2f623e3201256b99fa29fc85d1a1430ea18760f8..84575cd9a9d258f4cf2de7a66b4d2fd5396dd53b 100644 (file)
@@ -38,6 +38,7 @@ extern bool arg_quiet;
 extern char *arg_profile;
 extern bool arg_legend;
 extern bool arg_table;
+extern ImagePolicy *arg_image_policy;
 
 int acquire_bus(sd_bus **bus, bool *use_full_bus);
 
index ac40600a6dedd53325fee303e35385df97cf811b..c50c35f09f6e0907df5e40f4e114753ad10baa0c 100644 (file)
@@ -11,7 +11,9 @@ systemd_analyze_sources = files(
         'analyze-dot.c',
         'analyze-dump.c',
         'analyze-exit-status.c',
+        'analyze-fdstore.c',
         'analyze-filesystems.c',
+        'analyze-image-policy.c',
         'analyze-inspect-elf.c',
         'analyze-log-control.c',
         'analyze-malloc.c',
index f161e659730d863a66d36532fc5496e1b5be1dc3..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) {
@@ -138,14 +142,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 /* Empty argument or explicit string "masked" for default behaviour. */
                                 arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT);
                         else {
-                                bool b;
-
-                                r = parse_boolean_argument("--echo=", optarg, &b);
+                                r = parse_boolean_argument("--echo=", optarg, NULL);
                                 if (r < 0)
                                         return r;
 
-                                SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, b);
-                                SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !b);
+                                SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, r);
+                                SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !r);
                         }
                         break;
 
@@ -195,13 +197,11 @@ static int parse_argv(int argc, char *argv[]) {
         if (isempty(emoji) || streq(emoji, "auto"))
                 SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO));
         else {
-                bool b;
-
-                r = parse_boolean_argument("--emoji=", emoji, &b);
+                r = parse_boolean_argument("--emoji=", emoji, NULL);
                 if (r < 0)
                          return r;
 
-                SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !b);
+                SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r);
         }
 
         if (argc > optind) {
index 096526a8570e224592eabe42f0c6ebb30f15776f..788f3abc37f7d2636a96dc06e293cc9330f9160f 100644 (file)
@@ -197,10 +197,10 @@ Architecture uname_architecture(void);
 #  elif defined(__SH4A__)
 #    define LIB_ARCH_TUPLE "sh4a-linux-gnu"
 #  endif
-#elif defined(__loongarch64)
+#elif defined(__loongarch_lp64)
 #  define native_architecture() ARCHITECTURE_LOONGARCH64
 #  if defined(__loongarch_double_float)
-#    define LIB_ARCH_TUPLE "loongarch64-linux-gnuf64"
+#    define LIB_ARCH_TUPLE "loongarch64-linux-gnu"
 #  elif defined(__loongarch_single_float)
 #    define LIB_ARCH_TUPLE "loongarch64-linux-gnuf32"
 #  elif defined(__loongarch_soft_float)
diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h
deleted file mode 100644 (file)
index 448471f..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <dirent.h>
-#include <stdio.h>
-
-#include "stat-util.h"
-
-typedef enum ChaseSymlinksFlags {
-        CHASE_PREFIX_ROOT        = 1 << 0,  /* The specified path will be prefixed by the specified root before beginning the iteration */
-        CHASE_NONEXISTENT        = 1 << 1,  /* It's OK if the path doesn't actually exist. */
-        CHASE_NO_AUTOFS          = 1 << 2,  /* Return -EREMOTE if autofs mount point found */
-        CHASE_SAFE               = 1 << 3,  /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
-        CHASE_TRAIL_SLASH        = 1 << 4,  /* Any trailing slash will be preserved */
-        CHASE_STEP               = 1 << 5,  /* Just execute a single step of the normalization */
-        CHASE_NOFOLLOW           = 1 << 6,  /* Do not follow the path's right-most component. With ret_fd, when the path's
-                                             * right-most component refers to symlink, return O_PATH fd of the symlink. */
-        CHASE_WARN               = 1 << 7,  /* Emit an appropriate warning when an error is encountered.
-                                             * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
-        CHASE_AT_RESOLVE_IN_ROOT = 1 << 8,  /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
-                                             * relative to the given directory fd instead of root. */
-        CHASE_PROHIBIT_SYMLINKS  = 1 << 9,  /* Refuse all symlinks */
-        CHASE_PARENT             = 1 << 10, /* Chase the parent directory of the given path. Note that the
-                                             * full path is still stored in ret_path and only the returned
-                                             * file descriptor will point to the parent directory. */
-        CHASE_MKDIR_0755         = 1 << 11, /* Create any missing parent directories in the given path. */
-} ChaseSymlinksFlags;
-
-bool unsafe_transition(const struct stat *a, const struct stat *b);
-
-/* How many iterations to execute before returning -ELOOP */
-#define CHASE_SYMLINKS_MAX 32
-
-int chase_symlinks(const char *path_with_prefix, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, int *ret_fd);
-
-int chase_symlinks_and_open(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
-int chase_symlinks_and_opendir(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, DIR **ret_dir);
-int chase_symlinks_and_stat(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat);
-int chase_symlinks_and_access(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path);
-int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseSymlinksFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
-int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path);
-
-int chase_symlinks_at(int dir_fd, const char *path, ChaseSymlinksFlags flags, char **ret_path, int *ret_fd);
-int chase_symlinks_at_and_open(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
similarity index 57%
rename from src/basic/chase-symlinks.c
rename to src/basic/chase.c
index 5c2b56ea20a381bd4c86c9f910a0d62b91523824..373252b64526f28c5f2e8acc2befc8569347022b 100644 (file)
@@ -3,7 +3,7 @@
 #include <linux/magic.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
@@ -24,7 +24,7 @@ bool unsafe_transition(const struct stat *a, const struct stat *b) {
         return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
 }
 
-static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFlags flags) {
+static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
         _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
         struct stat st;
 
@@ -44,7 +44,7 @@ static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFl
                                  strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
 }
 
-static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags flags) {
+static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
         _cleanup_free_ char *n1 = NULL;
 
         if (!FLAGS_SET(flags, CHASE_WARN))
@@ -57,7 +57,7 @@ static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags f
                                  strna(n1), path);
 }
 
-static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
+static int log_prohibited_symlink(int fd, ChaseFlags flags) {
         _cleanup_free_ char *n1 = NULL;
 
         assert(fd >= 0);
@@ -72,37 +72,34 @@ static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
                                  strna(n1));
 }
 
-int chase_symlinks_at(
-                int dir_fd,
-                const char *path,
-                ChaseSymlinksFlags flags,
-                char **ret_path,
-                int *ret_fd) {
-
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
         _cleanup_free_ char *buffer = NULL, *done = NULL;
         _cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
-        unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
+        unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
         bool exists = true, append_trail_slash = false;
-        struct stat previous_stat;
+        struct stat st; /* stat obtained from fd */
         const char *todo;
         int r;
 
         assert(path);
         assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
+        assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
+        assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME));
+        assert(!FLAGS_SET(flags, CHASE_MKDIR_0755) || (flags & (CHASE_NONEXISTENT | CHASE_PARENT)) != 0);
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
         /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
-        if ((flags & CHASE_NONEXISTENT))
+        if (FLAGS_SET(flags, CHASE_NONEXISTENT))
                 assert(!ret_fd);
 
-        if ((flags & CHASE_STEP))
+        if (FLAGS_SET(flags, CHASE_STEP))
                 assert(!ret_fd);
 
         if (isempty(path))
                 path = ".";
 
         /* This function resolves symlinks of the path relative to the given directory file descriptor. If
-         * CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
+         * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
          * are resolved relative to the given directory file descriptor. Otherwise, they are resolved
          * relative to the root directory of the host.
          *
@@ -114,12 +111,26 @@ int chase_symlinks_at(
          * given directory file descriptor, even if it is absolute. If the given directory file descriptor is
          * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
          *
-         * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this
-         * functions returns a relative path in "ret_path" because openat() like functions generally ignore
-         * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is
-         * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because
-         * otherwise, if the caller passes the returned relative path to another openat() like function, it
-         * would be resolved relative to the current working directory instead of to "/".
+         * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function
+         * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat()
+         * like functions generally ignore the directory fd if they are provided with an absolute path. When
+         * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file
+         * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When
+         * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path"
+         * because otherwise, if the caller passes the returned relative path to another openat() like
+         * function, it would be resolved relative to the current working directory instead of to "/".
+         *
+         * Summary about the result path:
+         * - "dir_fd" points to the root directory
+         *    → result will be absolute
+         * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set
+         *    → relative
+         * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set
+         *    → relative when all resolved symlinks are relative, otherwise absolute
+         * - "dir_fd" is AT_FDCWD, and "path" is absolute
+         *    → absolute
+         * - "dir_fd" is AT_FDCWD, and "path" is relative
+         *    → relative when all resolved symlinks are relative, otherwise absolute
          *
          * Algorithmically this operates on two path buffers: "done" are the components of the path we
          * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
@@ -162,6 +173,17 @@ int chase_symlinks_at(
          *    the mount point is emitted. CHASE_WARN cannot be used in PID 1.
          */
 
+        if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
+                /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
+                 * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
+
+                r = dir_fd_is_root_or_cwd(dir_fd);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
+        }
+
         if (!(flags &
               (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
                CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) &&
@@ -169,7 +191,7 @@ int chase_symlinks_at(
 
                 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root
                  * set and doesn't care about any of the other special features we provide either. */
-                r = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+                r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
                 if (r < 0)
                         return -errno;
 
@@ -177,15 +199,14 @@ int chase_symlinks_at(
                 return 0;
         }
 
-        if (!buffer) {
-                buffer = strdup(path);
-                if (!buffer)
-                        return -ENOMEM;
-        }
+        buffer = strdup(path);
+        if (!buffer)
+                return -ENOMEM;
 
         /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
-         * a relative path would be interpreted relative to the current working directory. */
-        bool need_absolute = dir_fd == AT_FDCWD && path_is_absolute(path);
+         * a relative path would be interpreted relative to the current working directory. Also, let's make
+         * the result absolute when the file descriptor of the root directory is specified. */
+        bool need_absolute = (dir_fd == AT_FDCWD && path_is_absolute(path)) || dir_fd_is_root(dir_fd) > 0;
         if (need_absolute) {
                 done = strdup("/");
                 if (!done)
@@ -193,7 +214,7 @@ int chase_symlinks_at(
         }
 
         /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
-         * directory file descriptor is provided will we look at CHASE_AT_RESOLVE_IN_ROOT to determine
+         * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
          * whether to resolve symlinks in it or not. */
         if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
                 root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
@@ -213,16 +234,16 @@ int chase_symlinks_at(
         if (fd < 0)
                 return -errno;
 
-        if (fstat(fd, &previous_stat) < 0)
+        if (fstat(fd, &st) < 0)
                 return -errno;
 
-        if (flags & CHASE_TRAIL_SLASH)
+        if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
                 append_trail_slash = ENDSWITH_SET(buffer, "/", "/.");
 
         for (todo = buffer;;) {
                 _cleanup_free_ char *first = NULL;
                 _cleanup_close_ int child = -EBADF;
-                struct stat st;
+                struct stat st_child;
                 const char *e;
 
                 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
@@ -243,6 +264,7 @@ int chase_symlinks_at(
                 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. */
@@ -253,13 +275,19 @@ int chase_symlinks_at(
                         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)
@@ -271,21 +299,19 @@ int chase_symlinks_at(
                         } else
                                 return r;
 
-                        if (flags & CHASE_STEP)
+                        if (FLAGS_SET(flags, CHASE_STEP))
                                 goto chased_one;
 
-                        if (flags & CHASE_SAFE) {
-                                if (unsafe_transition(&previous_stat, &st))
-                                        return log_unsafe_transition(fd, fd_parent, path, flags);
-
-                                previous_stat = st;
-                        }
+                        if (FLAGS_SET(flags, CHASE_SAFE) &&
+                            unsafe_transition(&st, &st_parent))
+                                return log_unsafe_transition(fd, fd_parent, path, flags);
 
                         if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
                                 break;
 
+                        /* update fd and stat */
+                        st = st_parent;
                         close_and_replace(fd, fd_parent);
-
                         continue;
                 }
 
@@ -299,7 +325,7 @@ int chase_symlinks_at(
                                 return r;
 
                         if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
-                                child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755);
+                                child = xopenat(fd, first, O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0755);
                                 if (child < 0)
                                         return child;
                         } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
@@ -307,7 +333,7 @@ int chase_symlinks_at(
                                         return -ENOMEM;
 
                                 break;
-                        } else if (flags & CHASE_NONEXISTENT) {
+                        } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
                                 if (!path_extend(&done, first, todo))
                                         return -ENOMEM;
 
@@ -317,22 +343,21 @@ int chase_symlinks_at(
                                 return r;
                 }
 
-                if (fstat(child, &st) < 0)
+                if (fstat(child, &st_child) < 0)
                         return -errno;
-                if ((flags & CHASE_SAFE) &&
-                    unsafe_transition(&previous_stat, &st))
-                        return log_unsafe_transition(fd, child, path, flags);
 
-                previous_stat = st;
+                if (FLAGS_SET(flags, CHASE_SAFE) &&
+                    unsafe_transition(&st, &st_child))
+                        return log_unsafe_transition(fd, child, path, flags);
 
-                if ((flags & CHASE_NO_AUTOFS) &&
+                if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
                     fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
                         return log_autofs_mount_point(child, path, flags);
 
-                if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
+                if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
                         _cleanup_free_ char *destination = NULL;
 
-                        if (flags & CHASE_PROHIBIT_SYMLINKS)
+                        if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
                                 return log_prohibited_symlink(child, flags);
 
                         /* This is a symlink, in this case read the destination. But let's make sure we
@@ -356,15 +381,17 @@ int chase_symlinks_at(
                                 if (fd < 0)
                                         return fd;
 
-                                if (flags & CHASE_SAFE) {
-                                        if (fstat(fd, &st) < 0)
-                                                return -errno;
+                                if (fstat(fd, &st) < 0)
+                                        return -errno;
 
-                                        if (unsafe_transition(&previous_stat, &st))
-                                                return log_unsafe_transition(child, fd, path, flags);
+                                if (FLAGS_SET(flags, CHASE_SAFE) &&
+                                    unsafe_transition(&st_child, &st))
+                                        return log_unsafe_transition(child, fd, path, flags);
 
-                                        previous_stat = st;
-                                }
+                                /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
+                                 * outside of the specified dir_fd. Let's make the result absolute. */
+                                if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
+                                        need_absolute = true;
 
                                 r = free_and_strdup(&done, need_absolute ? "/" : NULL);
                                 if (r < 0)
@@ -379,7 +406,7 @@ int chase_symlinks_at(
                         free_and_replace(buffer, destination);
                         todo = buffer;
 
-                        if (flags & CHASE_STEP)
+                        if (FLAGS_SET(flags, CHASE_STEP))
                                 goto chased_one;
 
                         continue;
@@ -393,16 +420,28 @@ int chase_symlinks_at(
                         break;
 
                 /* And iterate again, but go one directory further down. */
+                st = st_child;
                 close_and_replace(fd, child);
         }
 
-        if (flags & CHASE_PARENT) {
-                r = fd_verify_directory(fd);
+        if (FLAGS_SET(flags, CHASE_PARENT)) {
+                r = stat_verify_directory(&st);
                 if (r < 0)
                         return r;
         }
 
         if (ret_path) {
+                if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
+                        _cleanup_free_ char *f = NULL;
+
+                        r = path_extract_filename(done, &f);
+                        if (r < 0 && r != -EADDRNOTAVAIL)
+                                return r;
+
+                        /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
+                        free_and_replace(done, f);
+                }
+
                 if (!done) {
                         done = strdup(append_trail_slash ? "./" : ".");
                         if (!done)
@@ -420,7 +459,7 @@ int chase_symlinks_at(
                 *ret_fd = TAKE_FD(fd);
         }
 
-        if (flags & CHASE_STEP)
+        if (FLAGS_SET(flags, CHASE_STEP))
                 return 1;
 
         return exists;
@@ -455,14 +494,8 @@ chased_one:
         return 0;
 }
 
-int chase_symlinks(
-                const char *path,
-                const char *original_root,
-                ChaseSymlinksFlags flags,
-                char **ret_path,
-                int *ret_fd) {
-
-        _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
+int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
+        _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
         _cleanup_close_ int fd = -EBADF, pfd = -EBADF;
         int r;
 
@@ -471,28 +504,28 @@ int chase_symlinks(
         if (isempty(path))
                 return -EINVAL;
 
-        /* A root directory of "/" or "" is identical to none */
-        if (empty_or_root(original_root))
-                original_root = NULL;
-
-        if (original_root) {
-                r = path_make_absolute_cwd(original_root, &root);
+        /* A root directory of "/" or "" is identical to "/". */
+        if (empty_or_root(root))
+                root = "/";
+        else {
+                r = path_make_absolute_cwd(root, &root_abs);
                 if (r < 0)
                         return r;
 
                 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
-                 * end. While we won't resolve the root path we still simplify it. Note that dropping the
-                 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
-                 * anyway. Moreover at the end of this function after processing everything we'll always turn
-                 * the empty string back to "/". */
-                delete_trailing_chars(root, "/");
-                path_simplify(root);
-
-                if (flags & CHASE_PREFIX_ROOT) {
+                 * end. While we won't resolve the root path we still simplify it. */
+                root = path_simplify(root_abs);
+
+                assert(path_is_absolute(root));
+                assert(!empty_or_root(root));
+
+                if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
                         absolute = path_join(root, path);
                         if (!absolute)
                                 return -ENOMEM;
                 }
+
+                flags |= CHASE_AT_RESOLVE_IN_ROOT;
         }
 
         if (!absolute) {
@@ -501,38 +534,47 @@ int chase_symlinks(
                         return r;
         }
 
-        path = path_startswith(absolute, empty_to_root(root));
+        path = path_startswith(absolute, root);
         if (!path)
-                return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
-                                        SYNTHETIC_ERRNO(ECHRNG),
-                                        "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
-                                        absolute, empty_to_root(root));
+                return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
+                                      SYNTHETIC_ERRNO(ECHRNG),
+                                      "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+                                      absolute, root);
 
-        fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
+        fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
         if (fd < 0)
                 return -errno;
 
-        flags |= CHASE_AT_RESOLVE_IN_ROOT;
-        flags &= ~CHASE_PREFIX_ROOT;
-
-        r = chase_symlinks_at(fd, path, flags, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
+        r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
         if (r < 0)
                 return r;
 
         if (ret_path) {
-                _cleanup_free_ char *q = NULL;
+                if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
 
-                q = path_join(empty_to_root(root), p);
-                if (!q)
-                        return -ENOMEM;
+                        /* When "root" points to the root directory, the result of chaseat() is always
+                         * absolute, hence it is not necessary to prefix with the root. When "root" points to
+                         * a non-root directory, the result path is always normalized and relative, hence
+                         * we can simply call path_join() and not necessary to call path_simplify().
+                         * Note that the result of chaseat() may start with "." (more specifically, it may be
+                         * "." or "./"), and we need to drop "." in that case. */
 
-                path_simplify(q);
+                        if (empty_or_root(root))
+                                assert(path_is_absolute(p));
+                        else {
+                                char *q;
 
-                if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/."))
-                        if (!strextend(&q, "/"))
-                                return -ENOMEM;
+                                assert(!path_is_absolute(p));
+
+                                q = path_join(root, p + (*p == '.'));
+                                if (!q)
+                                        return -ENOMEM;
+
+                                free_and_replace(p, q);
+                        }
+                }
 
-                *ret_path = TAKE_PTR(q);
+                *ret_path = TAKE_PTR(p);
         }
 
         if (ret_fd)
@@ -541,13 +583,38 @@ int chase_symlinks(
         return r;
 }
 
-int chase_symlinks_and_open(
-                const char *path,
-                const char *root,
-                ChaseSymlinksFlags chase_flags,
-                int open_flags,
-                char **ret_path) {
+int chaseat_prefix_root(const char *path, const char *root, char **ret) {
+        char *q;
+        int r;
+
+        assert(path);
+        assert(ret);
+
+        /* This is mostly for prefixing the result of chaseat(). */
 
+        if (!path_is_absolute(path)) {
+                _cleanup_free_ char *root_abs = NULL;
+
+                /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
+                assert(!empty_or_root(root));
+
+                r = path_make_absolute_cwd(root, &root_abs);
+                if (r < 0)
+                        return r;
+
+                root = path_simplify(root_abs);
+
+                q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
+        } else
+                q = strdup(path);
+        if (!q)
+                return -ENOMEM;
+
+        *ret = q;
+        return 0;
+}
+
+int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL, *fname = NULL;
         mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
@@ -563,7 +630,7 @@ int chase_symlinks_and_open(
                                           open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
                                           mode));
 
-        r = chase_symlinks(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
+        r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
         if (r < 0)
                 return r;
         assert(path_fd >= 0);
@@ -572,14 +639,13 @@ int chase_symlinks_and_open(
         if (isempty(q))
                 q = ".";
 
-        r = path_extract_filename(q, &fname);
-        if (r < 0 && r != -EADDRNOTAVAIL)
-                return r;
+        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
+                r = path_extract_filename(q, &fname);
+                if (r < 0 && r != -EADDRNOTAVAIL)
+                        return r;
+        }
 
-        if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EADDRNOTAVAIL)
-                r = fd_reopen(path_fd, open_flags);
-        else
-                r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
+        r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
         if (r < 0)
                 return r;
 
@@ -589,13 +655,7 @@ int chase_symlinks_and_open(
         return r;
 }
 
-int chase_symlinks_and_opendir(
-                const char *path,
-                const char *root,
-                ChaseSymlinksFlags chase_flags,
-                char **ret_path,
-                DIR **ret_dir) {
-
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         DIR *d;
@@ -615,7 +675,7 @@ int chase_symlinks_and_opendir(
                 return 0;
         }
 
-        r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
         if (r < 0)
                 return r;
         assert(path_fd >= 0);
@@ -631,13 +691,7 @@ int chase_symlinks_and_opendir(
         return 0;
 }
 
-int chase_symlinks_and_stat(
-                const char *path,
-                const char *root,
-                ChaseSymlinksFlags chase_flags,
-                char **ret_path,
-                struct stat *ret_stat) {
-
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -647,16 +701,12 @@ int chase_symlinks_and_stat(
         assert(ret_stat);
 
         if (empty_or_root(root) && !ret_path &&
-            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
                 /* Shortcut this call if none of the special features of this call are requested */
+                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
+                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
 
-                if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
-                        return -errno;
-
-                return 1;
-        }
-
-        r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
         if (r < 0)
                 return r;
         assert(path_fd >= 0);
@@ -667,16 +717,10 @@ int chase_symlinks_and_stat(
         if (ret_path)
                 *ret_path = TAKE_PTR(p);
 
-        return 1;
+        return 0;
 }
 
-int chase_symlinks_and_access(
-                const char *path,
-                const char *root,
-                ChaseSymlinksFlags chase_flags,
-                int access_mode,
-                char **ret_path) {
-
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -685,16 +729,12 @@ int chase_symlinks_and_access(
         assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
 
         if (empty_or_root(root) && !ret_path &&
-            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
                 /* Shortcut this call if none of the special features of this call are requested */
+                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
+                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
 
-                if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
-                        return -errno;
-
-                return 1;
-        }
-
-        r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+        r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
         if (r < 0)
                 return r;
         assert(path_fd >= 0);
@@ -706,13 +746,13 @@ int chase_symlinks_and_access(
         if (ret_path)
                 *ret_path = TAKE_PTR(p);
 
-        return 1;
+        return 0;
 }
 
-int chase_symlinks_and_fopen_unlocked(
+int chase_and_fopen_unlocked(
                 const char *path,
                 const char *root,
-                ChaseSymlinksFlags chase_flags,
+                ChaseFlags chase_flags,
                 const char *open_flags,
                 char **ret_path,
                 FILE **ret_file) {
@@ -730,7 +770,7 @@ int chase_symlinks_and_fopen_unlocked(
         if (mode_flags < 0)
                 return mode_flags;
 
-        fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
+        fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
         if (fd < 0)
                 return fd;
 
@@ -744,13 +784,7 @@ int chase_symlinks_and_fopen_unlocked(
         return 0;
 }
 
-int chase_symlinks_and_unlink(
-                const char *path,
-                const char *root,
-                ChaseSymlinksFlags chase_flags,
-                int unlink_flags,
-                char **ret_path) {
-
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
         _cleanup_free_ char *p = NULL, *fname = NULL;
         _cleanup_close_ int fd = -EBADF;
         int r;
@@ -758,7 +792,7 @@ int chase_symlinks_and_unlink(
         assert(path);
         assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
 
-        fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
+        fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
         if (fd < 0)
                 return fd;
 
@@ -775,13 +809,19 @@ int chase_symlinks_and_unlink(
         return 0;
 }
 
-int chase_symlinks_at_and_open(
-                int dir_fd,
-                const char *path,
-                ChaseSymlinksFlags chase_flags,
-                int open_flags,
-                char **ret_path) {
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
+        int pfd, r;
 
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+        r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
+        if (r < 0)
+                return r;
+
+        return pfd;
+}
+
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) {
         _cleanup_close_ int path_fd = -EBADF;
         _cleanup_free_ char *p = NULL, *fname = NULL;
         mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
@@ -796,18 +836,17 @@ int chase_symlinks_at_and_open(
                                           open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
                                           mode));
 
-        r = chase_symlinks_at(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
+        r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
         if (r < 0)
                 return r;
 
-        r = path_extract_filename(p, &fname);
-        if (r < 0 && r != -EDESTADDRREQ)
-                return r;
+        if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
+                r = path_extract_filename(p, &fname);
+                if (r < 0 && r != -EADDRNOTAVAIL)
+                        return r;
+        }
 
-        if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EDESTADDRREQ)
-                r = fd_reopen(path_fd, open_flags);
-        else
-                r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
+        r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
         if (r < 0)
                 return r;
 
@@ -817,3 +856,168 @@ int chase_symlinks_at_and_open(
         return r;
 }
 
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
+        _cleanup_close_ int path_fd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        DIR *d;
+        int r;
+
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+        assert(ret_dir);
+
+        if (dir_fd == AT_FDCWD && !ret_path &&
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+                /* Shortcut this call if none of the special features of this call are requested */
+                d = opendir(path);
+                if (!d)
+                        return -errno;
+
+                *ret_dir = d;
+                return 0;
+        }
+
+        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+        if (r < 0)
+                return r;
+        assert(path_fd >= 0);
+
+        d = xopendirat(path_fd, ".", O_NOFOLLOW);
+        if (!d)
+                return -errno;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        *ret_dir = d;
+        return 0;
+}
+
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
+        _cleanup_close_ int path_fd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        assert(path);
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+        assert(ret_stat);
+
+        if (dir_fd == AT_FDCWD && !ret_path &&
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
+                /* Shortcut this call if none of the special features of this call are requested */
+                return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
+                                          FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
+
+        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+        if (r < 0)
+                return r;
+        assert(path_fd >= 0);
+
+        if (fstat(path_fd, ret_stat) < 0)
+                return -errno;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        return 0;
+}
+
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
+        _cleanup_close_ int path_fd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        assert(path);
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+        if (dir_fd == AT_FDCWD && !ret_path &&
+            (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
+                /* Shortcut this call if none of the special features of this call are requested */
+                return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
+                                            FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
+
+        r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+        if (r < 0)
+                return r;
+        assert(path_fd >= 0);
+
+        r = access_fd(path_fd, access_mode);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        return 0;
+}
+
+int chase_and_fopenat_unlocked(
+                int dir_fd,
+                const char *path,
+                ChaseFlags chase_flags,
+                const char *open_flags,
+                char **ret_path,
+                FILE **ret_file) {
+
+        _cleanup_free_ char *final_path = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        int mode_flags, r;
+
+        assert(path);
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
+        assert(open_flags);
+        assert(ret_file);
+
+        mode_flags = fopen_mode_to_flags(open_flags);
+        if (mode_flags < 0)
+                return mode_flags;
+
+        fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
+        if (fd < 0)
+                return fd;
+
+        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(final_path);
+
+        return 0;
+}
+
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
+        _cleanup_free_ char *p = NULL, *fname = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(path);
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
+
+        fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
+        if (fd < 0)
+                return fd;
+
+        r = path_extract_filename(p, &fname);
+        if (r < 0)
+                return r;
+
+        if (unlinkat(fd, fname, unlink_flags) < 0)
+                return -errno;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        return 0;
+}
+
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
+        int pfd, r;
+
+        assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+        r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
+        if (r < 0)
+                return r;
+
+        return pfd;
+}
diff --git a/src/basic/chase.h b/src/basic/chase.h
new file mode 100644 (file)
index 0000000..f37e836
--- /dev/null
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <dirent.h>
+#include <stdio.h>
+
+#include "stat-util.h"
+
+typedef enum ChaseFlags {
+        CHASE_PREFIX_ROOT        = 1 << 0,  /* The specified path will be prefixed by the specified root before beginning the iteration */
+        CHASE_NONEXISTENT        = 1 << 1,  /* It's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS          = 1 << 2,  /* Return -EREMOTE if autofs mount point found */
+        CHASE_SAFE               = 1 << 3,  /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
+        CHASE_TRAIL_SLASH        = 1 << 4,  /* Any trailing slash will be preserved */
+        CHASE_STEP               = 1 << 5,  /* Just execute a single step of the normalization */
+        CHASE_NOFOLLOW           = 1 << 6,  /* Do not follow the path's right-most component. With ret_fd, when the path's
+                                             * right-most component refers to symlink, return O_PATH fd of the symlink. */
+        CHASE_WARN               = 1 << 7,  /* Emit an appropriate warning when an error is encountered.
+                                             * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
+        CHASE_AT_RESOLVE_IN_ROOT = 1 << 8,  /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
+                                             * relative to the given directory fd instead of root. */
+        CHASE_PROHIBIT_SYMLINKS  = 1 << 9,  /* Refuse all symlinks */
+        CHASE_PARENT             = 1 << 10, /* Chase the parent directory of the given path. Note that the
+                                             * full path is still stored in ret_path and only the returned
+                                             * file descriptor will point to the parent directory. Note that
+                                             * the result path is the root or '.', then the file descriptor
+                                             * also points to the result path even if this flag is set.
+                                             * When this specified, chase() will succeed with 1 even if the
+                                             * file points to the last path component does not exist. */
+        CHASE_MKDIR_0755         = 1 << 11, /* Create any missing parent directories in the given path. This
+                                             * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT.
+                                             * Note, chase_and_open() or friends always add CHASE_PARENT flag
+                                             * when internally call chase(), hence CHASE_MKDIR_0755 can be
+                                             * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */
+        CHASE_EXTRACT_FILENAME   = 1 << 12, /* Only return the last component of the resolved path */
+} ChaseFlags;
+
+bool unsafe_transition(const struct stat *a, const struct stat *b);
+
+/* How many iterations to execute before returning -ELOOP */
+#define CHASE_MAX 32
+
+int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd);
+
+int chaseat_prefix_root(const char *path, const char *root, char **ret);
+
+int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename);
+
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd);
+
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename);
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 d64277b806b163c4bb79c7105126c1b1cfd9d74c..c31fe79ebdaaae922cbdb39e2fba4ad5d3318930 100644 (file)
@@ -5,7 +5,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "constants.h"
 #include "dirent-util.h"
 #include "terminal-util.h"
 
 static int files_add(
-                Hashmap **h,
+                DIR *dir,
+                const char *dirpath,
+                Hashmap **files,
                 Set **masked,
                 const char *suffix,
-                const char *root,
-                unsigned flags,
-                const char *path) {
+                unsigned flags) {
 
-        _cleanup_free_ char *dirpath = NULL;
-        _cleanup_closedir_ DIR *dir = NULL;
         int r;
 
-        assert(h);
+        assert(dir);
+        assert(dirpath);
+        assert(files);
         assert(masked);
-        assert(path);
-
-        r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &dirpath, &dir);
-        if (r == -ENOENT)
-                return 0;
-        if (r < 0)
-                return log_debug_errno(r, "Failed to open directory '%s/%s': %m", empty_or_root(root) ? "" : root, dirpath);
 
         FOREACH_DIRENT(de, dir, return -errno) {
                 _cleanup_free_ char *n = NULL, *p = NULL;
@@ -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 640bddc4851401c191091c9b7e556a5a47f6b576..3e674a8dba88cfe0619ec5c9dc44f93148407870 100644 (file)
@@ -1,6 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+extern void __gcov_dump(void);
+extern void __gcov_reset(void);
+
 /* When built with --coverage (gcov) we need to explicitly call __gcov_dump()
  * in places where we use _exit(), since _exit() skips at-exit hooks resulting
  * in lost coverage.
  * To make sure we don't miss any _exit() calls, this header file is included
  * explicitly on the compiler command line via the -include directive (only
  * when built with -Db_coverage=true)
- * */
+ */
 extern void _exit(int);
-extern void __gcov_dump(void);
 
 static inline _Noreturn void _coverage__exit(int status) {
         __gcov_dump();
         _exit(status);
 }
 #define _exit(x) _coverage__exit(x)
+
+/* gcov provides wrappers for the exec*() calls but there's none for execveat(),
+ * which means we lose all coverage prior to the call. To mitigate this, let's
+ * add a simple execveat() wrapper in gcov's style[0], which dumps and resets
+ * the coverage data when needed.
+ *
+ * This applies only when we're built with -Dfexecve=true.
+ *
+ * [0] https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/libgcov-interface.c;h=b2ee930864183b78c8826255183ca86e15e21ded;hb=HEAD
+ */
+
+extern int execveat(int, const char *, char * const [], char * const [], int);
+
+static inline int _coverage_execveat(
+                        int dirfd,
+                        const char *pathname,
+                        char * const argv[],
+                        char * const envp[],
+                        int flags) {
+        __gcov_dump();
+        int r = execveat(dirfd, pathname, argv, envp, flags);
+        __gcov_reset();
+
+        return r;
+}
+#define execveat(d,p,a,e,f) _coverage_execveat(d, p, a, e, f)
index bd1b4d6c3901f50d6967622ee6361bac639cf620..f82e13bdb02875449bcf2ee7d5233bd8dbb94c2a 100644 (file)
@@ -3,7 +3,7 @@
 #include <string.h>
 #include <sys/stat.h>
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "devnum-util.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -83,7 +83,7 @@ int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
 
         assert(ret);
 
-        if (major(devnum) == 0 && minor(devnum) == 0)
+        if (devnum_is_zero(devnum))
                 /* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
                  * /dev/block/ and /dev/char/, hence we handle them specially here. */
                 return device_path_make_inaccessible(mode, ret);
@@ -92,7 +92,7 @@ int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
         if (r < 0)
                 return r;
 
-        return chase_symlinks(p, NULL, 0, ret, NULL);
+        return chase(p, NULL, 0, ret, NULL);
 }
 
 int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) {
index 38aa4efa87b602a4771e1b3c6ee99b01fa7e2193..e109de991360b28676b55a060d5718873c1328cc 100644 (file)
@@ -50,3 +50,7 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) {
 }
 
 #define FORMAT_DEVNUM(d) format_devnum((d), (char[DEVNUM_STR_MAX]) {})
+
+static inline bool devnum_is_zero(dev_t d) {
+        return major(d) == 0 && minor(d) == 0;
+}
index 16de727c09cd855e3a49adec4509e8e469dbbec9..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,25 +398,13 @@ int parse_env_file_fd_sentinel(
                 const char *fname, /* only used for logging */
                 ...) {
 
-        _cleanup_close_ int fd_ro = -EBADF;
-        _cleanup_fclose_ FILE *f = NULL;
         va_list ap;
         int r;
 
         assert(fd >= 0);
 
-        fd_ro = fd_reopen(fd, O_CLOEXEC | O_RDONLY);
-        if (fd_ro < 0)
-                return fd_ro;
-
-        f = fdopen(fd_ro, "re");
-        if (!f)
-                return -errno;
-
-        TAKE_FD(fd_ro);
-
         va_start(ap, fname);
-        r = parse_env_filev(f, fname, ap);
+        r = parse_env_file_fdv(fd, fname, ap);
         va_end(ap);
 
         return r;
@@ -485,6 +490,7 @@ int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
         int r;
 
         assert(f || fname);
+        assert(ret);
 
         r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
         if (r < 0)
@@ -494,6 +500,19 @@ int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
         return 0;
 }
 
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(fd >= 0);
+
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
+
+        return load_env_file_pairs(f, fname, ret);
+}
+
 static int merge_env_file_push(
                 const char *filename, unsigned line,
                 const char *key, char *value,
index dc38b7a5c9b8a7d1592a02671d2049845c838ec5..2465eeddf4d6e3d7de6ac629783a9967d1f88b48 100644 (file)
@@ -8,12 +8,14 @@
 #include "macro.h"
 
 int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
 int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
 #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
 int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
 #define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL)
 int load_env_file(FILE *f, const char *fname, char ***ret);
 int load_env_file_pairs(FILE *f, const char *fname, char ***ret);
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret);
 
 int merge_env_file(char ***env, FILE *f, const char *fname);
 
index 55ac11a5124605eec6e1a536b33394dc4a7345b1..41fad1d1b9e118ab6dddea9e721e7c073c0ca601 100644 (file)
@@ -459,6 +459,48 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
         return strv_env_replace_consume(l, p);
 }
 
+int _strv_env_assign_many(char ***l, ...) {
+        va_list ap;
+        int r;
+
+        assert(l);
+
+        va_start(ap, l);
+        for (;;) {
+                const char *key, *value;
+
+                key = va_arg(ap, const char *);
+                if (!key)
+                        break;
+
+                if (!env_name_is_valid(key)) {
+                        va_end(ap);
+                        return -EINVAL;
+                }
+
+                value = va_arg(ap, const char *);
+                if (!value) {
+                        strv_env_unset(*l, key);
+                        continue;
+                }
+
+                char *p = strjoin(key, "=", value);
+                if (!p) {
+                        va_end(ap);
+                        return -ENOMEM;
+                }
+
+                r = strv_env_replace_consume(l, p);
+                if (r < 0) {
+                        va_end(ap);
+                        return r;
+                }
+        }
+        va_end(ap);
+
+        return 0;
+}
+
 char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
         assert(name);
 
index b927ac7a4883f345868c47bc0aecabb2e6a0e046..b0ff5a11d1aa7e1e5a82abcc26e925ccc875e5ec 100644 (file)
@@ -49,6 +49,8 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */
 int strv_env_replace_strdup(char ***l, const char *assignment);
 int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
 int strv_env_assign(char ***l, const char *key, const char *value);
+int _strv_env_assign_many(char ***l, ...) _sentinel_;
+#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
 
 char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
 char *strv_env_get(char **x, const char *n) _pure_;
index d7296432650800f0b3baa645338416929bef78d6..974a7aac655725d5ebfd95d0016570729a0178ea 100644 (file)
@@ -534,6 +534,11 @@ bool fdname_is_valid(const char *s) {
 int fd_get_path(int fd, char **ret) {
         int r;
 
+        assert(fd >= 0 || fd == AT_FDCWD);
+
+        if (fd == AT_FDCWD)
+                return safe_getcwd(ret);
+
         r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret);
         if (r == -ENOENT) {
                 /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
@@ -744,23 +749,42 @@ finish:
 int fd_reopen(int fd, int flags) {
         int new_fd, r;
 
+        assert(fd >= 0 || fd == AT_FDCWD);
+
         /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
          * turn O_RDWR fds into O_RDONLY fds.
          *
          * This doesn't work on sockets (since they cannot be open()ed, ever).
          *
-         * This implicitly resets the file read index to 0. */
-
-        if (FLAGS_SET(flags, O_DIRECTORY)) {
+         * This implicitly resets the file read index to 0.
+         *
+         * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd.
+         *
+         * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used
+         * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without
+         * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you
+         * can reopen it only if you pass O_PATH again. */
+
+        if (FLAGS_SET(flags, O_NOFOLLOW))
+                /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is primarily implemented
+                 * via a symlink-based interface in /proc/self/fd. Let's refuse this here early. Note that
+                 * the kernel would generate ELOOP here too, hence this manual check is mostly redundant –
+                 * the only reason we add it here is so that the O_DIRECTORY special case (see below) behaves
+                 * the same way as the non-O_DIRECTORY case. */
+                return -ELOOP;
+
+        if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) {
                 /* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole
                  * magic /proc/ directory, and make ourselves independent of that being mounted. */
-                new_fd = openat(fd, ".", flags);
+                new_fd = openat(fd, ".", flags | O_DIRECTORY);
                 if (new_fd < 0)
                         return -errno;
 
                 return new_fd;
         }
 
+        assert(fd >= 0);
+
         new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
         if (new_fd < 0) {
                 if (errno != ENOENT)
@@ -879,10 +903,31 @@ 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;
+
+        /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+         * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+         * following could be used to trick us:
+         *
+         * $ mkdir /tmp/x /tmp/x/y
+         * $ mount --bind /tmp/x /tmp/x/y
+         *
+         * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old
+         * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious
+         * mount points in an early boot stage, and silently skip the following check. */
+
         if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
                 int mntid;
 
                 r = path_get_mnt_id_at(dir_fd, "", &mntid);
+                if (r == -ENOSYS)
+                        return true; /* skip the mount ID check */
                 if (r < 0)
                         return r;
                 assert(mntid >= 0);
@@ -891,14 +936,12 @@ 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;
 
                 r = path_get_mnt_id_at(dir_fd, "..", &mntid);
+                if (r == -ENOSYS)
+                        return true; /* skip the mount ID check */
                 if (r < 0)
                         return r;
                 assert(mntid >= 0);
@@ -907,12 +950,18 @@ 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:
-         *
-         * $ mkdir /tmp/x /tmp/x/y
-         * $ mount --bind /tmp/x /tmp/x/y
-         */
-        return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
+        return statx_mount_same(&st.nsx, &pst.nsx);
+}
+
+const char *accmode_to_string(int flags) {
+        switch (flags & O_ACCMODE) {
+        case O_RDONLY:
+                return "ro";
+        case O_WRONLY:
+                return "wo";
+        case O_RDWR:
+                return "rw";
+        default:
+                return NULL;
+        }
 }
index a6353cf48e4b824eda6880a55f923bcc0e56f50d..2f59e334c5c12051405b3bfa94ef3ac2fbe80aa7 100644 (file)
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <sys/socket.h>
@@ -101,6 +102,9 @@ int read_nr_open(void);
 int fd_get_diskseq(int fd, uint64_t *ret);
 
 int dir_fd_is_root(int dir_fd);
+static inline int dir_fd_is_root_or_cwd(int dir_fd) {
+        return dir_fd == AT_FDCWD ? true : dir_fd_is_root(dir_fd);
+}
 
 /* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
 #define PROC_FD_PATH_MAX \
@@ -115,3 +119,13 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f
 
 #define FORMAT_PROC_FD_PATH(fd) \
         format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
+
+const char *accmode_to_string(int flags);
+
+/* Like ASSERT_PTR, but for fds */
+#define ASSERT_FD(fd)                           \
+        ({                                      \
+                int _fd_ = (fd);                \
+                assert(_fd_ >= 0);              \
+                _fd_;                           \
+        })
index c802db749b369744b45cd09526c73140d690ce5b..48ffb4e5e6e5b47d3ceea1d019ce47069f9bcd6f 100644 (file)
@@ -13,7 +13,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
  * can detect EOFs. */
 #define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
 
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret) {
-        int r;
-
-        assert(ret);
-
-        r = xfopenat(dir_fd, path, options, flags, ret);
-        if (r < 0)
-                return r;
-
-        (void) __fsetlocking(*ret, FSETLOCKING_BYCALLER);
-
-        return 0;
-}
-
 int fdopen_unlocked(int fd, const char *options, FILE **ret) {
         assert(ret);
 
@@ -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;
-
-                /* Seeking is not supported on AF_UNIX sockets */
-                if (offset != UINT64_MAX)
-                        return -ENXIO;
-
-                sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
-                if (sk < 0)
-                        return -errno;
-
-                if (bind_name) {
-                        /* If the caller specified a socket name to bind to, do so before connecting. This is
-                         * useful to communicate some minor, short meta-information token from the client to
-                         * the server. */
-                        union sockaddr_union bsa;
-
-                        r = sockaddr_un_set_path(&bsa.un, bind_name);
-                        if (r < 0)
-                                return r;
-
-                        if (bind(sk, &bsa.sa, r) < 0)
-                                return -errno;
-                }
-
-                r = connect_unix_path(sk, dir_fd, filename);
-                if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
-                        return -ENXIO;
-                if (r < 0)
-                        return r;
-
-                if (shutdown(sk, SHUT_WR) < 0)
-                        return -errno;
-
-                f = fdopen(sk, "r");
-                if (!f)
-                        return -errno;
-
-                TAKE_FD(sk);
-        }
+        if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+            offset == UINT64_MAX)                              /* Seeking is not supported on AF_UNIX sockets */
+                xflags |= XFOPEN_SOCKET;
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+        if (r < 0)
+                return r;
 
         return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
 }
@@ -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,37 +925,143 @@ int fopen_mode_to_flags(const char *mode) {
         return flags;
 }
 
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
         FILE *f;
 
         /* A combination of fopen() with openat() */
 
-        if (dir_fd == AT_FDCWD && flags == 0) {
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        if (dir_fd == AT_FDCWD && open_flags == 0)
                 f = fopen(path, mode);
-                if (!f)
-                        return -errno;
-        } else {
-                int fd, mode_flags;
+        else {
+                _cleanup_close_ int fd = -EBADF;
+                int mode_flags;
 
                 mode_flags = fopen_mode_to_flags(mode);
                 if (mode_flags < 0)
                         return mode_flags;
 
-                fd = openat(dir_fd, path, mode_flags | flags);
+                fd = openat(dir_fd, path, mode_flags | open_flags);
                 if (fd < 0)
                         return -errno;
 
-                f = fdopen(fd, mode);
-                if (!f) {
-                        safe_close(fd);
+                f = take_fdopen(&fd, mode);
+        }
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+        _cleanup_close_ int sk = -EBADF;
+        FILE *f;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(ret);
+
+        sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        if (sk < 0)
+                return -errno;
+
+        if (bind_name) {
+                /* If the caller specified a socket name to bind to, do so before connecting. This is
+                 * useful to communicate some minor, short meta-information token from the client to
+                 * the server. */
+                union sockaddr_union bsa;
+
+                r = sockaddr_un_set_path(&bsa.un, bind_name);
+                if (r < 0)
+                        return r;
+
+                if (bind(sk, &bsa.sa, r) < 0)
                         return -errno;
-                }
         }
 
+        r = connect_unix_path(sk, dir_fd, path);
+        if (r < 0)
+                return r;
+
+        if (shutdown(sk, SHUT_WR) < 0)
+                return -errno;
+
+        f = take_fdopen(&sk, "r");
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret) {
+
+        FILE *f = NULL;  /* avoid false maybe-uninitialized warning */
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+        if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+                r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+                if (IN_SET(r, -ENOTSOCK, -EINVAL))
+                        return -ENXIO; /* propagate original error if this is not a socket after all */
+        }
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         *ret = f;
         return 0;
 }
 
+int fdopen_independent(int fd, const char *mode, FILE **ret) {
+        _cleanup_close_ int copy_fd = -EBADF;
+        _cleanup_fclose_ FILE *f = NULL;
+        int mode_flags;
+
+        assert(fd >= 0);
+        assert(mode);
+        assert(ret);
+
+        /* A combination of fdopen() + fd_reopen(). i.e. reopens the inode the specified fd points to and
+         * returns a FILE* for it */
+
+        mode_flags = fopen_mode_to_flags(mode);
+        if (mode_flags < 0)
+                return mode_flags;
+
+        copy_fd = fd_reopen(fd, mode_flags);
+        if (copy_fd < 0)
+                return copy_fd;
+
+        f = take_fdopen(&copy_fd, mode);
+        if (!f)
+                return -errno;
+
+        *ret = TAKE_PTR(f);
+        return 0;
+}
+
 static int search_and_fopen_internal(
                 const char *path,
                 const char *mode,
index 7da3ee33e09e9568456b4f3d9013cd85bafa49f5..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,31 @@ 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);
 
 int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path);
 int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path);
index b942abd477e1f00fb8979e3f27f31a950f48b6c4..1e1413dc80eca339ace82b939030f4e2b8569a4d 100644 (file)
@@ -3,6 +3,7 @@
 #include <errno.h>
 #include <stddef.h>
 #include <stdlib.h>
+#include <sys/file.h>
 #include <linux/falloc.h>
 #include <linux/magic.h>
 #include <unistd.h>
@@ -13,6 +14,7 @@
 #include "fileio.h"
 #include "fs-util.h"
 #include "hostname-util.h"
+#include "lock-util.h"
 #include "log.h"
 #include "macro.h"
 #include "missing_fcntl.h"
@@ -781,12 +783,23 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
         return 0;
 }
 
-int open_parent(const char *path, int flags, mode_t mode) {
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
         _cleanup_free_ char *parent = NULL;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
         r = path_extract_directory(path, &parent);
-        if (r < 0)
+        if (r == -EDESTADDRREQ) {
+                parent = strdup(".");
+                if (!parent)
+                        return -ENOMEM;
+        } else if (r == -EADDRNOTAVAIL) {
+                parent = strdup(path);
+                if (!parent)
+                        return -ENOMEM;
+        } else if (r < 0)
                 return r;
 
         /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
@@ -797,7 +810,7 @@ int open_parent(const char *path, int flags, mode_t mode) {
         else if (!FLAGS_SET(flags, O_TMPFILE))
                 flags |= O_DIRECTORY|O_RDONLY;
 
-        return RET_NERRNO(open(parent, flags, mode));
+        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
 }
 
 int conservative_renameat(
@@ -811,7 +824,7 @@ int conservative_renameat(
          * have the exact same contents and basic file attributes already. In that case remove the new file
          * instead. This call is useful for reducing inotify wakeups on files that are updated but don't
          * actually change. This function is written in a style that we rather rename too often than suppress
-         * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+         * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
          * events is an optimization only, not more. */
 
         old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
@@ -987,7 +1000,7 @@ int parse_cifs_service(
 
 int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
         _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
-        _cleanup_free_ char *fname = NULL;
+        _cleanup_free_ char *fname = NULL, *parent = NULL;
         int r;
 
         /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
@@ -1002,19 +1015,13 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
         /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
          * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
 
-        if (isempty(path))
-                return -EINVAL;
-
-        if (!filename_is_valid(path)) {
-                _cleanup_free_ char *parent = NULL;
-
-                /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
-                 * that we can pin it, and operate below it. */
-
-                r = path_extract_directory(path, &parent);
-                if (r < 0)
+        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
+         * that we can pin it, and operate below it. */
+        r = path_extract_directory(path, &parent);
+        if (r < 0) {
+                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
                         return r;
-
+        } else {
                 r = path_extract_filename(path, &fname);
                 if (r < 0)
                         return r;
@@ -1088,6 +1095,14 @@ int xopenat(int dir_fd, const char *path, int flags, mode_t mode) {
         bool made = false;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        if (isempty(path)) {
+                assert(!FLAGS_SET(flags, O_CREAT|O_EXCL));
+                return fd_reopen(dir_fd, flags & ~O_NOFOLLOW);
+        }
+
         if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) {
                 r = RET_NERRNO(mkdirat(dir_fd, path, mode));
                 if (r == -EEXIST) {
@@ -1124,3 +1139,42 @@ int xopenat(int dir_fd, const char *path, int flags, mode_t mode) {
 
         return TAKE_FD(fd);
 }
+
+int xopenat_lock(int dir_fd, const char *path, int flags, mode_t mode, LockType locktype, int operation) {
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
+
+        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
+         * the same error here). */
+        if (FLAGS_SET(flags, O_DIRECTORY) && locktype != LOCK_BSD)
+                return -EBADF;
+
+        for (;;) {
+                struct stat st;
+
+                fd = xopenat(dir_fd, path, flags, mode);
+                if (fd < 0)
+                        return fd;
+
+                r = lock_generic(fd, locktype, operation);
+                if (r < 0)
+                        return r;
+
+                /* If we acquired the lock, let's check if the file/directory still exists in the file
+                 * system. If not, then the previous exclusive owner removed it and then closed it. In such a
+                 * case our acquired lock is worthless, hence try again. */
+
+                if (fstat(fd, &st) < 0)
+                        return -errno;
+                if (st.st_nlink > 0)
+                        break;
+
+                fd = safe_close(fd);
+        }
+
+        return TAKE_FD(fd);
+}
index b66dda01b4a7d97b5d205d183684bd24b991335b..cf381dfc266d1ca7fbf1c30e9e37a60d2b5a15c4 100644 (file)
@@ -12,6 +12,7 @@
 
 #include "alloc-util.h"
 #include "errno-util.h"
+#include "lock-util.h"
 #include "time-util.h"
 #include "user-util.h"
 
@@ -113,7 +114,10 @@ typedef enum UnlinkDeallocateFlags {
 
 int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
 
-int open_parent(const char *path, int flags, mode_t mode);
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode);
+static inline int open_parent(const char *path, int flags, mode_t mode) {
+        return open_parent_at(AT_FDCWD, path, flags, mode);
+}
 
 int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
 static inline int conservative_rename(const char *oldpath, const char *newpath) {
@@ -129,3 +133,5 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode);
 int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created);
 
 int xopenat(int dir_fd, const char *path, int flags, mode_t mode);
+
+int xopenat_lock(int dir_fd, const char *path, int flags, mode_t mode, LockType locktype, int operation);
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 ffc8bd830453ecd3c451966c88749f45717a4502..e4e5dff3ea2678568efdbf692e4ca1a32f457a22 100644 (file)
@@ -46,7 +46,7 @@
 
 /* Remove an item from the list */
 #define LIST_REMOVE(name,head,item)                                     \
-        ({                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_item = (item);     \
                 assert(_item);                                          \
                 if (_item->name##_next)                                 \
                 _b;                                                     \
         })
 
-#define LIST_JUST_US(name,item)                                         \
-        (!(item)->name##_prev && !(item)->name##_next)
+#define LIST_JUST_US(name, item)                                        \
+        ({                                                              \
+                typeof(*(item)) *_item = (item);                        \
+                !(_item)->name##_prev && !(_item)->name##_next;         \
+        })
 
 /* The type of the iterator 'i' is automatically determined by the type of 'head', and declared in the
  * loop. Hence, do not declare the same variable in the outer scope. Sometimes, we set 'head' through
index 13e4c12237a42d0adc43456a777dc092be13b727..8ca30a50f0b70a11c9847e14f3a0c34d3a8753ff 100644 (file)
 #include "missing_fcntl.h"
 #include "path-util.h"
 
-int make_lock_file(const char *p, int operation, LockFile *ret) {
-        _cleanup_close_ int fd = -EBADF;
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret) {
+        _cleanup_close_ int fd = -EBADF, dfd = -EBADF;
         _cleanup_free_ char *t = NULL;
-        int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(p);
         assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
         assert(ret);
 
+        if (isempty(p))
+                return -EINVAL;
+
         /* We use UNPOSIX locks as they have nice semantics, and are mostly compatible with NFS. */
 
+        dfd = fd_reopen(dir_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
+        if (dfd < 0)
+                return dfd;
+
         t = strdup(p);
         if (!t)
                 return -ENOMEM;
 
-        for (;;) {
-                struct stat st;
-
-                fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
-                if (fd < 0)
-                        return -errno;
-
-                r = unposix_lock(fd, operation);
-                if (r < 0)
-                        return r == -EAGAIN ? -EBUSY : r;
-
-                /* If we acquired the lock, let's check if the file still exists in the file system. If not,
-                 * then the previous exclusive owner removed it and then closed it. In such a case our
-                 * acquired lock is worthless, hence try again. */
-
-                if (fstat(fd, &st) < 0)
-                        return -errno;
-                if (st.st_nlink > 0)
-                        break;
-
-                fd = safe_close(fd);
-        }
+        fd = xopenat_lock(dfd, p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600, LOCK_UNPOSIX, operation);
+        if (fd < 0)
+                return fd == -EAGAIN ? -EBUSY : fd;
 
         *ret = (LockFile) {
+                .dir_fd = TAKE_FD(dfd),
                 .path = TAKE_PTR(t),
                 .fd = TAKE_FD(fd),
                 .operation = operation,
@@ -100,11 +89,12 @@ void release_lock_file(LockFile *f) {
                         f->operation = LOCK_EX|LOCK_NB;
 
                 if ((f->operation & ~LOCK_NB) == LOCK_EX)
-                        (void) unlink(f->path);
+                        (void) unlinkat(f->dir_fd, f->path, 0);
 
                 f->path = mfree(f->path);
         }
 
+        f->dir_fd = safe_close(f->dir_fd);
         f->fd = safe_close(f->fd);
         f->operation = 0;
 }
@@ -173,3 +163,18 @@ void unposix_unlockpp(int **fd) {
         (void) fcntl_lock(**fd, LOCK_UN, /*ofd=*/ true);
         *fd = NULL;
 }
+
+int lock_generic(int fd, LockType type, int operation) {
+        assert(fd >= 0);
+
+        switch (type) {
+        case LOCK_BSD:
+                return RET_NERRNO(flock(fd, operation));
+        case LOCK_POSIX:
+                return posix_lock(fd, operation);
+        case LOCK_UNPOSIX:
+                return unposix_lock(fd, operation);
+        default:
+                assert_not_reached();
+        }
+}
index 6eebd0914350570d1a701d52db4db6bce555a638..e7744476bbeefadfdf95648ab84874fdedcc09c1 100644 (file)
@@ -1,17 +1,23 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
+
 typedef struct LockFile {
+        int dir_fd;
         char *path;
         int fd;
         int operation;
 } LockFile;
 
-int make_lock_file(const char *p, int operation, LockFile *ret);
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret);
+static inline int make_lock_file(const char *p, int operation, LockFile *ret) {
+        return make_lock_file_at(AT_FDCWD, p, operation, ret);
+}
 int make_lock_file_for(const char *p, int operation, LockFile *ret);
 void release_lock_file(LockFile *f);
 
-#define LOCK_FILE_INIT { .fd = -EBADF, .path = NULL }
+#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF }
 
 /* POSIX locks with the same interface as flock(). */
 int posix_lock(int fd, int operation);
@@ -26,3 +32,11 @@ void unposix_unlockpp(int **fd);
 
 #define CLEANUP_UNPOSIX_UNLOCK(fd)                                   \
         _cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd)
+
+typedef enum LockType {
+        LOCK_BSD,
+        LOCK_POSIX,
+        LOCK_UNPOSIX,
+} LockType;
+
+int lock_generic(int fd, LockType type, int operation);
index 8b973f9e0a905502529aa63c01c7f249f1f89196..4cd2d5a4ab6667fb081e8a95f68a3463cd4716e3 100644 (file)
@@ -42,7 +42,7 @@
 #include "utf8.h"
 
 #define SNDBUF_SIZE (8*1024*1024)
-#define IOVEC_MAX 128U
+#define IOVEC_MAX 256U
 
 static log_syntax_callback_t log_syntax_callback = NULL;
 static void *log_syntax_callback_userdata = NULL;
@@ -50,6 +50,7 @@ static void *log_syntax_callback_userdata = NULL;
 static LogTarget log_target = LOG_TARGET_CONSOLE;
 static int log_max_level = LOG_INFO;
 static int log_facility = LOG_DAEMON;
+static bool ratelimit_kmsg = true;
 
 static int console_fd = STDERR_FILENO;
 static int syslog_fd = -EBADF;
@@ -73,11 +74,14 @@ static bool prohibit_ipc = false;
 static char *log_abort_msg = NULL;
 
 typedef struct LogContext {
+        unsigned n_ref;
         /* Depending on which destructor is used (log_context_free() or log_context_detach()) the memory
          * referenced by this is freed or not */
         char **fields;
         struct iovec *input_iovec;
         size_t n_input_iovec;
+        char *key;
+        char *value;
         bool owned;
         LIST_FIELDS(struct LogContext, ll);
 } LogContext;
@@ -85,6 +89,8 @@ typedef struct LogContext {
 static thread_local LIST_HEAD(LogContext, _log_context) = NULL;
 static thread_local size_t _log_context_num_fields = 0;
 
+static thread_local const char *log_prefix = NULL;
+
 #if LOG_MESSAGE_VERIFICATION || defined(__COVERITY__)
 bool _log_message_dummy = false; /* Always false */
 #endif
@@ -392,7 +398,7 @@ static int write_to_console(
              header_time[FORMAT_TIMESTAMP_MAX],
              prefix[1 + DECIMAL_STR_MAX(int) + 2],
              tid_string[3 + DECIMAL_STR_MAX(pid_t) + 1];
-        struct iovec iovec[9];
+        struct iovec iovec[11];
         const char *on = NULL, *off = NULL;
         size_t n = 0;
 
@@ -431,6 +437,10 @@ static int write_to_console(
 
         if (on)
                 iovec[n++] = IOVEC_MAKE_STRING(on);
+        if (log_prefix) {
+                iovec[n++] = IOVEC_MAKE_STRING(log_prefix);
+                iovec[n++] = IOVEC_MAKE_STRING(": ");
+        }
         iovec[n++] = IOVEC_MAKE_STRING(buffer);
         if (off)
                 iovec[n++] = IOVEC_MAKE_STRING(off);
@@ -490,6 +500,8 @@ static int write_to_syslog(
                 IOVEC_MAKE_STRING(header_time),
                 IOVEC_MAKE_STRING(program_invocation_short_name),
                 IOVEC_MAKE_STRING(header_pid),
+                IOVEC_MAKE_STRING(strempty(log_prefix)),
+                IOVEC_MAKE_STRING(log_prefix ? ": " : ""),
                 IOVEC_MAKE_STRING(buffer),
         };
         const struct msghdr msghdr = {
@@ -541,8 +553,12 @@ static int write_to_kmsg(
         if (kmsg_fd < 0)
                 return 0;
 
-        if (!ratelimit_below(&ratelimit))
-                return 0;
+        if (ratelimit_kmsg && !ratelimit_below(&ratelimit)) {
+                if (ratelimit_num_dropped(&ratelimit) > 1)
+                        return 0;
+
+                buffer = "Too many messages being logged to kmsg, ignoring";
+        }
 
         xsprintf(header_priority, "<%i>", level);
         xsprintf(header_pid, "["PID_FMT"]: ", getpid_cached());
@@ -551,6 +567,8 @@ static int write_to_kmsg(
                 IOVEC_MAKE_STRING(header_priority),
                 IOVEC_MAKE_STRING(program_invocation_short_name),
                 IOVEC_MAKE_STRING(header_pid),
+                IOVEC_MAKE_STRING(strempty(log_prefix)),
+                IOVEC_MAKE_STRING(log_prefix ? ": " : ""),
                 IOVEC_MAKE_STRING(buffer),
                 IOVEC_MAKE_STRING("\n"),
         };
@@ -631,6 +649,15 @@ static void log_do_context(struct iovec *iovec, size_t iovec_len, size_t *n) {
                         iovec[(*n)++] = c->input_iovec[i];
                         iovec[(*n)++] = IOVEC_MAKE_STRING("\n");
                 }
+
+                if (c->key && c->value) {
+                        if (*n + 3 >= iovec_len)
+                                return;
+
+                        iovec[(*n)++] = IOVEC_MAKE_STRING(c->key);
+                        iovec[(*n)++] = IOVEC_MAKE_STRING(c->value);
+                        iovec[(*n)++] = IOVEC_MAKE_STRING("\n");
+                }
         }
 }
 
@@ -653,13 +680,17 @@ static int write_to_journal(
         if (journal_fd < 0)
                 return 0;
 
-        iovec_len = MIN(4 + _log_context_num_fields * 2, IOVEC_MAX);
+        iovec_len = MIN(6 + _log_context_num_fields * 2, IOVEC_MAX);
         iovec = newa(struct iovec, iovec_len);
 
         log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object, extra_field, extra);
 
         iovec[n++] = IOVEC_MAKE_STRING(header);
         iovec[n++] = IOVEC_MAKE_STRING("MESSAGE=");
+        if (log_prefix) {
+                iovec[n++] = IOVEC_MAKE_STRING(log_prefix);
+                iovec[n++] = IOVEC_MAKE_STRING(": ");
+        }
         iovec[n++] = IOVEC_MAKE_STRING(buffer);
         iovec[n++] = IOVEC_MAKE_STRING("\n");
 
@@ -835,16 +866,9 @@ int log_object_internalv(
         /* Make sure that %m maps to the specified error (or "Success"). */
         LOCAL_ERRNO(ERRNO_VALUE(error));
 
-        /* Prepend the object name before the message */
-        if (object) {
-                size_t n;
-
-                n = strlen(object);
-                buffer = newa(char, n + 2 + LINE_MAX);
-                b = stpcpy(stpcpy(buffer, object), ": ");
-        } else
-                b = buffer = newa(char, LINE_MAX);
+        LOG_SET_PREFIX(object);
 
+        b = buffer = newa(char, LINE_MAX);
         (void) vsnprintf(b, LINE_MAX, format, ap);
 
         return log_dispatch_internal(level, error, file, line, func,
@@ -1159,6 +1183,17 @@ int log_set_max_level_from_string(const char *e) {
         return 0;
 }
 
+static int log_set_ratelimit_kmsg_from_string(const char *e) {
+        int r;
+
+        r = parse_boolean(e);
+        if (r < 0)
+                return r;
+
+        ratelimit_kmsg = r;
+        return 0;
+}
+
 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
 
         /*
@@ -1209,6 +1244,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (log_show_time_from_string(value ?: "1") < 0)
                         log_warning("Failed to parse log time setting '%s'. Ignoring.", value);
 
+        } else if (proc_cmdline_key_streq(key, "systemd.log_ratelimit_kmsg")) {
+
+                if (log_set_ratelimit_kmsg_from_string(value ?: "1") < 0)
+                        log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", value);
         }
 
         return 0;
@@ -1249,6 +1288,10 @@ void log_parse_environment_variables(void) {
         e = getenv("SYSTEMD_LOG_TID");
         if (e && log_show_tid_from_string(e) < 0)
                 log_warning("Failed to parse log tid '%s'. Ignoring.", e);
+
+        e = getenv("SYSTEMD_LOG_RATELIMIT_KMSG");
+        if (e && log_set_ratelimit_kmsg_from_string(e) < 0)
+                log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", e);
 }
 
 void log_parse_environment(void) {
@@ -1264,6 +1307,24 @@ LogTarget log_get_target(void) {
         return log_target;
 }
 
+void log_settle_target(void) {
+
+        /* If we're using LOG_TARGET_AUTO and opening the log again on every single log call, we'll check if
+         * stderr is attached to the journal every single log call. However, if we then close all file
+         * descriptors later, that will stop working because stderr will be closed as well. To avoid that
+         * problem, this function is used to permanently change the log target depending on whether stderr is
+         * connected to the journal or not. */
+
+        LogTarget t = log_get_target();
+
+        if (t != LOG_TARGET_AUTO)
+                return;
+
+        t = getpid_cached() == 1 || stderr_is_journal() ? (prohibit_ipc ? LOG_TARGET_KMSG : LOG_TARGET_JOURNAL_OR_KMSG)
+                                                        : LOG_TARGET_CONSOLE;
+        log_set_target(t);
+}
+
 int log_get_max_level(void) {
         return log_max_level;
 }
@@ -1542,6 +1603,15 @@ void log_setup(void) {
                 log_show_color(true);
 }
 
+const char *_log_set_prefix(const char *prefix, bool force) {
+        const char *old = log_prefix;
+
+        if (prefix || force)
+                log_prefix = prefix;
+
+        return old;
+}
+
 static int saved_log_context_enabled = -1;
 
 bool log_context_enabled(void) {
@@ -1562,33 +1632,67 @@ bool log_context_enabled(void) {
         return saved_log_context_enabled;
 }
 
-LogContext* log_context_attach(LogContext *c) {
+static LogContext* log_context_attach(LogContext *c) {
         assert(c);
 
         _log_context_num_fields += strv_length(c->fields);
         _log_context_num_fields += c->n_input_iovec;
+        _log_context_num_fields += !!c->key;
 
         return LIST_PREPEND(ll, _log_context, c);
 }
 
-LogContext* log_context_detach(LogContext *c) {
+static LogContext* log_context_detach(LogContext *c) {
         if (!c)
                 return NULL;
 
-        assert(_log_context_num_fields >= strv_length(c->fields) + c->n_input_iovec);
+        assert(_log_context_num_fields >= strv_length(c->fields) + c->n_input_iovec +!!c->key);
         _log_context_num_fields -= strv_length(c->fields);
         _log_context_num_fields -= c->n_input_iovec;
+        _log_context_num_fields -= !!c->key;
 
         LIST_REMOVE(ll, _log_context, c);
         return NULL;
 }
 
-LogContext* log_context_new(char **fields, bool owned) {
+LogContext* log_context_new(const char *key, const char *value) {
+        assert(key);
+        assert(endswith(key, "="));
+        assert(value);
+
+        LIST_FOREACH(ll, i, _log_context)
+                if (i->key == key && i->value == value)
+                        return log_context_ref(i);
+
         LogContext *c = new(LogContext, 1);
         if (!c)
                 return NULL;
 
         *c = (LogContext) {
+                .n_ref = 1,
+                .key = (char *) key,
+                .value = (char *) value,
+        };
+
+        return log_context_attach(c);
+}
+
+LogContext* log_context_new_strv(char **fields, bool owned) {
+        if (!fields)
+                return NULL;
+
+        LIST_FOREACH(ll, i, _log_context)
+                if (i->fields == fields) {
+                        assert(!owned);
+                        return log_context_ref(i);
+                }
+
+        LogContext *c = new(LogContext, 1);
+        if (!c)
+                return NULL;
+
+        *c = (LogContext) {
+                .n_ref = 1,
                 .fields = fields,
                 .owned = owned,
         };
@@ -1596,15 +1700,22 @@ LogContext* log_context_new(char **fields, bool owned) {
         return log_context_attach(c);
 }
 
-LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bool owned) {
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned) {
         if (!input_iovec || n_input_iovec == 0)
-                return NULL; /* Nothing to do */
+                return NULL;
+
+        LIST_FOREACH(ll, i, _log_context)
+                if (i->input_iovec == input_iovec && i->n_input_iovec == n_input_iovec) {
+                        assert(!owned);
+                        return log_context_ref(i);
+                }
 
         LogContext *c = new(LogContext, 1);
         if (!c)
                 return NULL;
 
         *c = (LogContext) {
+                .n_ref = 1,
                 .input_iovec = input_iovec,
                 .n_input_iovec = n_input_iovec,
                 .owned = owned,
@@ -1613,7 +1724,7 @@ LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bo
         return log_context_attach(c);
 }
 
-LogContext* log_context_free(LogContext *c) {
+static LogContext* log_context_free(LogContext *c) {
         if (!c)
                 return NULL;
 
@@ -1622,21 +1733,25 @@ LogContext* log_context_free(LogContext *c) {
         if (c->owned) {
                 strv_free(c->fields);
                 iovec_array_free(c->input_iovec, c->n_input_iovec);
+                free(c->key);
+                free(c->value);
         }
 
         return mfree(c);
 }
 
-LogContext* log_context_new_consume(char **fields) {
-        LogContext *c = log_context_new(fields, /*owned=*/ true);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(LogContext, log_context, log_context_free);
+
+LogContext* log_context_new_strv_consume(char **fields) {
+        LogContext *c = log_context_new_strv(fields, /*owned=*/ true);
         if (!c)
                 strv_free(fields);
 
         return c;
 }
 
-LogContext* log_context_new_consumev(struct iovec *input_iovec, size_t n_input_iovec) {
-        LogContext *c = log_context_newv(input_iovec, n_input_iovec, /*owned=*/ true);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec) {
+        LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ true);
         if (!c)
                 iovec_array_free(input_iovec, n_input_iovec);
 
index c4ac73e27bcd5e2d2a2609cce5e132809d48e13f..9008d473909e13f642b8e1275ceba1e7f992a739 100644 (file)
@@ -54,6 +54,7 @@ void log_set_target(LogTarget target);
 void log_set_target_and_open(LogTarget target);
 int log_set_target_from_string(const char *e);
 LogTarget log_get_target(void) _pure_;
+void log_settle_target(void);
 
 void log_set_max_level(int level);
 int log_set_max_level_from_string(const char *e);
@@ -425,6 +426,16 @@ typedef struct LogRateLimit {
 #define log_ratelimit_error_errno(error, ...)     log_ratelimit_full_errno(LOG_ERR,     error, __VA_ARGS__)
 #define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__)
 
+const char *_log_set_prefix(const char *prefix, bool force);
+static inline const char *_log_unset_prefixp(const char **p) {
+        assert(p);
+        _log_set_prefix(*p, true);
+        return NULL;
+}
+
+#define LOG_SET_PREFIX(prefix) \
+        _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false);
+
 /*
  * The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep
  * track of a thread local log context onto which we can push extra metadata fields that should be logged.
@@ -457,39 +468,44 @@ typedef struct LogContext LogContext;
 
 bool log_context_enabled(void);
 
-LogContext* log_context_attach(LogContext *c);
-LogContext* log_context_detach(LogContext *c);
-
-LogContext* log_context_new(char **fields, bool owned);
-LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
-LogContext* log_context_free(LogContext *c);
+LogContext* log_context_new(const char *key, const char *value);
+LogContext* log_context_new_strv(char **fields, bool owned);
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
 
 /* Same as log_context_new(), but frees the given fields strv/iovec on failure. */
-LogContext* log_context_new_consume(char **fields);
-LogContext* log_context_new_consumev(struct iovec *input_iovec, size_t n_input_iovec);
+LogContext* log_context_new_strv_consume(char **fields);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec);
+
+LogContext *log_context_ref(LogContext *c);
+LogContext *log_context_unref(LogContext *c);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref);
 
 /* Returns the number of attached log context objects. */
 size_t log_context_num_contexts(void);
 /* Returns the number of fields in all attached log contexts. */
 size_t log_context_num_fields(void);
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_detach);
-DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_free);
-
 #define LOG_CONTEXT_PUSH(...) \
         LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__))
 
 #define LOG_CONTEXT_PUSHF(...) \
         LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__))
 
+#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value);
+
+#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \
+        _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ))
+
 #define _LOG_CONTEXT_PUSH_STRV(strv, c) \
-        _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new(strv, /*owned=*/ false);
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false);
 
 #define LOG_CONTEXT_PUSH_STRV(strv) \
         _LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ))
 
 #define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \
-        _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_newv(input_iovec, n_input_iovec, /*owned=*/ false);
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false);
 
 #define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \
         _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
@@ -503,19 +519,19 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_free);
         _unused_ _cleanup_strv_free_ strv = strv_new(s);                                                \
         if (!strv)                                                                                      \
                 free(s);                                                                                \
-        _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(TAKE_PTR(strv))
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv))
 
 #define LOG_CONTEXT_CONSUME_STR(s) \
         _LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ))
 
 #define _LOG_CONTEXT_CONSUME_STRV(strv, c) \
-        _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(strv);
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv);
 
 #define LOG_CONTEXT_CONSUME_STRV(strv) \
         _LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ))
 
 #define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \
-        _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consumev(input_iovec, n_input_iovec);
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec);
 
 #define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \
         _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
index 2770d01aa910026afa678a71d15b877a1c6fc47f..99262e920654dcc60d6f4e81fa9c0148874e1065 100644 (file)
@@ -431,7 +431,7 @@ assert_cc(sizeof(dummy_t) == 0);
         })
 
 /* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
- * convertable. The iteration variable 'entry' must already be defined. */
+ * convertible. The iteration variable 'entry' must already be defined. */
 #define VA_ARGS_FOREACH(entry, ...)                                     \
         _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
 #define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...)         \
index 5f616c18934f4e4383d7dd4cbe061930a5ebc74d..ee9433714019bf65226ce766b4463ce985902be1 100644 (file)
@@ -14,7 +14,7 @@ basic_sources = files(
         'cap-list.c',
         'capability-util.c',
         'cgroup-util.c',
-        'chase-symlinks.c',
+        'chase.c',
         'chattr-util.c',
         'conf-files.c',
         'devnum-util.c',
index 00937d2af03d91ff58743f76baf8e7487114c501..79e95a8f6fda9e0839ea21130f2d0dc60d86110b 100644 (file)
 #ifndef O_TMPFILE
 #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
 #endif
+
+/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
+ * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
+ * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */
+#if O_LARGEFILE != 0
+#define RAW_O_LARGEFILE O_LARGEFILE
+#else
+#define RAW_O_LARGEFILE 0100000
+#endif
index 2257a1452f82f56fe5be02d1b60b684a14df9d29..41af1482bc7bfbf0d139bcc4c913df485d6389ef 100644 (file)
@@ -5,7 +5,7 @@
 #include <string.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "format-util.h"
 #include "fs-util.h"
@@ -33,11 +33,8 @@ int mkdirat_safe_internal(
         assert(_mkdirat && _mkdirat != mkdirat);
 
         r = _mkdirat(dir_fd, path, mode);
-        if (r >= 0) {
-                r = chmod_and_chown_at(dir_fd, path, mode, uid, gid);
-                if (r < 0)
-                        return r;
-        }
+        if (r >= 0)
+                return chmod_and_chown_at(dir_fd, path, mode, uid, gid);
         if (r != -EEXIST)
                 return r;
 
@@ -47,7 +44,7 @@ int mkdirat_safe_internal(
         if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
                 _cleanup_free_ char *p = NULL;
 
-                r = chase_symlinks_at(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
+                r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -234,7 +231,7 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m
                 if (r < 0)
                         return r;
 
-                dfd = chase_symlinks_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
+                dfd = chase_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
                 if (dfd < 0)
                         return dfd;
         }
index 24e38a34e82f6141464008c24dd918289560beaa..3584f317876f533a00c84c61d8eab849b88171dd 100644 (file)
@@ -8,7 +8,7 @@
 #endif
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "filesystems.h"
@@ -63,7 +63,7 @@ int name_to_handle_at_loop(
 
                 h->handle_bytes = n;
 
-                if (name_to_handle_at(fd, path, h, &mnt_id, flags) >= 0) {
+                if (name_to_handle_at(fd, strempty(path), h, &mnt_id, flags) >= 0) {
 
                         if (ret_handle)
                                 *ret_handle = TAKE_PTR(h);
@@ -94,7 +94,9 @@ int name_to_handle_at_loop(
 
                 /* The buffer was too small. Size the new buffer by what name_to_handle_at() returned. */
                 n = h->handle_bytes;
-                if (offsetof(struct file_handle, f_handle) + n < n) /* check for addition overflow */
+
+                /* paranoia: check for overflow (note that .handle_bytes is unsigned only) */
+                if (n > UINT_MAX - offsetof(struct file_handle, f_handle))
                         return -EOVERFLOW;
         }
 }
@@ -121,18 +123,13 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mn
 
         r = read_full_virtual_file(path, &fdinfo, NULL);
         if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
-                return -EOPNOTSUPP;
+                return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
         if (r < 0)
                 return r;
 
-        p = startswith(fdinfo, "mnt_id:");
-        if (!p) {
-                p = strstr(fdinfo, "\nmnt_id:");
-                if (!p) /* The mnt_id field is a relatively new addition */
-                        return -EOPNOTSUPP;
-
-                p += 8;
-        }
+        p = find_line_startswith(fdinfo, "mnt_id:");
+        if (!p) /* The mnt_id field is a relatively new addition */
+                return -EOPNOTSUPP;
 
         p += strspn(p, WHITESPACE);
         p[strcspn(p, WHITESPACE)] = 0;
@@ -218,9 +215,14 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
          * reported. Also, btrfs subvolumes have different st_dev, even though they aren't real mounts of
          * their own. */
 
-        if (statx(fd, filename, (FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW) |
-                                (flags & AT_EMPTY_PATH) |
-                                AT_NO_AUTOMOUNT, STATX_TYPE, &sx) < 0) {
+        if (statx(fd,
+                  filename,
+                  (FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW) |
+                  (flags & AT_EMPTY_PATH) |
+                  AT_NO_AUTOMOUNT |            /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */
+                  AT_STATX_DONT_SYNC,          /* don't go to the network for this – for similar reasons */
+                  STATX_TYPE,
+                  &sx) < 0) {
                 if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
                         return -errno;
 
@@ -269,16 +271,16 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
         /* If the file handle for the directory we are interested in and its parent are identical,
          * we assume this is the root directory, which is a mount point. */
 
-        if (h->handle_bytes == h_parent->handle_bytes &&
-            h->handle_type == h_parent->handle_type &&
-            memcmp(h->f_handle, h_parent->f_handle, h->handle_bytes) == 0)
+        if (h->handle_type == h_parent->handle_type &&
+            memcmp_nn(h->f_handle, h->handle_bytes,
+                      h_parent->f_handle, h_parent->handle_bytes) == 0)
                 return 1;
 
         return mount_id != mount_id_parent;
 
 fallback_fdinfo:
         r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
-        if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM))
+        if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM, -ENOSYS))
                 goto fallback_fstat;
         if (r < 0)
                 return r;
@@ -341,7 +343,7 @@ int path_is_mount_point(const char *t, const char *root, int flags) {
          * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
          * look at needs to be /usr, not /. */
         if (flags & AT_SYMLINK_FOLLOW) {
-                r = chase_symlinks(t, root, CHASE_TRAIL_SLASH, &canonical, NULL);
+                r = chase(t, root, CHASE_TRAIL_SLASH, &canonical, NULL);
                 if (r < 0)
                         return r;
 
@@ -360,12 +362,13 @@ int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) {
         int r;
 
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
-        assert(path);
         assert(ret);
 
         if (statx(dir_fd,
-                  path,
-                  AT_NO_AUTOMOUNT|(isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW),
+                  strempty(path),
+                  (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) |
+                  AT_NO_AUTOMOUNT |    /* don't trigger automounts, mnt_id is a local concept */
+                  AT_STATX_DONT_SYNC,  /* don't go to the network, mnt_id is a local concept */
                   STATX_MNT_ID,
                   &buf.sx) < 0) {
                 if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
@@ -545,6 +548,8 @@ int dev_is_devtmpfs(void) {
                 return r;
 
         r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
+        if (r == -ENOENT)
+                return proc_mounted() > 0 ? -ENOENT : -ENOSYS;
         if (r < 0)
                 return r;
 
@@ -564,12 +569,12 @@ int dev_is_devtmpfs(void) {
                 if (mid != mount_id)
                         continue;
 
-                e = strstr(line, " - ");
+                e = strstrafter(line, " - ");
                 if (!e)
                         continue;
 
                 /* accept any name that starts with the currently expected type */
-                if (startswith(e + 3, "devtmpfs"))
+                if (startswith(e, "devtmpfs"))
                         return true;
         }
 
@@ -718,7 +723,7 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu
         _cleanup_close_ int fd = -EBADF;
         int r;
 
-        /* Checks if the specified file system supports a mount option. Returns > 0 if it suppors it, == 0 if
+        /* Checks if the specified file system supports a mount option. Returns > 0 if it supports it, == 0 if
          * it does not. Return -EAGAIN if we can't determine it. And any other error otherwise. */
 
         assert(fstype);
index 98d68e0b011486819960de2b5ac1f9506127d2c6..2acc61886bd98fa9d3782940563f56dfcb970e20 100644 (file)
@@ -4,7 +4,7 @@
 #include "string-util.h"
 #include "strv.h"
 
-char** strv_parse_nulstr(const char *s, size_t l) {
+char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) {
         /* l is the length of the input data, which will be split at NULs into elements of the resulting
          * strv. Hence, the number of items in the resulting strv will be equal to one plus the number of NUL
          * bytes in the l bytes starting at s, unless s[l-1] is NUL, in which case the final empty string is
@@ -18,6 +18,10 @@ char** strv_parse_nulstr(const char *s, size_t l) {
 
         assert(s || l <= 0);
 
+        if (drop_trailing_nuls)
+                while (l > 0 && s[l-1] == '\0')
+                        l--;
+
         if (l <= 0)
                 return new0(char*, 1);
 
index fd0ed445282b1d2d00a9a664d4f9de3f6fcdcbc6..d7bc5fd1ced9523ad8ce1bfc3d42136a19520237 100644 (file)
@@ -20,7 +20,10 @@ static inline bool nulstr_contains(const char *nulstr, const char *needle) {
         return nulstr_get(nulstr, needle);
 }
 
-char** strv_parse_nulstr(const char *s, size_t l);
+char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls);
+static inline char** strv_parse_nulstr(const char *s, size_t l) {
+        return strv_parse_nulstr_full(s, l, false);
+}
 char** strv_split_nulstr(const char *s);
 int strv_make_nulstr(char * const *l, char **p, size_t *n);
 int set_make_nulstr(Set *s, char **ret, size_t *ret_size);
diff --git a/src/basic/origin-id.h b/src/basic/origin-id.h
new file mode 100644 (file)
index 0000000..c55b0a3
--- /dev/null
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <pthread.h>
+
+#include "random-util.h"
+
+/* This pattern needs to be repeated exactly in multiple modules, so macro it.
+ * To ensure an object is not passed into a different module (e.g.: when two shared objects statically
+ * linked to libsystemd get loaded in the same process, and the object created by one is passed to the
+ * other, see https://github.com/systemd/systemd/issues/27216), create a random static global random
+ * (mixed with PID, so that we can also check for reuse after fork) that is stored in the object and
+ * checked by public API on use. */
+#define _DEFINE_ORIGIN_ID_HELPERS(type, name, scope)                  \
+static uint64_t origin_id;                                            \
+                                                                      \
+static void origin_id_initialize(void) {                              \
+        origin_id = random_u64();                                     \
+}                                                                     \
+                                                                      \
+static uint64_t origin_id_query(void) {                               \
+        static pthread_once_t once = PTHREAD_ONCE_INIT;               \
+        assert_se(pthread_once(&once, origin_id_initialize) == 0);    \
+        return origin_id ^ getpid_cached();                           \
+}                                                                     \
+                                                                      \
+scope bool name##_origin_changed(type *p) {                           \
+        assert(p);                                                    \
+        return p->origin_id != origin_id_query();                     \
+}
+
+#define DEFINE_ORIGIN_ID_HELPERS(type, name)                          \
+        _DEFINE_ORIGIN_ID_HELPERS(type, name,);
+
+#define DEFINE_PRIVATE_ORIGIN_ID_HELPERS(type, name)                  \
+        _DEFINE_ORIGIN_ID_HELPERS(type, name, static);
index bf844e5b7fe05776d61b2706ea67e5f4b7ea1640..5d06e20871cb3858135ca3e43c9e744f3369acd7 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dirent-util.h"
 #include "env-file.h"
 #include "env-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "stat-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "utf8.h"
 #include "xattr-util.h"
 
+static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
+        [IMAGE_MACHINE]  = "machine",
+        [IMAGE_PORTABLE] = "portable",
+        [IMAGE_SYSEXT]   = "extension",
+        [IMAGE_CONFEXT]  = "confext",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
+
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+        const char *release_file_directory;
+        const char *release_file_path_prefix;
+} image_class_release_info[_IMAGE_CLASS_MAX] = {
+        [IMAGE_SYSEXT] = {
+                .release_file_directory = "/usr/lib/extension-release.d/",
+                .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
+        },
+        [IMAGE_CONFEXT] = {
+                .release_file_directory = "/etc/extension-release.d/",
+                .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
+        }
+};
+
 bool image_name_is_valid(const char *s) {
         if (!filename_is_valid(s))
                 return false;
@@ -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,205 +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_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT,
-                                   ret_path ? &q : NULL,
-                                   ret_fd ? &fd : NULL);
-                log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
-
-                /* Cannot find the expected extension-release file? The image filename might have been
-                 * mangled on deployment, so fallback to checking for any file in the extension-release.d
-                 * directory, and return the first one with a user.extension-release xattr instead.
-                 * The user.extension-release.strict xattr is checked to ensure the author of the image
-                 * considers it OK if names do not match. */
-                if (r == -ENOENT) {
-                        _cleanup_free_ char *extension_release_dir_path = NULL;
-                        _cleanup_closedir_ DIR *extension_release_dir = NULL;
-
-                        r = chase_symlinks_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
-                                                       &extension_release_dir_path, &extension_release_dir);
-                        if (r < 0)
-                                return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
-
-                        r = -ENOENT;
-                        FOREACH_DIRENT(de, extension_release_dir, return -errno) {
-                                int k;
-
-                                if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
-                                        continue;
-
-                                const char *image_name = startswith(de->d_name, "extension-release.");
-                                if (!image_name)
-                                        continue;
-
-                                if (!image_name_is_valid(image_name)) {
-                                        log_debug("%s/%s is not a valid extension-release file name, ignoring.",
-                                                  extension_release_dir_path, de->d_name);
-                                        continue;
-                                }
-
-                                /* We already chased the directory, and checked that
-                                 * this is a real file, so we shouldn't fail to open it. */
-                                _cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir),
-                                                                                  de->d_name,
-                                                                                  O_PATH|O_CLOEXEC|O_NOFOLLOW);
-                                if (extension_release_fd < 0)
-                                        return log_debug_errno(errno,
-                                                               "Failed to open extension-release file %s/%s: %m",
-                                                               extension_release_dir_path,
-                                                               de->d_name);
-
-                                /* Really ensure it is a regular file after we open it. */
-                                if (fd_verify_regular(extension_release_fd) < 0) {
-                                        log_debug("%s/%s is not a regular file, ignoring.", extension_release_dir_path, de->d_name);
-                                        continue;
-                                }
-
-                                if (!relax_extension_release_check) {
-                                        k = extension_release_strict_xattr_value(extension_release_fd,
-                                                                                 extension_release_dir_path,
-                                                                                 de->d_name);
-                                        if (k != 0)
-                                                continue;
-                                }
-
-                                /* We already found what we were looking for, but there's another candidate?
-                                 * We treat this as an error, as we want to enforce that there are no ambiguities
-                                 * in case we are in the fallback path. */
-                                if (r == 0) {
-                                        r = -ENOTUNIQ;
-                                        break;
-                                }
-
-                                r = 0; /* Found it! */
-
-                                if (ret_fd)
-                                        fd = TAKE_FD(extension_release_fd);
-
-                                if (ret_path) {
-                                        q = path_join(extension_release_dir_path, de->d_name);
-                                        if (!q)
-                                                return -ENOMEM;
-                                }
-                        }
-                }
-        } else {
-                const char *var = secure_getenv("SYSTEMD_OS_RELEASE");
-                if (var)
-                        r = chase_symlinks(var, root, 0,
-                                           ret_path ? &q : NULL,
-                                           ret_fd ? &fd : NULL);
-                else
-                        FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
-                                r = chase_symlinks(path, root, CHASE_PREFIX_ROOT,
-                                                   ret_path ? &q : NULL,
-                                                   ret_fd ? &fd : NULL);
-                                if (r != -ENOENT)
-                                        break;
-                        }
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
+        const char *e;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        e = secure_getenv("SYSTEMD_OS_RELEASE");
+        if (e)
+                return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+
+        FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
+                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+                if (r != -ENOENT)
+                        return r;
         }
+
+        return -ENOENT;
+}
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd) {
+        _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
+
+        r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
         if (r < 0)
                 return r;
 
-        if (ret_fd) {
-                int real_fd;
+        if (ret_path) {
+                r = chaseat_prefix_root(p, root, ret_path);
+                if (r < 0)
+                        return r;
+        }
+
+        if (ret_fd)
+                *ret_fd = TAKE_FD(fd);
+
+        return 0;
+}
+
+int open_extension_release_at(
+                int rfd,
+                ImageClass image_class,
+                const char *extension,
+                bool relax_extension_release_check,
+                char **ret_path,
+                int *ret_fd) {
+
+        _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
+        _cleanup_close_ int fd_found = -EBADF;
+        _cleanup_closedir_ DIR *dir = NULL;
+        bool found = false;
+        const char *p;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
+
+        if (!extension)
+                return open_os_release_at(rfd, ret_path, ret_fd);
+
+        if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
+                return -EINVAL;
+
+        if (!image_name_is_valid(extension))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
+
+        p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
+        r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+        log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
+        if (r != -ENOENT)
+                return r;
+
+        /* Cannot find the expected extension-release file? The image filename might have been mangled on
+         * deployment, so fallback to checking for any file in the extension-release.d directory, and return
+         * the first one with a user.extension-release xattr instead. The user.extension-release.strict
+         * xattr is checked to ensure the author of the image considers it OK if names do not match. */
+
+        p = image_class_release_info[image_class].release_file_directory;
+        r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
+        if (r < 0)
+                return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
+
+        FOREACH_DIRENT(de, dir, return -errno) {
+                _cleanup_close_ int fd = -EBADF;
+                const char *image_name;
 
-                /* Convert the O_PATH fd into a proper, readable one */
-                real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                safe_close(fd);
-                if (real_fd < 0)
-                        return real_fd;
+                if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
+                        continue;
+
+                image_name = startswith(de->d_name, "extension-release.");
+                if (!image_name)
+                        continue;
 
-                *ret_fd = real_fd;
+                if (!image_name_is_valid(image_name)) {
+                        log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
+                        continue;
+                }
+
+                /* We already chased the directory, and checked that this is a real file, so we shouldn't
+                 * fail to open it. */
+                fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+                if (fd < 0)
+                        return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
+
+                /* Really ensure it is a regular file after we open it. */
+                r = fd_verify_regular(fd);
+                if (r < 0) {
+                        log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
+                        continue;
+                }
+
+                if (!relax_extension_release_check &&
+                    extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0)
+                        continue;
+
+                /* We already found what we were looking for, but there's another candidate? We treat this as
+                 * an error, as we want to enforce that there are no ambiguities in case we are in the
+                 * fallback path. */
+                if (found)
+                        return -ENOTUNIQ;
+
+                found = true;
+
+                if (ret_fd)
+                        fd_found = TAKE_FD(fd);
+
+                if (ret_path) {
+                        path_found = path_join(dir_path, de->d_name);
+                        if (!path_found)
+                                return -ENOMEM;
+                }
         }
+        if (!found)
+                return -ENOENT;
 
+        if (ret_fd)
+                *ret_fd = TAKE_FD(fd_found);
         if (ret_path)
-                *ret_path = TAKE_PTR(q);
+                *ret_path = TAKE_PTR(path_found);
 
         return 0;
 }
 
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+int open_extension_release(
+                const char *root,
+                ImageClass image_class,
+                const char *extension,
+                bool relax_extension_release_check,
+                char **ret_path,
+                int *ret_fd) {
+
+        _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
         _cleanup_free_ char *p = NULL;
-        _cleanup_close_ int fd = -EBADF;
-        FILE *f;
         int r;
 
-        if (!ret_file)
-                return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
 
-        r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
+                                      ret_path ? &p : NULL, ret_fd ? &fd : NULL);
         if (r < 0)
                 return r;
 
-        f = take_fdopen(&fd, "r");
-        if (!f)
-                return -errno;
+        if (ret_path) {
+                r = chaseat_prefix_root(p, root, ret_path);
+                if (r < 0)
+                        return r;
+        }
 
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
-        *ret_file = f;
+        if (ret_fd)
+                *ret_fd = TAKE_FD(fd);
 
         return 0;
 }
 
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
-        _cleanup_fclose_ FILE *f = NULL;
+static int parse_extension_release_atv(
+                int rfd,
+                ImageClass image_class,
+                const char *extension,
+                bool relax_extension_release_check,
+                va_list ap) {
+
+        _cleanup_close_ int fd = -EBADF;
         _cleanup_free_ char *p = NULL;
         int r;
 
-        r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
         if (r < 0)
                 return r;
 
-        return parse_env_filev(f, p, ap);
+        return parse_env_file_fdv(fd, p, ap);
 }
 
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int parse_extension_release_at_sentinel(
+                int rfd,
+                ImageClass image_class,
+                bool relax_extension_release_check,
+                const char *extension,
+                ...) {
+
         va_list ap;
         int r;
 
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
         va_start(ap, extension);
-        r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
         va_end(ap);
-
         return r;
 }
 
-int _parse_os_release(const char *root, ...) {
+int parse_extension_release_sentinel(
+                const char *root,
+                ImageClass image_class,
+                bool relax_extension_release_check,
+                const char *extension,
+                ...) {
+
+        _cleanup_close_ int rfd = -EBADF;
         va_list ap;
         int r;
 
-        va_start(ap, root);
-        r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
-        va_end(ap);
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
 
+        va_start(ap, extension);
+        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
+        va_end(ap);
         return r;
 }
 
-int load_os_release_pairs(const char *root, char ***ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *p = NULL;
-        int r;
-
-        r = fopen_os_release(root, &p, &f);
-        if (r < 0)
-                return r;
-
-        return load_env_file_pairs(f, p, ret);
-}
-
 int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
         _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
         int r;
@@ -324,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 3445d3130761c708c595cc8ec242b8af7e62080d..a53cbc73b89e12f3774456d4b93bd6429e99dddd 100644 (file)
@@ -50,7 +50,6 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         int r;
 
         assert(s);
-        assert(ret_pid);
 
         r = safe_atolu(s, &ul);
         if (r < 0)
@@ -64,7 +63,8 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         if (!pid_is_valid(pid))
                 return -ERANGE;
 
-        *ret_pid = pid;
+        if (ret_pid)
+                *ret_pid = pid;
         return 0;
 }
 
index 40a819d47d344fc7f1eab9a7645bca029effba6b..0b0f0da7602675b5e33797c09dc1f403413442a7 100644 (file)
@@ -8,7 +8,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "extract-word.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -285,7 +285,7 @@ char **path_strv_resolve(char **l, const char *root) {
                 } else
                         t = *s;
 
-                r = chase_symlinks(t, root, 0, &u, NULL);
+                r = chase(t, root, 0, &u, NULL);
                 if (r == -ENOENT) {
                         if (root) {
                                 u = TAKE_PTR(orig);
@@ -491,25 +491,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, ...) {
@@ -621,16 +629,13 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f
 
         assert(name);
 
-        /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of
+        /* Function chase() is invoked only when root is not NULL, as using it regardless of
          * root value would alter the behavior of existing callers for example: /bin/sleep would become
          * /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when
          * needed to avoid unforeseen regression or other complicated changes. */
         if (root) {
-                r = chase_symlinks(name,
-                                   root,
-                                   CHASE_PREFIX_ROOT,
-                                   &path_name,
-                                   /* ret_fd= */ NULL); /* prefix root to name in case full paths are not specified */
+                 /* prefix root to name in case full paths are not specified */
+                r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL);
                 if (r < 0)
                         return r;
 
@@ -896,6 +901,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;
@@ -920,6 +927,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..7843599816e69972bc283daf7e074df654adbe0d 100644 (file)
@@ -66,15 +66,18 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot
 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 eea70d86069fed6dce46c2010145d74aa04e7981..39e9f2c668c938a87f3d33efe77865841756cd24 100644 (file)
@@ -7,17 +7,79 @@
 #include "efivars.h"
 #include "extract-word.h"
 #include "fileio.h"
+#include "getopt-defs.h"
 #include "initrd-util.h"
 #include "macro.h"
 #include "parse-util.h"
 #include "proc-cmdline.h"
 #include "process-util.h"
-#include "special.h"
 #include "string-util.h"
+#include "strv.h"
 #include "virt.h"
 
+int proc_cmdline_filter_pid1_args(
+                char **argv,   /* input, may be reordered by this function. */
+                char ***ret) {
+
+        enum {
+                COMMON_GETOPT_ARGS,
+                SYSTEMD_GETOPT_ARGS,
+                SHUTDOWN_GETOPT_ARGS,
+        };
+
+        static const struct option options[] = {
+                COMMON_GETOPT_OPTIONS,
+                SYSTEMD_GETOPT_OPTIONS,
+                SHUTDOWN_GETOPT_OPTIONS,
+                {}
+        };
+
+        int saved_optind, saved_opterr, saved_optopt, argc;
+        char *saved_optarg;
+        char **filtered;
+        size_t idx;
+
+        assert(argv);
+        assert(ret);
+
+        /* Backup global variables. */
+        saved_optind = optind;
+        saved_opterr = opterr;
+        saved_optopt = optopt;
+        saved_optarg = optarg;
+
+        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
+         * the GNU extensions, but might be used previously. Hence, we need to always reset it. */
+        optind = 0;
+
+        /* Do not print an error message. */
+        opterr = 0;
+
+        /* Filter out all known options. */
+        argc = strv_length(argv);
+        while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
+                ;
+
+        idx = optind;
+
+        /* Restore global variables. */
+        optind = saved_optind;
+        opterr = saved_opterr;
+        optopt = saved_optopt;
+        optarg = saved_optarg;
+
+        filtered = strv_copy(strv_skip(argv, idx));
+        if (!filtered)
+                return -ENOMEM;
+
+        *ret = filtered;
+        return 0;
+}
+
 int proc_cmdline(char **ret) {
         const char *e;
+
         assert(ret);
 
         /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
@@ -39,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);
+
+        /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
+        e = secure_getenv("SYSTEMD_PROC_CMDLINE");
+        if (e)
+                return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
 
-                r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+        if (detect_container() > 0) {
+                _cleanup_strv_free_ char **args = NULL;
+
+                r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
                 if (r < 0)
                         return r;
-                if (r == 0)
-                        break;
 
-                /* Filter out arguments that are intended only for the initrd */
-                c = startswith(word, "rd.");
-                if (c) {
-                        if (!in_initrd())
-                                continue;
+                if (filter_pid1_args)
+                        return proc_cmdline_filter_pid1_args(args, ret);
 
-                        if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
-                                r = free_and_strdup(&word, c);
-                                if (r < 0)
-                                        return r;
-                        }
+                *ret = TAKE_PTR(args);
+                return 0;
+
+        } else {
+                _cleanup_free_ char *s = NULL;
 
-                } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
-                        continue; /* And optionally filter out arguments that are intended only for the host */
+                r = read_one_line_file("/proc/cmdline", &s);
+                if (r < 0)
+                        return r;
 
-                *p = q;
-                *ret_word = TAKE_PTR(word);
-                return 1;
+                return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
         }
+}
 
-        *p = q;
-        *ret_word = NULL;
-        return 0;
+int proc_cmdline_strv(char ***ret) {
+        return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
 }
 
-int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
-        const char *p;
+static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
+        char *c;
+
+        c = startswith(word, "rd.");
+        if (c) {
+                /* Filter out arguments that are intended only for the initrd */
+
+                if (!in_initrd())
+                        return NULL;
+
+                if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
+                        return c;
+
+        } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
+                /* And optionally filter out arguments that are intended only for the host */
+                return NULL;
+
+        return (char*) word;
+}
+
+static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
         int r;
 
         assert(parse_item);
 
-        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
-         * clear. */
+        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
+         * make this clear. */
         assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
 
-        p = line;
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                char *value;
+        STRV_FOREACH(word, args) {
+                char *key, *value;
 
-                r = proc_cmdline_extract_first(&p, &word, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
+                key = mangle_word(*word, flags);
+                if (!key)
+                        continue;
 
-                value = strchr(word, '=');
+                value = strchr(key, '=');
                 if (value)
-                        *(value++) = 0;
+                        *(value++) = '\0';
 
-                r = parse_item(word, value, data);
+                r = parse_item(key, value, data);
                 if (r < 0)
                         return r;
         }
@@ -112,7 +189,7 @@ int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item,
 }
 
 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);
@@ -120,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) {
@@ -172,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) {
-        _cleanup_free_ char *ret = NULL;
+static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
+        _cleanup_free_ char *v = NULL;
         bool found = false;
-        const char *p;
         int r;
 
-        assert(line);
         assert(key);
 
-        p = line;
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
+        STRV_FOREACH(p, args) {
+                const char *word;
 
-                r = proc_cmdline_extract_first(&p, &word, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
+                word = mangle_word(*p, flags);
+                if (!word)
+                        continue;
 
                 if (ret_value) {
                         const char *e;
@@ -199,7 +277,7 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
                                 continue;
 
                         if (*e == '=') {
-                                r = free_and_strdup(&ret, e+1);
+                                r = free_and_strdup(&v, e+1);
                                 if (r < 0)
                                         return r;
 
@@ -209,7 +287,7 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
                                 found = true;
 
                 } else {
-                        if (streq(word, key)) {
+                        if (proc_cmdline_key_streq(word, key)) {
                                 found = true;
                                 break; /* we found what we were looking for */
                         }
@@ -217,12 +295,13 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
         }
 
         if (ret_value)
-                *ret_value = TAKE_PTR(ret);
+                *ret_value = TAKE_PTR(v);
 
         return found;
 }
 
 int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
+        _cleanup_strv_free_ char **args = NULL;
         _cleanup_free_ char *line = NULL, *v = NULL;
         int r;
 
@@ -246,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) {
@@ -263,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)
@@ -274,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) {
@@ -302,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;
                 }
+        }
 
-                va_start(ap, flags);
+        return ret;
+}
 
-                for (;;) {
-                        char **v;
-                        const char *k, *e;
+int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
+        _cleanup_strv_free_ char **args = NULL;
+        int r, ret = 0;
+        va_list ap;
 
-                        k = va_arg(ap, const char*);
-                        if (!k)
-                                break;
+        /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
+         * this clear. */
+        assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
 
-                        assert_se(v = va_arg(ap, char**));
+        /* This call may clobber arguments on failure! */
 
-                        e = proc_cmdline_key_startswith(word, k);
-                        if (e && *e == '=') {
-                                r = free_and_strdup(v, e + 1);
-                                if (r < 0) {
-                                        va_end(ap);
-                                        return r;
-                                }
+        if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+                _cleanup_free_ char *line = NULL;
 
-                                ret++;
-                        }
-                }
+                r = systemd_efi_options_variable(&line);
+                if (r < 0 && r != -ENODATA)
+                        log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
+                if (r >= 0) {
+                        r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+                        if (r < 0)
+                                return r;
 
-                va_end(ap);
+                        va_start(ap, flags);
+                        r = cmdline_get_key_ap(flags, args, ap);
+                        va_end(ap);
+                        if (r < 0)
+                                return r;
+
+                        ret = r;
+                        args = strv_free(args);
+                }
         }
 
-        return ret;
+        r = proc_cmdline_strv(&args);
+        if (r < 0)
+                return r;
+
+        va_start(ap, flags);
+        r = cmdline_get_key_ap(flags, args, ap);
+        va_end(ap);
+        if (r < 0)
+                return r;
+
+        return ret + r;
 }
index 45f3a27f27483c1f7fb1a940edb1c07f45f06ff9..a64d7757bcc8d1176450d2f1dc2ec538b1c83d05 100644 (file)
@@ -14,9 +14,11 @@ 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_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags);
 int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, ProcCmdlineFlags flags);
 
 int proc_cmdline_get_key(const char *parameter, ProcCmdlineFlags flags, char **value);
index 5d499a57b4f015db0ac14c01647109cb6b177f96..7de7d80cd4aa33261e04653356b3df9baa1b04fc 100644 (file)
@@ -222,18 +222,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
 
                 _cleanup_strv_free_ char **args = NULL;
 
-                args = strv_parse_nulstr(t, k);
+                /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end.
+                 * See also issue #21186. */
+                args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
                 if (!args)
                         return -ENOMEM;
 
-                /* Drop trailing empty strings. See issue #21186. */
-                STRV_FOREACH_BACKWARDS(p, args) {
-                        if (!isempty(*p))
-                                break;
-
-                        *p = mfree(*p);
-                }
-
                 ans = quote_command_line(args, shflags);
                 if (!ans)
                         return -ENOMEM;
@@ -259,6 +253,28 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
         return 0;
 }
 
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
+        _cleanup_free_ char *t = NULL;
+        char **args;
+        size_t k;
+        int r;
+
+        assert(pid >= 0);
+        assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0);
+        assert(ret);
+
+        r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
+        if (r < 0)
+                return r;
+
+        args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
+        if (!args)
+                return -ENOMEM;
+
+        *ret = args;
+        return 0;
+}
+
 int container_get_leader(const char *machine, pid_t *pid) {
         _cleanup_free_ char *s = NULL, *class = NULL;
         const char *p;
@@ -1223,6 +1239,7 @@ int safe_fork_full(
                 /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
                 log_close();
                 log_set_open_when_needed(true);
+                log_settle_target();
         }
 
         if (name) {
@@ -1348,6 +1365,14 @@ int safe_fork_full(
                 }
         }
 
+        if (!FLAGS_SET(flags, FORK_KEEP_NOTIFY_SOCKET)) {
+                r = RET_NERRNO(unsetenv("NOTIFY_SOCKET"));
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to unset $NOTIFY_SOCKET: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         if (ret_pid)
                 *ret_pid = getpid_cached();
 
@@ -1447,6 +1472,15 @@ int pidfd_get_pid(int fd, pid_t *ret) {
         char *p;
         int r;
 
+        /* Converts a pidfd into a pid. Well known errors:
+         *
+         *    -EBADF   → fd invalid
+         *    -ENOSYS  → /proc/ not mounted
+         *    -ENOTTY  → fd valid, but not a pidfd
+         *    -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
+         *    -ESRCH   → fd valid, but process is already reaped
+         */
+
         if (fd < 0)
                 return -EBADF;
 
@@ -1454,22 +1488,22 @@ int pidfd_get_pid(int fd, pid_t *ret) {
 
         r = read_full_virtual_file(path, &fdinfo, NULL);
         if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */
-                return -ESRCH;
+                return proc_mounted() > 0 ? -EBADF : -ENOSYS;
         if (r < 0)
                 return r;
 
-        p = startswith(fdinfo, "Pid:");
-        if (!p) {
-                p = strstr(fdinfo, "\nPid:");
-                if (!p)
-                        return -ENOTTY; /* not a pidfd? */
-
-                p += 5;
-        }
+        p = find_line_startswith(fdinfo, "Pid:");
+        if (!p)
+                return -ENOTTY; /* not a pidfd? */
 
         p += strspn(p, WHITESPACE);
         p[strcspn(p, WHITESPACE)] = 0;
 
+        if (streq(p, "0"))
+                return -EREMOTE; /* PID is in foreign PID namespace? */
+        if (streq(p, "-1"))
+                return -ESRCH;   /* refers to reaped process? */
+
         return parse_pid(p, ret);
 }
 
index 6f6fc7d94e7111f0a39b1630daacd44c16075703..230a0edb0934da1086850644fd7f98b18955d63d 100644 (file)
@@ -40,6 +40,7 @@ typedef enum ProcessCmdlineFlags {
 
 int get_process_comm(pid_t pid, char **ret);
 int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret);
 int get_process_exe(pid_t pid, char **ret);
 int get_process_uid(pid_t pid, uid_t *ret);
 int get_process_gid(pid_t pid, gid_t *ret);
@@ -155,6 +156,7 @@ typedef enum ForkFlags {
         FORK_FLUSH_STDIO        = 1 << 13, /* fflush() stdout (and stderr) before forking */
         FORK_NEW_USERNS         = 1 << 14, /* Run child in its own user namespace */
         FORK_CLOEXEC_OFF        = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
+        FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
 } ForkFlags;
 
 int safe_fork_full(
index f90a63b1a90882c09b0dd49db08408352d4c251d..5675ec2f46bfea24ab7a537641aacc7c7b645d87 100644 (file)
@@ -10,7 +10,6 @@
 
 bool ratelimit_below(RateLimit *r) {
         usec_t ts;
-        bool good = false;
 
         assert(r);
 
@@ -21,20 +20,32 @@ bool ratelimit_below(RateLimit *r) {
 
         if (r->begin <= 0 ||
             usec_sub_unsigned(ts, r->begin) > r->interval) {
-                r->begin = ts;
+                r->begin = ts;  /* Start a new time window */
+                r->num = 1;     /* Reset counter */
+                return true;
+        }
 
-                /* Reset counter */
-                r->num = 0;
-                good = true;
-        } else if (r->num < r->burst)
-                good = true;
+        if (_unlikely_(r->num == UINT_MAX))
+                return false;
 
         r->num++;
-        return good;
+        return r->num <= r->burst;
 }
 
 unsigned ratelimit_num_dropped(RateLimit *r) {
         assert(r);
 
-        return r->num > r->burst ? r->num - r->burst : 0;
+        if (r->num == UINT_MAX) /* overflow, return as special case */
+                return UINT_MAX;
+
+        return LESS_BY(r->num, r->burst);
+}
+
+usec_t ratelimit_end(const RateLimit *rl) {
+        assert(rl);
+
+        if (rl->begin == 0)
+                return 0;
+
+        return usec_add(rl->begin, rl->interval);
 }
index 2236189851e6fcbf2167826daa9a41e8a9f525c4..048084ece49b42bebb765afb5e95bcc8f47a41ba 100644 (file)
@@ -23,3 +23,5 @@ static inline bool ratelimit_configured(RateLimit *rl) {
 bool ratelimit_below(RateLimit *r);
 
 unsigned ratelimit_num_dropped(RateLimit *r);
+
+usec_t ratelimit_end(const RateLimit *rl);
index 43b292252cc7d77087dc685fec0138df7be107c9..5b76948c0648712e2dba170ff6c02dc35093088f 100644 (file)
@@ -1047,7 +1047,7 @@ ssize_t receive_one_fd_iov(
         }
 
         if (found)
-                *ret_fd = *(int*) CMSG_DATA(found);
+                *ret_fd = *CMSG_TYPED_DATA(found, int);
         else
                 *ret_fd = -EBADF;
 
@@ -1171,6 +1171,24 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng
         return NULL;
 }
 
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len) {
+        struct cmsghdr *cmsg;
+
+        assert(mh);
+        assert(buf);
+        assert(buf_len > 0);
+
+        /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used
+         * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on
+         * RISCV32. See issue #27241. */
+
+        cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len));
+        if (!cmsg)
+                return NULL;
+
+        return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len);
+}
+
 int socket_ioctl_fd(void) {
         int fd;
 
index 0bfb29d417550520c73a3425450b8a379a411143..b323b1b99f57d02e5b5014ed34016ee0ca0f20e0 100644 (file)
@@ -175,18 +175,30 @@ int flush_accept(int fd);
 #define CMSG_FOREACH(cmsg, mh)                                          \
         for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
 
+/* Returns the cmsghdr's data pointer, but safely cast to the specified type. Does two alignment checks: one
+ * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one
+ * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as
+ * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8
+ * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as
+ * riscv32. */
 #define CMSG_TYPED_DATA(cmsg, type)                                     \
         ({                                                              \
-                struct cmsghdr *_cmsg = cmsg;                           \
+                struct cmsghdr *_cmsg = (cmsg);                         \
+                assert_cc(alignof(type) <= alignof(struct cmsghdr));    \
                 _cmsg ? CAST_ALIGN_PTR(type, CMSG_DATA(_cmsg)) : (type*) NULL; \
         })
 
 struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len);
 
 /* Type-safe, dereferencing version of cmsg_find() */
 #define CMSG_FIND_DATA(mh, level, type, ctype)                          \
         CMSG_TYPED_DATA(cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))), ctype)
 
+/* Type-safe version of cmsg_find_and_copy_data() */
+#define CMSG_FIND_AND_COPY_DATA(mh, level, type, ctype)             \
+        (ctype*) cmsg_find_and_copy_data(mh, level, type, &(ctype){}, sizeof(ctype))
+
 /* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type
  * itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr"
  * structures. */
index 88b8346a0fd9342f0ae483d6ea53174cc93f0dcb..335daca2345217443e4ca31f81688c7ea79469c8 100644 (file)
@@ -8,7 +8,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dirent-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
@@ -145,24 +145,13 @@ int null_or_empty_path_with_root(const char *fn, const char *root) {
         if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
                 return true;
 
-        r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
+        r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
         if (r < 0)
                 return r;
 
         return null_or_empty(&st);
 }
 
-int null_or_empty_fd(int fd) {
-        struct stat st;
-
-        assert(fd >= 0);
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
-
-        return null_or_empty(&st);
-}
-
 static int fd_is_read_only_fs(int fd) {
         struct statvfs st;
 
@@ -201,10 +190,10 @@ int files_same(const char *filea, const char *fileb, int flags) {
         assert(fileb);
 
         if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
-                return -errno;
+                return log_debug_errno(errno, "Cannot stat %s: %m", filea);
 
         if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
-                return -errno;
+                return log_debug_errno(errno, "Cannot stat %s: %m", fileb);
 
         return stat_inode_same(&a, &b);
 }
@@ -307,6 +296,18 @@ int fd_verify_regular(int fd) {
         return stat_verify_regular(&st);
 }
 
+int verify_regular_at(int dir_fd, const char *path, bool follow) {
+        struct stat st;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
+                return -errno;
+
+        return stat_verify_regular(&st);
+}
+
 int stat_verify_directory(const struct stat *st) {
         assert(st);
 
@@ -454,6 +455,20 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
         return 0;
 }
 
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(ret);
+
+        fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, 0);
+        if (fd < 0)
+                return fd;
+
+        return RET_NERRNO(fstatfs(fd, ret));
+}
+
 void inode_hash_func(const struct stat *q, struct siphash *state) {
         siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
         siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
@@ -470,3 +485,26 @@ int inode_compare_func(const struct stat *a, const struct stat *b) {
 }
 
 DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
+
+const char* inode_type_to_string(mode_t m) {
+
+        /* Returns a short string for the inode type. We use the same name as the underlying macros for each
+         * inode type. */
+
+        switch (m & S_IFMT) {
+        case S_IFREG:
+                return "reg";
+        case S_IFDIR:
+                return "dir";
+        case S_IFCHR:
+                return "chr";
+        case S_IFBLK:
+                return "blk";
+        case S_IFIFO:
+                return "fifo";
+        case S_IFSOCK:
+                return "sock";
+        }
+
+        return NULL;
+}
index de11c0cf7c2b9503fdb8a68f2fccb324e13530b1..e6b84d215e0afca050b91743f581f645cb1c67df 100644 (file)
@@ -30,7 +30,6 @@ static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
 
 bool null_or_empty(struct stat *st) _pure_;
 int null_or_empty_path_with_root(const char *fn, const char *root);
-int null_or_empty_fd(int fd);
 
 static inline int null_or_empty_path(const char *fn) {
         return null_or_empty_path_with_root(fn, NULL);
@@ -65,6 +64,7 @@ int path_is_network_fs(const char *path);
 
 int stat_verify_regular(const struct stat *st);
 int fd_verify_regular(int fd);
+int verify_regular_at(int dir_fd, const char *path, bool follow);
 
 int stat_verify_directory(const struct stat *st);
 int fd_verify_directory(int fd);
@@ -79,6 +79,8 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b);
 
 int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx);
 
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
+
 #if HAS_FEATURE_MEMORY_SANITIZER
 #  warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
 #  define STRUCT_STATX_DEFINE(var)              \
@@ -101,3 +103,5 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
 void inode_hash_func(const struct stat *q, struct siphash *state);
 int inode_compare_func(const struct stat *a, const struct stat *b);
 extern const struct hash_ops inode_hash_ops;
+
+const char* inode_type_to_string(mode_t m);
index e3a26a623ca48443b7318c8e1287ac28760ba56b..3be70dfadedf6655120fd260fca0e418378e2344 100644 (file)
@@ -95,6 +95,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
 #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max)         \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,)  \
         _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
+#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
 
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
index ad8c9863bdda531ac78c93d7f354e612588fdd04..c74ee67dfec24c7d08eb11051d2b4887d98a493d 100644 (file)
@@ -1260,3 +1260,38 @@ char *strdupcspn(const char *a, const char *reject) {
 
         return strndup(a, strcspn(a, reject));
 }
+
+char *find_line_startswith(const char *haystack, const char *needle) {
+        char *p;
+
+        assert(haystack);
+        assert(needle);
+
+        /* Finds the first line in 'haystack' that starts with the specified string. Returns a pointer to the
+         * first character after it */
+
+        p = strstr(haystack, needle);
+        if (!p)
+                return NULL;
+
+        if (p > haystack)
+                while (p[-1] != '\n') {
+                        p = strstr(p + 1, needle);
+                        if (!p)
+                                return NULL;
+                }
+
+        return p + strlen(needle);
+}
+
+char *startswith_strv(const char *string, char **strv) {
+        char *found = NULL;
+
+        STRV_FOREACH(i, strv) {
+                found = startswith(string, *i);
+                if (found)
+                        break;
+        }
+
+        return found;
+}
index e0a47a21a97dc1617fccc23225531d86572fcd37..4430910e225857a741fb750b1738baa80e37a95d 100644 (file)
@@ -29,6 +29,18 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) {
         return strstr(haystack, needle);
 }
 
+static inline char *strstrafter(const char *haystack, const char *needle) {
+        char *p;
+
+        /* Returns NULL if not found, or pointer to first character after needle if found */
+
+        p = strstr_ptr(haystack, needle);
+        if (!p)
+                return NULL;
+
+        return p + strlen(needle);
+}
+
 static inline const char* strnull(const char *s) {
         return s ?: "(null)";
 }
@@ -253,3 +265,10 @@ size_t strspn_from_end(const char *str, const char *accept);
 
 char *strdupspn(const char *a, const char *accept);
 char *strdupcspn(const char *a, const char *reject);
+
+char *find_line_startswith(const char *haystack, const char *needle);
+
+char *startswith_strv(const char *string, char **strv);
+
+#define STARTSWITH_SET(p, ...)                                  \
+        startswith_strv(p, STRV_MAKE(__VA_ARGS__))
index 5fcf3620a60ca7932a90e590c9a4643cbc8da837..822dadeeb4a1bcdedbbfa9fc8b94344553c713d5 100644 (file)
@@ -7,6 +7,7 @@
 #include <stdlib.h>
 
 #include "alloc-util.h"
+#include "env-util.h"
 #include "escape.h"
 #include "extract-word.h"
 #include "fileio.h"
@@ -63,6 +64,16 @@ char* strv_find_startswith(char * const *l, const char *name) {
         return NULL;
 }
 
+char* strv_find_first_field(char * const *needles, char * const *haystack) {
+        STRV_FOREACH(k, needles) {
+                char *value = strv_env_pairs_get((char **)haystack, *k);
+                if (value)
+                        return value;
+        }
+
+        return NULL;
+}
+
 char** strv_free(char **l) {
         STRV_FOREACH(k, l)
                 free(*k);
index 419cda1ee3f9456a4eb1178a9f66c971352b3f83..544d46a3f8d6f5b19623b8b95b7848aecfc9becf 100644 (file)
@@ -17,6 +17,9 @@ char* strv_find(char * const *l, const char *name) _pure_;
 char* strv_find_case(char * const *l, const char *name) _pure_;
 char* strv_find_prefix(char * const *l, const char *name) _pure_;
 char* strv_find_startswith(char * const *l, const char *name) _pure_;
+/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
+ * of the first key from the first vector that is found in the second vector. */
+char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;
 
 #define strv_contains(l, s) (!!strv_find((l), (s)))
 #define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
@@ -197,18 +200,6 @@ static inline void strv_print(char * const *l) {
                 _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
         })
 
-#define STARTSWITH_SET(p, ...)                                  \
-        ({                                                      \
-                const char *_p = (p);                           \
-                char *_found = NULL;                            \
-                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
-                        _found = startswith(_p, *_i);           \
-                        if (_found)                             \
-                                break;                          \
-                }                                               \
-                _found;                                         \
-        })
-
 #define ENDSWITH_SET(p, ...)                                    \
         ({                                                      \
                 const char *_p = (p);                           \
index d3aa5c27e2b788d9e0c2a0353ea51fa54c37eb24..796c262acfd1501544aa54ed27cd227bf8dfd8ff 100644 (file)
@@ -979,7 +979,7 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
                    &ttynr) != 1)
                 return -EIO;
 
-        if (major(ttynr) == 0 && minor(ttynr) == 0)
+        if (devnum_is_zero(ttynr))
                 return -ENXIO;
 
         if (d)
index 379d81d5c8c3abe89175a344f6a6995f1042da85..d44464dd7bd16fce94235551a4987a625fed72bd 100644 (file)
@@ -272,7 +272,7 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
         return fd;
 }
 
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
         _cleanup_free_ char *tmp = NULL;
         int r, fd;
 
@@ -286,7 +286,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
          * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
          * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
 
-        fd = open_parent(target, O_TMPFILE|flags, 0640);
+        fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
         if (fd >= 0) {
                 *ret_path = NULL;
                 return fd;
@@ -298,7 +298,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
         if (r < 0)
                 return r;
 
-        fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+        fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
         if (fd < 0)
                 return -errno;
 
@@ -349,11 +349,12 @@ static int link_fd(int fd, int newdirfd, const char *newpath) {
         return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
 }
 
-int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, bool replace) {
         _cleanup_free_ char *tmp = NULL;
         int r;
 
         assert(fd >= 0);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(target);
 
         /* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
@@ -362,12 +363,12 @@ int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
 
         if (path) {
                 if (replace)
-                        return RET_NERRNO(rename(path, target));
+                        return RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
 
-                return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+                return rename_noreplace(dir_fd, path, dir_fd, target);
         }
 
-        r = link_fd(fd, AT_FDCWD, target);
+        r = link_fd(fd, dir_fd, target);
         if (r != -EEXIST || !replace)
                 return r;
 
@@ -381,12 +382,12 @@ int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
         if (r < 0)
                 return r;
 
-        if (link_fd(fd, AT_FDCWD, tmp) < 0)
+        if (link_fd(fd, dir_fd, tmp) < 0)
                 return -EEXIST; /* propagate original error */
 
-        r = RET_NERRNO(rename(tmp, target));
+        r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
         if (r < 0) {
-                (void) unlink(tmp);
+                (void) unlinkat(dir_fd, tmp, 0);
                 return r;
         }
 
index 4665dafb24c0e10c0ebf614def8e1ce60c066a31..f48ce10e688207fc6b71fe0749c069d3a01ae517 100644 (file)
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 
 int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
@@ -22,10 +23,16 @@ int tempfn_random(const char *p, const char *extra, char **ret);
 int tempfn_random_child(const char *p, const char *extra, char **ret);
 
 int open_tmpfile_unlinkable(const char *directory, int flags);
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path);
+static inline int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+        return open_tmpfile_linkable_at(AT_FDCWD, target, flags, ret_path);
+}
 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file);
 
-int link_tmpfile(int fd, const char *path, const char *target, bool replace);
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, bool replace);
+static inline int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
+        return link_tmpfile_at(fd, AT_FDCWD, path, target, replace);
+}
 int flink_tmpfile(FILE *f, const char *path, const char *target, bool replace);
 
 int mkdtemp_malloc(const char *template, char **ret);
index bdb1860246fecba7c269a12912d2f893df6e6eff..86b66e2be0c49d5f9dbe336e8f18df0cb9856868 100644 (file)
@@ -180,27 +180,30 @@ static const char* const scope_state_table[_SCOPE_STATE_MAX] = {
 DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
 
 static const char* const service_state_table[_SERVICE_STATE_MAX] = {
-        [SERVICE_DEAD]           = "dead",
-        [SERVICE_CONDITION]      = "condition",
-        [SERVICE_START_PRE]      = "start-pre",
-        [SERVICE_START]          = "start",
-        [SERVICE_START_POST]     = "start-post",
-        [SERVICE_RUNNING]        = "running",
-        [SERVICE_EXITED]         = "exited",
-        [SERVICE_RELOAD]         = "reload",
-        [SERVICE_RELOAD_SIGNAL]  = "reload-signal",
-        [SERVICE_RELOAD_NOTIFY]  = "reload-notify",
-        [SERVICE_STOP]           = "stop",
-        [SERVICE_STOP_WATCHDOG]  = "stop-watchdog",
-        [SERVICE_STOP_SIGTERM]   = "stop-sigterm",
-        [SERVICE_STOP_SIGKILL]   = "stop-sigkill",
-        [SERVICE_STOP_POST]      = "stop-post",
-        [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
-        [SERVICE_FINAL_SIGTERM]  = "final-sigterm",
-        [SERVICE_FINAL_SIGKILL]  = "final-sigkill",
-        [SERVICE_FAILED]         = "failed",
-        [SERVICE_AUTO_RESTART]   = "auto-restart",
-        [SERVICE_CLEANING]       = "cleaning",
+        [SERVICE_DEAD]                       = "dead",
+        [SERVICE_CONDITION]                  = "condition",
+        [SERVICE_START_PRE]                  = "start-pre",
+        [SERVICE_START]                      = "start",
+        [SERVICE_START_POST]                 = "start-post",
+        [SERVICE_RUNNING]                    = "running",
+        [SERVICE_EXITED]                     = "exited",
+        [SERVICE_RELOAD]                     = "reload",
+        [SERVICE_RELOAD_SIGNAL]              = "reload-signal",
+        [SERVICE_RELOAD_NOTIFY]              = "reload-notify",
+        [SERVICE_STOP]                       = "stop",
+        [SERVICE_STOP_WATCHDOG]              = "stop-watchdog",
+        [SERVICE_STOP_SIGTERM]               = "stop-sigterm",
+        [SERVICE_STOP_SIGKILL]               = "stop-sigkill",
+        [SERVICE_STOP_POST]                  = "stop-post",
+        [SERVICE_FINAL_WATCHDOG]             = "final-watchdog",
+        [SERVICE_FINAL_SIGTERM]              = "final-sigterm",
+        [SERVICE_FINAL_SIGKILL]              = "final-sigkill",
+        [SERVICE_FAILED]                     = "failed",
+        [SERVICE_DEAD_BEFORE_AUTO_RESTART]   = "dead-before-auto-restart",
+        [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
+        [SERVICE_DEAD_RESOURCES_PINNED]      = "dead-resources-pinned",
+        [SERVICE_AUTO_RESTART]               = "auto-restart",
+        [SERVICE_CLEANING]                   = "cleaning",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
index bae132ea097c185eca57a6576c35b2a089d1fd58..169e1f719ec4ffcd85ae6ef79664c99abd7dc079 100644 (file)
@@ -144,6 +144,9 @@ typedef enum ServiceState {
         SERVICE_FINAL_SIGTERM,     /* In case the STOP_POST executable hangs, we shoot that down, too */
         SERVICE_FINAL_SIGKILL,
         SERVICE_FAILED,
+        SERVICE_DEAD_BEFORE_AUTO_RESTART,
+        SERVICE_FAILED_BEFORE_AUTO_RESTART,
+        SERVICE_DEAD_RESOURCES_PINNED,  /* Like SERVICE_DEAD, but with pinned resources */
         SERVICE_AUTO_RESTART,
         SERVICE_CLEANING,
         _SERVICE_STATE_MAX,
index 1f79a2735e9f543d4b7ee6205868abf790c1a17c..41422579d6415ecdca607043e7e9cd4dd0570a3c 100644 (file)
@@ -2,7 +2,7 @@
 
 #include "sd-id128.h"
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -71,10 +71,11 @@ int unit_symlink_name_compatible(const char *symlink, const char *target, bool i
 }
 
 int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, const char *target) {
-        const char *src, *dst;
+        _cleanup_free_ char *src = NULL, *dst = NULL;
         _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
         UnitType src_unit_type, dst_unit_type;
         UnitNameFlags src_name_type, dst_name_type;
+        int r;
 
         /* Check if the *alias* symlink is valid. This applies to symlinks like
          * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
@@ -87,8 +88,13 @@ int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, con
          * -ELOOP for an alias to self.
          */
 
-        src = basename(filename);
-        dst = basename(target);
+        r = path_extract_filename(filename, &src);
+        if (r < 0)
+                return r;
+
+        r = path_extract_filename(target, &dst);
+        if (r < 0)
+                return r;
 
         /* src checks */
 
@@ -322,7 +328,7 @@ int unit_file_resolve_symlink(
         }
 
         /* Get rid of "." and ".." components in target path */
-        r = chase_symlinks(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
+        r = chase(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
         if (r < 0)
                 return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m",
                                          dir, filename, target);
@@ -432,7 +438,7 @@ int unit_file_build_name_map(
                 if (r < 0)
                         return log_oom();
 
-                r = chase_symlinks(*dir, NULL, 0, &resolved_dir, NULL);
+                r = chase(*dir, NULL, 0, &resolved_dir, NULL);
                 if (r < 0) {
                         if (r != -ENOENT)
                                 log_warning_errno(r, "Failed to resolve symlink %s, ignoring: %m", *dir);
@@ -762,7 +768,10 @@ int unit_file_find_fragment(
         }
 
         if (fragment && ret_names) {
-                const char *fragment_basename = basename(fragment);
+                _cleanup_free_ char *fragment_basename = NULL;
+                r = path_extract_filename(fragment, &fragment_basename);
+                if (r < 0)
+                        return r;
 
                 if (!streq(fragment_basename, unit_name)) {
                         /* Add names based on the fragment name to the set of names */
index 0a47947d34853e174227eb01582d0534b7c687a7..dd2642d214e57a7c66f18cccc5f4c8fa17d1820e 100644 (file)
@@ -13,7 +13,7 @@
 #include "sd-messages.h"
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "errno-util.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -23,7 +23,6 @@
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
-#include "path-util.h"
 #include "random-util.h"
 #include "string-util.h"
 #include "strv.h"
@@ -156,21 +155,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_symlinks(DEFAULT_USER_SHELL, root, CHASE_PREFIX_ROOT, NULL, NULL);
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
         if (r < 0 && r != -ENOENT)
-                log_debug_errno(r, "Failed to look up shell '%s%s%s': %m",
-                                strempty(root), root ? "/" : "", DEFAULT_USER_SHELL);
+                log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
         if (r > 0)
                 return DEFAULT_USER_SHELL;
 
         return "/bin/sh";
 }
 
+const char *default_root_shell(const char *root) {
+        _cleanup_close_ int rfd = -EBADF;
+
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return "/bin/sh";
+
+        return default_root_shell_at(rfd);
+}
+
 static int synthesize_user_creds(
                 const char **username,
                 uid_t *uid, gid_t *gid,
index a08683bceab096bece04de7049ac431b5a0d90e5..5aca6307e05bff7567efbc3cce20dad0a98ca42e 100644 (file)
@@ -80,7 +80,8 @@ int take_etc_passwd_lock(const char *root);
 #define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1))
 #define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1))
 
-#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
+#define ETC_PASSWD_LOCK_FILENAME ".pwd.lock"
+#define ETC_PASSWD_LOCK_PATH "/etc/" ETC_PASSWD_LOCK_FILENAME
 
 /* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer
  * NULL is special */
@@ -130,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 f264cc6eb3d787fb4ce20a7c4eda104e22f68fc7..aadc923bb54f93f2d0580d01fef124cbeee19ff4 100644 (file)
@@ -264,6 +264,7 @@ static Virtualization detect_vm_dmi(void) {
                          * so we fallback to using the product name which is less restricted
                          * to distinguish metal systems from virtualized instances */
                         _cleanup_free_ char *s = NULL;
+                        const char *e;
 
                         r = read_full_virtual_file("/sys/class/dmi/id/product_name", &s, NULL);
                         /* In EC2, virtualized is much more common than metal, so if for some reason
@@ -273,8 +274,9 @@ static Virtualization detect_vm_dmi(void) {
                                                 " assuming virtualized: %m");
                                 return VIRTUALIZATION_AMAZON;
                         }
-                        if (endswith(truncate_nl(s), ".metal")) {
-                                log_debug("DMI product name ends with '.metal', assuming no virtualization");
+                        e = strstrafter(truncate_nl(s), ".metal");
+                        if (e && IN_SET(*e, 0, '-')) {
+                                log_debug("DMI product name has '.metal', assuming no virtualization");
                                 return VIRTUALIZATION_NONE;
                         } else
                                 return VIRTUALIZATION_AMAZON;
index 6d356d640fb3f300f92b7515979131dec8cdf2df..2dd50360ecd471a21a1600fcf566a6a39e252f7e 100644 (file)
@@ -4,7 +4,7 @@
 #include "bootctl-install.h"
 #include "bootctl-random-seed.h"
 #include "bootctl-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "copy.h"
 #include "dirent-util.h"
 #include "efi-api.h"
@@ -133,7 +133,7 @@ static int settle_make_entry_directory(void) {
         bool layout_type1 = use_boot_loader_spec_type1();
         if (arg_make_entry_directory < 0) { /* Automatic mode */
                 if (layout_type1) {
-                        if (arg_entry_token_type == ARG_ENTRY_TOKEN_MACHINE_ID) {
+                        if (arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID) {
                                 r = path_is_temporary_fs("/etc/machine-id");
                                 if (r < 0)
                                         return log_debug_errno(r, "Couldn't determine whether /etc/machine-id is on a temporary file system: %m");
@@ -189,29 +189,29 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t
         assert(to);
 
         r = get_file_version(fd_from, &a);
+        if (r == -ESRCH)
+                return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from);
         if (r < 0)
                 return r;
-        if (r == 0)
-                return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE),
-                                       "Source file \"%s\" does not carry version information!",
-                                       from);
 
         r = get_file_version(fd_to, &b);
+        if (r == -ESRCH)
+                return log_notice_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).",
+                                        to);
         if (r < 0)
                 return r;
-        if (r == 0 || compare_product(a, b) != 0)
-                return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE),
-                                        "Skipping \"%s\", since it's owned by another boot loader.",
-                                        to);
+        if (compare_product(a, b) != 0)
+                return log_notice_errno(SYNTHETIC_ERRNO(ESRCH),
+                                        "Skipping \"%s\", it's owned by another boot loader.", to);
 
         r = compare_version(a, b);
         log_debug("Comparing versions: \"%s\" %s \"%s", a, comparison_operator(r), b);
         if (r < 0)
                 return log_warning_errno(SYNTHETIC_ERRNO(ESTALE),
-                                         "Skipping \"%s\", since newer boot loader version in place already.", to);
+                                         "Skipping \"%s\", newer boot loader version in place already.", to);
         if (r == 0)
                 return log_info_errno(SYNTHETIC_ERRNO(ESTALE),
-                                      "Skipping \"%s\", since same boot loader version in place already.", to);
+                                      "Skipping \"%s\", same boot loader version in place already.", to);
 
         return 0;
 }
@@ -337,10 +337,10 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) {
         if (!p)
                 return log_oom();
 
-        r = chase_symlinks(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
+        r = chase(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
         /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
         if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
-                r = chase_symlinks(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
+                r = chase(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
         if (r < 0)
                 return log_error_errno(r,
                                        "Failed to resolve path %s%s%s: %m",
@@ -352,7 +352,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) {
         if (!q)
                 return log_oom();
 
-        r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
+        r = chase(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path);
 
@@ -369,7 +369,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) {
                 v = strjoina("/EFI/BOOT/BOOT", e);
                 ascii_strupper(strrchr(v, '/') + 1);
 
-                r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
+                r = chase(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path);
 
@@ -387,10 +387,14 @@ static int install_binaries(const char *esp_path, const char *arch, bool force)
         _cleanup_free_ char *path = NULL;
         int r;
 
-        r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+        r = chase_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
         /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
         if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
-                r = chase_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+                r = chase_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+        if (r == -ENOENT && arg_graceful) {
+                log_debug("Source directory does not exist, ignoring.");
+                return 0;
+        }
         if (r < 0)
                 return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
 
@@ -415,7 +419,7 @@ static int install_binaries(const char *esp_path, const char *arch, bool force)
                 k = copy_one_file(esp_path, de->d_name, force);
                 /* Don't propagate an error code if no update necessary, installed version already equal or
                  * newer version, or other boot loader in place. */
-                if (arg_graceful && IN_SET(k, -ESTALE, -EREMOTE))
+                if (arg_graceful && IN_SET(k, -ESTALE, -ESRCH))
                         continue;
                 if (k < 0 && r == 0)
                         r = k;
@@ -510,7 +514,7 @@ static int install_entry_token(void) {
         /* Let's save the used entry token in /etc/kernel/entry-token if we used it to create the entry
          * directory, or if anything else but the machine ID */
 
-        if (!arg_make_entry_directory && arg_entry_token_type == ARG_ENTRY_TOKEN_MACHINE_ID)
+        if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
                 return 0;
 
         p = path_join(arg_root, etc_kernel(), "entry-token");
@@ -648,7 +652,8 @@ static int install_variables(
                 uint64_t psize,
                 sd_id128_t uuid,
                 const char *path,
-                bool first) {
+                bool first,
+                bool graceful) {
 
         uint16_t slot;
         int r;
@@ -664,25 +669,37 @@ static int install_variables(
                 return 0;
         }
 
-        r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
+        r = chase_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
                 return log_error_errno(r, "Cannot access \"%s/%s\": %m", esp_path, path);
 
         r = find_slot(uuid, path, &slot);
-        if (r < 0)
-                return log_error_errno(r,
-                                       r == -ENOENT ?
-                                       "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" :
-                                       "Failed to determine current boot order: %m");
+        if (r < 0) {
+                int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
+                const char *skip = graceful ? ", skipping" : "";
+
+                log_full_errno(level, r,
+                               r == -ENOENT ?
+                               "Failed to access EFI variables%s. Is the \"efivarfs\" filesystem mounted?" :
+                               "Failed to determine current boot order%s: %m", skip);
+
+                return graceful ? 0 : r;
+        }
 
         if (first || r == 0) {
                 r = efi_add_boot_option(slot, pick_efi_boot_option_description(),
                                         part, pstart, psize,
                                         uuid, path);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to create EFI Boot variable entry: %m");
+                if (r < 0) {
+                        int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
+                        const char *skip = graceful ? ", skipping" : "";
+
+                        log_full_errno(level, r, "Failed to create EFI Boot variable entry%s: %m", skip);
+
+                        return graceful ? 0 : r;
+                }
 
                 log_info("Created EFI boot entry \"%s\".", pick_efi_boot_option_description());
         }
@@ -810,7 +827,7 @@ int verb_install(int argc, char *argv[], void *userdata) {
         }
 
         char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi");
-        return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install);
+        return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install, graceful);
 }
 
 static int remove_boot_efi(const char *esp_path) {
@@ -818,7 +835,7 @@ static int remove_boot_efi(const char *esp_path) {
         _cleanup_free_ char *p = NULL;
         int r, c = 0;
 
-        r = chase_symlinks_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
+        r = chase_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
@@ -839,9 +856,11 @@ static int remove_boot_efi(const char *esp_path) {
                         return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
 
                 r = get_file_version(fd, &v);
+                if (r == -ESRCH)
+                        continue;  /* No version information */
                 if (r < 0)
                         return r;
-                if (r > 0 && startswith(v, "systemd-boot ")) {
+                if (startswith(v, "systemd-boot ")) {
                         r = unlinkat(dirfd(d), de->d_name, 0);
                         if (r < 0)
                                 return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name);
index ce97b013cf5d784c6d046ee19abb252a33cd3f79..3da6478259010004afa22e1e31f8cd6d687e18ff 100644 (file)
@@ -7,7 +7,7 @@
 #include "bootctl-status.h"
 #include "bootctl-util.h"
 #include "bootspec.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "devnum-util.h"
 #include "dirent-util.h"
 #include "efi-api.h"
@@ -200,14 +200,14 @@ static int enumerate_binaries(
         assert(previous);
         assert(is_first);
 
-        r = chase_symlinks_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
+        r = chase_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
                 return log_error_errno(r, "Failed to read \"%s/%s\": %m", esp_path, path);
 
         FOREACH_DIRENT(de, d, break) {
-                _cleanup_free_ char *v = NULL;
+                _cleanup_free_ char *v = NULL, *filename = NULL;
                 _cleanup_close_ int fd = -EBADF;
 
                 if (!endswith_no_case(de->d_name, ".efi"))
@@ -216,15 +216,23 @@ static int enumerate_binaries(
                 if (prefix && !startswith_no_case(de->d_name, prefix))
                         continue;
 
+                filename = path_join(p, de->d_name);
+                if (!filename)
+                        return log_oom();
+                LOG_SET_PREFIX(filename);
+
                 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
                 if (fd < 0)
-                        return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
+                        return log_error_errno(errno, "Failed to open file for reading: %m");
 
                 r = get_file_version(fd, &v);
+                if (r == -ESRCH) /* Not the file we are looking for. */
+                        continue;
                 if (r < 0)
                         return r;
 
-                if (*previous) { /* let's output the previous entry now, since now we know that there will be one more, and can draw the tree glyph properly */
+                if (*previous) { /* Let's output the previous entry now, since now we know that there will be
+                                  * one more, and can draw the tree glyph properly. */
                         printf("         %s %s%s\n",
                                *is_first ? "File:" : "     ",
                                special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous);
@@ -560,7 +568,7 @@ static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *
                 return;
 
         if (arg_dry_run) {
-                r = chase_symlinks_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
+                r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
                 if (r < 0)
                         log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn);
                 else
@@ -568,7 +576,7 @@ static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *
                 return;
         }
 
-        r = chase_symlinks_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
+        r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
         if (r >= 0)
                 log_info("Removed \"%s\"", path);
         else if (r != -ENOENT)
@@ -576,7 +584,7 @@ static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *
 
         _cleanup_free_ char *d = NULL;
         if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) {
-                r = chase_symlinks_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
+                r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
                 if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT))
                         log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d);
         }
@@ -672,7 +680,7 @@ static int unlink_entry(const BootConfig *config, const char *root, const char *
         if (arg_dry_run)
                 log_info("Would remove \"%s\"", e->path);
         else {
-                r = chase_symlinks_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
+                r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to remove \"%s\": %m", e->path);
 
@@ -734,7 +742,7 @@ static int cleanup_orphaned_files(
         if (r < 0)
                 return log_error_errno(r, "Failed to count files in %s: %m", root);
 
-        dir_fd = chase_symlinks_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
+        dir_fd = chase_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
                         O_DIRECTORY|O_CLOEXEC, &full);
         if (dir_fd == -ENOENT)
                 return 0;
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 62c0b64406d272622b58a7550ad78a88909e64d5..7259f177a8031c2e44ea10a5749cbfc5aaae25ca 100644 (file)
@@ -5,11 +5,8 @@
 #include "bootctl.h"
 #include "bootctl-util.h"
 #include "fileio.h"
-#include "os-util.h"
-#include "path-util.h"
 #include "stat-util.h"
 #include "sync-util.h"
-#include "utf8.h"
 
 int sync_everything(void) {
         int ret = 0, k;
@@ -66,7 +63,7 @@ int get_file_version(int fd, char **ret) {
         struct stat st;
         char *buf;
         const char *s, *e;
-        char *x = NULL;
+        char *marker = NULL;
         int r;
 
         assert(fd >= 0);
@@ -76,135 +73,58 @@ int get_file_version(int fd, char **ret) {
                 return log_error_errno(errno, "Failed to stat EFI binary: %m");
 
         r = stat_verify_regular(&st);
-        if (r < 0)
-                return log_error_errno(r, "EFI binary is not a regular file: %m");
-
-        if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size)) {
-                *ret = NULL;
-                return 0;
+        if (r < 0) {
+                log_debug_errno(r, "EFI binary is not a regular file, assuming no version information: %m");
+                return -ESRCH;
         }
 
+        if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size))
+                return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
+                                       "EFI binary size too %s: %"PRIi64,
+                                       st.st_size < 27 ? "small" : "large", st.st_size);
+
         buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
         if (buf == MAP_FAILED)
-                return log_error_errno(errno, "Failed to memory map EFI binary: %m");
+                return log_error_errno(errno, "Failed to mmap EFI binary: %m");
 
         s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
         if (!s) {
-                r = -ESRCH;
+                r = log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no LoaderInfo marker.");
                 goto finish;
         }
 
         e = memmem_safe(s, st.st_size - (s - buf), " ####", 5);
         if (!e || e - s < 3) {
-                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed version string.");
+                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "EFI binary has malformed LoaderInfo marker.");
                 goto finish;
         }
 
-        x = strndup(s, e - s);
-        if (!x) {
+        marker = strndup(s, e - s);
+        if (!marker) {
                 r = log_oom();
                 goto finish;
         }
-        r = 1;
 
+        log_debug("EFI binary LoaderInfo marker: \"%s\"", marker);
+        r = 0;
+        *ret = marker;
 finish:
         (void) munmap(buf, st.st_size);
-        if (r >= 0)
-                *ret = x;
-
         return r;
 }
 
 int settle_entry_token(void) {
         int r;
 
-        switch (arg_entry_token_type) {
-
-        case ARG_ENTRY_TOKEN_AUTO: {
-                _cleanup_free_ char *buf = NULL, *p = NULL;
-                p = path_join(arg_root, etc_kernel(), "entry-token");
-                if (!p)
-                        return log_oom();
-                r = read_one_line_file(p, &buf);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read %s: %m", p);
-
-                if (!isempty(buf)) {
-                        free_and_replace(arg_entry_token, buf);
-                        arg_entry_token_type = ARG_ENTRY_TOKEN_LITERAL;
-                } else if (sd_id128_is_null(arg_machine_id)) {
-                        _cleanup_free_ char *id = NULL, *image_id = NULL;
-
-                        r = parse_os_release(arg_root,
-                                             "IMAGE_ID", &image_id,
-                                             "ID", &id);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to load /etc/os-release: %m");
-
-                        if (!isempty(image_id)) {
-                                free_and_replace(arg_entry_token, image_id);
-                                arg_entry_token_type = ARG_ENTRY_TOKEN_OS_IMAGE_ID;
-                        } else if (!isempty(id)) {
-                                free_and_replace(arg_entry_token, id);
-                                arg_entry_token_type = ARG_ENTRY_TOKEN_OS_ID;
-                        } else
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set, and /etc/os-release carries no ID=/IMAGE_ID= fields.");
-                } else {
-                        r = free_and_strdup_warn(&arg_entry_token, SD_ID128_TO_STRING(arg_machine_id));
-                        if (r < 0)
-                                return r;
-
-                        arg_entry_token_type = ARG_ENTRY_TOKEN_MACHINE_ID;
-                }
-
-                break;
-        }
-
-        case ARG_ENTRY_TOKEN_MACHINE_ID:
-                if (sd_id128_is_null(arg_machine_id))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set.");
-
-                r = free_and_strdup_warn(&arg_entry_token, SD_ID128_TO_STRING(arg_machine_id));
-                if (r < 0)
-                        return r;
-
-                break;
-
-        case ARG_ENTRY_TOKEN_OS_IMAGE_ID: {
-                _cleanup_free_ char *buf = NULL;
-
-                r = parse_os_release(arg_root, "IMAGE_ID", &buf);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to load /etc/os-release: %m");
-
-                if (isempty(buf))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "IMAGE_ID= field not set in /etc/os-release.");
-
-                free_and_replace(arg_entry_token, buf);
-                break;
-        }
-
-        case ARG_ENTRY_TOKEN_OS_ID: {
-                _cleanup_free_ char *buf = NULL;
-
-                r = parse_os_release(arg_root, "ID", &buf);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to load /etc/os-release: %m");
-
-                if (isempty(buf))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ID= field not set in /etc/os-release.");
-
-                free_and_replace(arg_entry_token, buf);
-                break;
-        }
-
-        case ARG_ENTRY_TOKEN_LITERAL:
-                assert(!isempty(arg_entry_token)); /* already filled in by command line parser */
-                break;
-        }
-
-        if (isempty(arg_entry_token) || !(utf8_is_valid(arg_entry_token) && string_is_safe(arg_entry_token)))
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected entry token not valid: %s", arg_entry_token);
+        r = boot_entry_token_ensure(
+                        arg_root,
+                        etc_kernel(),
+                        arg_machine_id,
+                        /* machine_id_is_random = */ false,
+                        &arg_entry_token_type,
+                        &arg_entry_token);
+        if (r < 0)
+                return r;
 
         log_debug("Using entry token: %s", arg_entry_token);
         return 0;
index f984c3050e9350455570c1d0f5a56fef1bdc1f13..0480e320c0f5408f6a891cf27cf66a7e189064bc 100644 (file)
@@ -43,7 +43,7 @@ bool arg_quiet = false;
 int arg_make_entry_directory = false; /* tri-state: < 0 for automatic logic */
 sd_id128_t arg_machine_id = SD_ID128_NULL;
 char *arg_install_layout = NULL;
-EntryTokenType arg_entry_token_type = ARG_ENTRY_TOKEN_AUTO;
+BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
 char *arg_entry_token = NULL;
 JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 bool arg_arch_all = false;
@@ -52,6 +52,7 @@ char *arg_image = NULL;
 InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO;
 char *arg_efi_boot_option_description = NULL;
 bool arg_dry_run = false;
+ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
@@ -60,6 +61,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 int acquire_esp(
                 bool unprivileged_mode,
@@ -168,6 +170,8 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --boot-path=PATH  Path to the $BOOT partition\n"
                "     --root=PATH       Operate on an alternate filesystem root\n"
                "     --image=PATH      Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY\n"
+               "                       Specify disk image dissection policy\n"
                "     --install-source=auto|image|host\n"
                "                       Where to pick files when using --root=/--image=\n"
                "  -p --print-esp-path  Print path to the EFI System Partition mount point\n"
@@ -207,6 +211,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_BOOT_PATH,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_INSTALL_SOURCE,
                 ARG_VERSION,
                 ARG_NO_VARIABLES,
@@ -228,6 +233,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "boot-path",                   required_argument, NULL, ARG_BOOT_PATH                   },
                 { "root",                        required_argument, NULL, ARG_ROOT                        },
                 { "image",                       required_argument, NULL, ARG_IMAGE                       },
+                { "image-policy",                required_argument, NULL, ARG_IMAGE_POLICY                },
                 { "install-source",              required_argument, NULL, ARG_INSTALL_SOURCE              },
                 { "print-esp-path",              no_argument,       NULL, 'p'                             },
                 { "print-path",                  no_argument,       NULL, 'p'                             }, /* Compatibility alias */
@@ -248,7 +254,6 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         int c, r;
-        bool b;
 
         assert(argc >= 0);
         assert(argv);
@@ -287,6 +292,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_INSTALL_SOURCE:
                         if (streq(optarg, "auto"))
                                 arg_install_source = ARG_INSTALL_SOURCE_AUTO;
@@ -328,40 +339,21 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_quiet = true;
                         break;
 
-                case ARG_ENTRY_TOKEN: {
-                        const char *e;
-
-                        if (streq(optarg, "machine-id")) {
-                                arg_entry_token_type = ARG_ENTRY_TOKEN_MACHINE_ID;
-                                arg_entry_token = mfree(arg_entry_token);
-                        } else if (streq(optarg, "os-image-id")) {
-                                arg_entry_token_type = ARG_ENTRY_TOKEN_OS_IMAGE_ID;
-                                arg_entry_token = mfree(arg_entry_token);
-                        } else if (streq(optarg, "os-id")) {
-                                arg_entry_token_type = ARG_ENTRY_TOKEN_OS_ID;
-                                arg_entry_token = mfree(arg_entry_token);
-                        } else if ((e = startswith(optarg, "literal:"))) {
-                                arg_entry_token_type = ARG_ENTRY_TOKEN_LITERAL;
-
-                                r = free_and_strdup_warn(&arg_entry_token, e);
-                                if (r < 0)
-                                        return r;
-                        } else
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Unexpected parameter for --entry-token=: %s", optarg);
-
+                case ARG_ENTRY_TOKEN:
+                        r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
+                        if (r < 0)
+                                return r;
                         break;
-                }
 
                 case ARG_MAKE_ENTRY_DIRECTORY:
                         if (streq(optarg, "auto"))  /* retained for backwards compatibility */
                                 arg_make_entry_directory = -1; /* yes if machine-id is permanent */
                         else {
-                                r = parse_boolean_argument("--make-entry-directory=", optarg, &b);
+                                r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
                                 if (r < 0)
                                         return r;
 
-                                arg_make_entry_directory = b;
+                                arg_make_entry_directory = r;
                         }
                         break;
 
@@ -385,7 +377,8 @@ static int parse_argv(int argc, char *argv[]) {
                         }
                         if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "--efi-boot-option-description= too long: %zu > %zu", strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
+                                                       "--efi-boot-option-description= too long: %zu > %zu",
+                                                       strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
                         r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg);
                         if (r < 0)
                                 return r;
@@ -455,8 +448,7 @@ static int run(int argc, char *argv[]) {
         _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
         int r;
 
-        log_parse_environment();
-        log_open();
+        log_setup();
 
         /* If we run in a container, automatically turn off EFI file system access */
         if (detect_container() > 0)
@@ -498,6 +490,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK,
                                 &unlink_dir,
index 9012bf932bbde6bb6295b6dcb96986127f86478c..dd98b959c2964869e49bdc1b399cf0fd78eca06c 100644 (file)
@@ -3,17 +3,11 @@
 
 #include "sd-id128.h"
 
+#include "boot-entry.h"
+#include "image-policy.h"
 #include "json.h"
 #include "pager.h"
 
-typedef enum EntryTokenType {
-        ARG_ENTRY_TOKEN_MACHINE_ID,
-        ARG_ENTRY_TOKEN_OS_IMAGE_ID,
-        ARG_ENTRY_TOKEN_OS_ID,
-        ARG_ENTRY_TOKEN_LITERAL,
-        ARG_ENTRY_TOKEN_AUTO,
-} EntryTokenType;
-
 typedef enum InstallSource {
         ARG_INSTALL_SOURCE_IMAGE,
         ARG_INSTALL_SOURCE_HOST,
@@ -32,7 +26,7 @@ extern bool arg_quiet;
 extern int arg_make_entry_directory; /* tri-state: < 0 for automatic logic */
 extern sd_id128_t arg_machine_id;
 extern char *arg_install_layout;
-extern EntryTokenType arg_entry_token_type;
+extern BootEntryTokenType arg_entry_token_type;
 extern char *arg_entry_token;
 extern JsonFormatFlags arg_json_format_flags;
 extern bool arg_arch_all;
@@ -41,6 +35,7 @@ extern char *arg_image;
 extern InstallSource arg_install_source;
 extern char *arg_efi_boot_option_description;
 extern bool arg_dry_run;
+extern ImagePolicy *arg_image_policy;
 
 static inline const char *arg_dollar_boot_path(void) {
         /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
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 e516417c07158199689f44acb1ecf361626b77c2..9759d036b38c82324ff48f27af0593968cf36fd9 100644 (file)
@@ -191,7 +191,7 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const
                 uint32_t entry_point;
         } _packed_ LinuxPeCompat1;
 
-        while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) {
+        while (size >= sizeof(LinuxPeCompat1) && addr % alignof(LinuxPeCompat1) == 0) {
                 LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) dos + addr);
 
                 if (compat->type == 0 || compat->size == 0 || compat->size > size)
index f2d9f20e96854450e447d9190ffa93da771e1dcc..8daeb71cb25d1c0b543f1ba4a0b4509eb5272be2 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "graphics.h"
+#include "logarithm.h"
 #include "proto/graphics-output.h"
 #include "splash.h"
 #include "unaligned-fundamental.h"
@@ -141,14 +142,14 @@ static void read_channel_maks(
                 channel_shift[R] = __builtin_ctz(dib->channel_mask_r);
                 channel_shift[G] = __builtin_ctz(dib->channel_mask_g);
                 channel_shift[B] = __builtin_ctz(dib->channel_mask_b);
-                channel_scale[R] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_r)) - 1);
-                channel_scale[G] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_g)) - 1);
-                channel_scale[B] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_b)) - 1);
+                channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1);
+                channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1);
+                channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1);
 
                 if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) {
                         channel_mask[A] = dib->channel_mask_a;
                         channel_shift[A] = __builtin_ctz(dib->channel_mask_a);
-                        channel_scale[A] = 0xff / ((1 << __builtin_popcount(dib->channel_mask_a)) - 1);
+                        channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1);
                 } else {
                         channel_mask[A] = 0;
                         channel_shift[A] = 0;
index 6339d82ddc8235ec1db184f01c04a16aaef2a263..eb4bd77ac3ca9dc9153ccd1ac6aaf6ea92e03d79 100644 (file)
@@ -277,6 +277,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
                 mangle_stub_cmdline(cmdline);
         }
 
+        /* SMBIOS strings are measured in PCR1, so we do not re-measure these command line extensions. */
         const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
         if (extra) {
                 _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra);
index 13972528cd59136d114304e58b856d8ed11c3309..f902b83a99b8aac3a7b903932c8a21a51d749c2c 100644 (file)
@@ -4,59 +4,91 @@
 #include "util.h"
 #include "vmm.h"
 
-#ifdef __x86_64__
-static uint64_t ticks_read(void) {
-        uint64_t a, d;
+#if defined(__i386__) || defined(__x86_64__)
+#  include <cpuid.h>
 
+static uint64_t ticks_read_arch(void) {
         /* The TSC might or might not be virtualized in VMs (and thus might not be accurate or start at zero
          * at boot), depending on hypervisor and CPU functionality. If it's not virtualized it's not useful
          * for keeping time, hence don't attempt to use it. */
         if (in_hypervisor())
                 return 0;
 
-        __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
-        return (d << 32) | a;
+        return __builtin_ia32_rdtsc();
 }
-#elif defined(__i386__)
-static uint64_t ticks_read(void) {
-        uint64_t val;
 
-        if (in_hypervisor())
+static uint64_t ticks_freq_arch(void) {
+        /* Detect TSC frequency from CPUID information if available. */
+
+        unsigned max_leaf, ebx, ecx, edx;
+        if (__get_cpuid(0, &max_leaf, &ebx, &ecx, &edx) == 0)
                 return 0;
 
-        __asm__ volatile ("rdtsc" : "=A" (val));
-        return val;
+        /* Leaf 0x15 is Intel only. */
+        if (max_leaf < 0x15 || ebx != signature_INTEL_ebx || ecx != signature_INTEL_ecx ||
+            edx != signature_INTEL_edx)
+                return 0;
+
+        unsigned denominator, numerator, crystal_hz;
+        __cpuid(0x15, denominator, numerator, crystal_hz, edx);
+        if (denominator == 0 || numerator == 0)
+                return 0;
+
+        uint64_t freq = crystal_hz;
+        if (crystal_hz == 0) {
+                /* If the crystal frquency is not available, try to deduce it from
+                 * the processor frequency leaf if available. */
+                if (max_leaf < 0x16)
+                        return 0;
+
+                unsigned core_mhz;
+                __cpuid(0x16, core_mhz, ebx, ecx, edx);
+                freq = core_mhz * 1000ULL * 1000ULL * denominator / numerator;
+        }
+
+        return freq * numerator / denominator;
 }
+
 #elif defined(__aarch64__)
-static uint64_t ticks_read(void) {
+
+static uint64_t ticks_read_arch(void) {
         uint64_t val;
         asm volatile("mrs %0, cntvct_el0" : "=r"(val));
         return val;
 }
-#else
-static uint64_t ticks_read(void) {
-        return 0;
-}
-#endif
 
-#if defined(__aarch64__)
-static uint64_t ticks_freq(void) {
+static uint64_t ticks_freq_arch(void) {
         uint64_t freq;
         asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
         return freq;
 }
+
 #else
-/* count TSC ticks during a millisecond delay */
+
+static uint64_t ticks_read_arch(void) {
+        return 0;
+}
+
+static uint64_t ticks_freq_arch(void) {
+        return 0;
+}
+
+#endif
+
 static uint64_t ticks_freq(void) {
-        uint64_t ticks_start, ticks_end;
         static uint64_t cache = 0;
 
         if (cache != 0)
                 return cache;
 
-        ticks_start = ticks_read();
+        cache = ticks_freq_arch();
+        if (cache != 0)
+                return cache;
+
+        /* As a fallback, count ticks during a millisecond delay. */
+        uint64_t ticks_start = ticks_read_arch();
         BS->Stall(1000);
-        ticks_end = ticks_read();
+        uint64_t ticks_end = ticks_read_arch();
 
         if (ticks_end < ticks_start) /* Check for an overflow (which is not that unlikely, given on some
                                       * archs the value is 32bit) */
@@ -65,16 +97,13 @@ static uint64_t ticks_freq(void) {
         cache = (ticks_end - ticks_start) * 1000UL;
         return cache;
 }
-#endif
 
 uint64_t time_usec(void) {
-        uint64_t ticks, freq;
-
-        ticks = ticks_read();
+        uint64_t ticks = ticks_read_arch();
         if (ticks == 0)
                 return 0;
 
-        freq = ticks_freq();
+        uint64_t freq = ticks_freq();
         if (freq == 0)
                 return 0;
 
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 896e2486532c747b097f7f295ca6172d8ceffdcc..afa474c88342b987ff6669cb5d56759dc7d3b525 100644 (file)
@@ -8,7 +8,7 @@
 #include "blkid-util.h"
 #include "blockdev-util.h"
 #include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "efi-loader.h"
 #include "efivars.h"
 #include "escape.h"
@@ -259,7 +259,7 @@ static int run(int argc, char *argv[]) {
                 if (optind != argc)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
 
-                dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
+                dfd = chase_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
                 if (dfd < 0)
                         return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
 
index e176710dfa859f8bef0f0db49120a635b2db4f7d..90f20c05a16d7bf788afed85c60c65abe13507b8 100644 (file)
@@ -39,7 +39,7 @@
 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
-static bool arg_full = false;
+static int arg_full = -1;
 static const char *arg_address = NULL;
 static bool arg_unique = false;
 static bool arg_acquired = false;
@@ -215,7 +215,7 @@ static int list_bus_names(int argc, char **argv, void *userdata) {
         if (!table)
                 return log_oom();
 
-        if (arg_full)
+        if (arg_full > 0)
                 table_set_width(table, 0);
 
         r = table_set_align_percent(table, table_get_cell(table, 0, COLUMN_PID), 100);
@@ -369,9 +369,6 @@ static int list_bus_names(int argc, char **argv, void *userdata) {
 }
 
 static void print_subtree(const char *prefix, const char *path, char **l) {
-        const char *vertical, *space;
-        char **n;
-
         /* We assume the list is sorted. Let's first skip over the
          * entry we are looking at. */
         for (;;) {
@@ -384,11 +381,13 @@ static void print_subtree(const char *prefix, const char *path, char **l) {
                 l++;
         }
 
-        vertical = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
-        space = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_SPACE));
+        const char
+                *vertical = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL)),
+                *space = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_SPACE));
 
         for (;;) {
                 bool has_more = false;
+                char **n;
 
                 if (!*l || !path_startswith(*l, path))
                         break;
@@ -973,8 +972,8 @@ static int introspect(int argc, char **argv, void *userdata) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_xml = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(member_set_freep) Set *members = NULL;
-        unsigned name_width, type_width, signature_width, result_width, j, k = 0;
-        Member *m, **sorted = NULL;
+        unsigned name_width, type_width, signature_width, result_width;
+        Member *m;
         const char *xml;
         int r;
 
@@ -1098,7 +1097,8 @@ static int introspect(int argc, char **argv, void *userdata) {
         signature_width = strlen("SIGNATURE");
         result_width = strlen("RESULT/VALUE");
 
-        sorted = newa(Member*, set_size(members));
+        Member **sorted = newa(Member*, set_size(members));
+        size_t k = 0;
 
         SET_FOREACH(m, members) {
                 if (argv[3] && !streq(argv[3], m->interface))
@@ -1120,7 +1120,7 @@ static int introspect(int argc, char **argv, void *userdata) {
                 sorted[k++] = m;
         }
 
-        if (result_width > 40)
+        if (result_width > 40 && arg_full <= 0)
                 result_width = 40;
 
         typesafe_qsort(sorted, k, member_compare_funcp);
@@ -1135,7 +1135,7 @@ static int introspect(int argc, char **argv, void *userdata) {
                        (int) result_width, "RESULT/VALUE",
                        "FLAGS");
 
-        for (j = 0; j < k; j++) {
+        for (size_t j = 0; j < k; j++) {
                 _cleanup_free_ char *ellipsized = NULL;
                 const char *rv;
                 bool is_interface;
@@ -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"
@@ -2550,6 +2551,9 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached();
                 }
 
+        if (arg_full < 0)
+                arg_full = terminal_is_dumb();
+
         return 1;
 }
 
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 5878a8187915456f11510d211bbcf845b2fef8da..0861b4b829110060afe9af9a7fb2a7b40ce48acf 100644 (file)
@@ -451,7 +451,9 @@ static int bpf_firewall_prepare_access_maps(
         }
 
         if (n_ipv4 > 0) {
+                char *name = strjoina("4_", u->id);
                 ipv4_map_fd = bpf_map_new(
+                                name,
                                 BPF_MAP_TYPE_LPM_TRIE,
                                 offsetof(struct bpf_lpm_trie_key, data) + sizeof(uint32_t),
                                 sizeof(uint64_t),
@@ -462,7 +464,9 @@ static int bpf_firewall_prepare_access_maps(
         }
 
         if (n_ipv6 > 0) {
+                char *name = strjoina("6_", u->id);
                 ipv6_map_fd = bpf_map_new(
+                                name,
                                 BPF_MAP_TYPE_LPM_TRIE,
                                 offsetof(struct bpf_lpm_trie_key, data) + sizeof(uint32_t)*4,
                                 sizeof(uint64_t),
@@ -500,7 +504,8 @@ static int bpf_firewall_prepare_accounting_maps(Unit *u, bool enabled, int *fd_i
 
         if (enabled) {
                 if (*fd_ingress < 0) {
-                        r = bpf_map_new(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
+                        char *name = strjoina("I_", u->id);
+                        r = bpf_map_new(name, BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
                         if (r < 0)
                                 return r;
 
@@ -508,8 +513,8 @@ static int bpf_firewall_prepare_accounting_maps(Unit *u, bool enabled, int *fd_i
                 }
 
                 if (*fd_egress < 0) {
-
-                        r = bpf_map_new(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
+                        char *name = strjoina("E_", u->id);
+                        r = bpf_map_new(name, BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(uint64_t), 2, 0);
                         if (r < 0)
                                 return r;
 
index 0be250af5cca8250639e4f7cc2b6a653cc60f90e..5f614ead04064212e15e544508b9b75a44dcf82b 100644 (file)
@@ -16,6 +16,7 @@
 #include "fileio.h"
 #include "filesystems.h"
 #include "log.h"
+#include "lsm-util.h"
 #include "manager.h"
 #include "mkdir.h"
 #include "nulstr-util.h"
@@ -91,41 +92,6 @@ static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) {
         return 0;
 }
 
-static int mac_bpf_use(void) {
-        _cleanup_free_ char *lsm_list = NULL;
-        static int cached_use = -1;
-        int r;
-
-        if (cached_use >= 0)
-                return cached_use;
-
-        cached_use = 0;
-
-        r = read_one_line_file("/sys/kernel/security/lsm", &lsm_list);
-        if (r < 0) {
-               if (r != -ENOENT)
-                       log_notice_errno(r, "bpf-lsm: Failed to read /sys/kernel/security/lsm, assuming bpf is unavailable: %m");
-               return 0;
-        }
-
-        for (const char *p = lsm_list;;) {
-                _cleanup_free_ char *word = NULL;
-
-                r = extract_first_word(&p, &word, ",", 0);
-                if (r == 0)
-                        return 0;
-                if (r == -ENOMEM)
-                        return log_oom();
-                if (r < 0) {
-                        log_notice_errno(r, "bpf-lsm: Failed to parse /sys/kernel/security/lsm, assuming bpf is unavailable: %m");
-                        return 0;
-                }
-
-                if (streq(word, "bpf"))
-                        return cached_use = 1;
-        }
-}
-
 bool lsm_bpf_supported(bool initialize) {
         _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL;
         static int supported = -1;
@@ -139,12 +105,11 @@ bool lsm_bpf_supported(bool initialize) {
         if (!cgroup_bpf_supported())
                 return (supported = false);
 
-        r = mac_bpf_use();
+        r = lsm_supported("bpf");
         if (r < 0) {
                 log_warning_errno(r, "bpf-lsm: Can't determine whether the BPF LSM module is used: %m");
                 return (supported = false);
         }
-
         if (r == 0) {
                 log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                "bpf-lsm: BPF LSM hook not enabled in the kernel, BPF LSM not supported");
diff --git a/src/core/bpf/meson.build b/src/core/bpf/meson.build
deleted file mode 100644 (file)
index ea9539b..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-if conf.get('BPF_FRAMEWORK') != 1
-        subdir_done()
-endif
-
-bpf_clang_flags = [
-        '-std=gnu11',
-        '-Wno-compare-distinct-pointer-types',
-        '-fno-stack-protector',
-        '-O2',
-        '-target',
-        'bpf',
-        '-g',
-        '-c',
-]
-
-bpf_gcc_flags = [
-        '-std=gnu11',
-        '-fno-stack-protector',
-        '-O2',
-        '-mkernel=5.2',
-        '-mcpu=v3',
-        '-mco-re',
-        '-gbtf',
-        '-c',
-]
-
-# Generate defines that are appropriate to tell the compiler what architecture
-# we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__.
-# This dictionary contains the exceptions where this doesn't work.
-#
-# C.f. https://mesonbuild.com/Reference-tables.html#cpu-families
-# and src/basic/missing_syscall_def.h.
-cpu_arch_defines = {
-        'ppc'     : ['-D__powerpc__'],
-        'ppc64'   : ['-D__powerpc64__', '-D_CALL_ELF=2'],
-        'riscv32' : ['-D__riscv', '-D__riscv_xlen=32'],
-        'riscv64' : ['-D__riscv', '-D__riscv_xlen=64'],
-        'x86'     : ['-D__i386__'],
-
-        # For arm, assume hardware fp is available.
-        'arm'     : ['-D__arm__', '-D__ARM_PCS_VFP'],
-}
-
-bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(),
-                                      ['-D__@0@__'.format(host_machine.cpu_family())])
-if bpf_compiler == 'gcc'
-        bpf_arch_flags += ['-m' + host_machine.endian() + '-endian']
-endif
-
-libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir')
-
-bpf_o_unstripped_cmd = []
-if bpf_compiler == 'clang'
-        bpf_o_unstripped_cmd += [
-                clang,
-                bpf_clang_flags,
-                bpf_arch_flags,
-        ]
-elif bpf_compiler == 'gcc'
-        bpf_o_unstripped_cmd += [
-                bpf_gcc,
-                bpf_gcc_flags,
-                bpf_arch_flags,
-        ]
-endif
-
-bpf_o_unstripped_cmd += ['-I.']
-
-if not meson.is_cross_build() and bpf_compiler == 'clang'
-        target_triplet_cmd = run_command('gcc', '-dumpmachine', check: false)
-        if target_triplet_cmd.returncode() == 0
-                target_triplet = target_triplet_cmd.stdout().strip()
-                bpf_o_unstripped_cmd += [
-                        '-isystem',
-                        '/usr/include/@0@'.format(target_triplet)
-                ]
-        endif
-endif
-
-bpf_o_unstripped_cmd += [
-        '-idirafter',
-        libbpf_include_dir,
-        '@INPUT@',
-        '-o',
-        '@OUTPUT@'
-]
-
-if bpftool_strip
-        bpf_o_cmd = [
-                bpftool,
-                'gen',
-                'object',
-                '@OUTPUT@',
-                '@INPUT@'
-        ]
-elif bpf_compiler == 'clang'
-        bpf_o_cmd = [
-                llvm_strip,
-                '-g',
-                '@INPUT@',
-                '-o',
-                '@OUTPUT@'
-        ]
-endif
-
-skel_h_cmd = [
-        bpftool,
-        'gen',
-        'skeleton',
-        '@INPUT@'
-]
index d5ef796e522082fa7264b96d1c1bf0e87fe5e284..4c413f4d29b141395afe610b00ace87de0829954 100644 (file)
@@ -49,7 +49,7 @@
 BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode);
-static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
+BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_proc, protect_proc, ProtectProc);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_proc_subset, proc_subset, ProcSubset);
@@ -1156,6 +1156,30 @@ static int bus_property_get_exec_dir_symlink(
         return sd_bus_message_close_container(reply);
 }
 
+static int property_get_image_policy(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        ImagePolicy **pp = ASSERT_PTR(userdata);
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        assert(bus);
+        assert(property);
+        assert(reply);
+
+        r = image_policy_to_string(*pp ?: &image_policy_service, /* simplify= */ true, &s);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_append(reply, "s", s);
+}
+
 const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1294,7 +1318,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
-        SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", bus_property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1324,6 +1348,9 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("ProtectHostname", "b", bus_property_get_bool, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("RootImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, root_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("MountImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, mount_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("ExtensionImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, extension_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
 
         /* Obsolete/redundant properties: */
         SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
@@ -1516,6 +1543,9 @@ int bus_set_transient_exec_command(
         unsigned n = 0;
         int r;
 
+        /* Drop Ex from the written setting. E.g. ExecStart=, not ExecStartEx=. */
+        const char *written_name = is_ex_prop ? strndupa(name, strlen(name) - 2) : name;
+
         r = sd_bus_message_enter_container(message, 'a', is_ex_prop ? "(sasas)" : "(sasb)");
         if (r < 0)
                 return r;
@@ -1597,31 +1627,32 @@ int bus_set_transient_exec_command(
                 if (!f)
                         return -ENOMEM;
 
-                fprintf(f, "%s=\n", name);
+                fprintf(f, "%s=\n", written_name);
 
                 LIST_FOREACH(command, c, *exec_command) {
                         _cleanup_free_ char *a = NULL, *exec_chars = NULL;
+                        UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS |
+                                (FLAGS_SET(c->flags, EXEC_COMMAND_NO_ENV_EXPAND) ? UNIT_ESCAPE_EXEC_SYNTAX : UNIT_ESCAPE_EXEC_SYNTAX_ENV);
 
                         exec_chars = exec_command_flags_to_exec_chars(c->flags);
                         if (!exec_chars)
                                 return -ENOMEM;
 
-                        a = unit_concat_strv(c->argv, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX);
+                        a = unit_concat_strv(c->argv, esc_flags);
                         if (!a)
                                 return -ENOMEM;
 
                         if (streq_ptr(c->path, c->argv ? c->argv[0] : NULL))
-                                fprintf(f, "%s=%s%s\n", name, exec_chars, a);
+                                fprintf(f, "%s=%s%s\n", written_name, exec_chars, a);
                         else {
                                 _cleanup_free_ char *t = NULL;
                                 const char *p;
 
-                                p = unit_escape_setting(c->path,
-                                                        UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX, &t);
+                                p = unit_escape_setting(c->path, esc_flags, &t);
                                 if (!p)
                                         return -ENOMEM;
 
-                                fprintf(f, "%s=%s@%s %s\n", name, exec_chars, p, a);
+                                fprintf(f, "%s=%s@%s %s\n", written_name, exec_chars, p, a);
                         }
                 }
 
@@ -1629,7 +1660,7 @@ int bus_set_transient_exec_command(
                 if (r < 0)
                         return r;
 
-                unit_write_setting(u, flags, name, buf);
+                unit_write_setting(u, flags, written_name, buf);
         }
 
         return 1;
@@ -1668,7 +1699,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_home, ProtectHome, protect_home_fr
 static BUS_DEFINE_SET_TRANSIENT_PARSE(keyring_mode, ExecKeyringMode, exec_keyring_mode_from_string);
 static BUS_DEFINE_SET_TRANSIENT_PARSE(protect_proc, ProtectProc, protect_proc_from_string);
 static BUS_DEFINE_SET_TRANSIENT_PARSE(proc_subset, ProcSubset, proc_subset_from_string);
-static BUS_DEFINE_SET_TRANSIENT_PARSE(preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string);
+BUS_DEFINE_SET_TRANSIENT_PARSE(exec_preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string);
 static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality);
 static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check);
 static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(capability, "t", uint64_t, uint64_t, "%" PRIu64, capability_set_to_string);
@@ -2020,7 +2051,7 @@ int bus_exec_context_set_transient_property(
                 return bus_set_transient_proc_subset(u, name, &c->proc_subset, message, flags, error);
 
         if (streq(name, "RuntimeDirectoryPreserve"))
-                return bus_set_transient_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error);
+                return bus_set_transient_exec_preserve_mode(u, name, &c->runtime_directory_preserve_mode, message, flags, error);
 
         if (streq(name, "UMask"))
                 return bus_set_transient_mode_t(u, name, &c->umask, message, flags, error);
@@ -3900,6 +3931,40 @@ int bus_exec_context_set_transient_property(
 
                 return 1;
 
+        } else if (STR_IN_SET(name, "RootImagePolicy", "MountImagePolicy", "ExtensionImagePolicy")) {
+                _cleanup_(image_policy_freep) ImagePolicy *p = NULL;
+                const char *s;
+
+                r = sd_bus_message_read(message, "s", &s);
+                if (r < 0)
+                        return r;
+
+                r = image_policy_from_string(s, &p);
+                if (r < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse image policy string: %s", s);
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+                        _cleanup_free_ char *t = NULL;
+                        ImagePolicy **pp =
+                                streq(name, "RootImagePolicy")  ? &c->root_image_policy :
+                                streq(name, "MountImagePolicy") ? &c->mount_image_policy :
+                                                                  &c->extension_image_policy;
+
+                        r = image_policy_to_string(p, /* simplify= */ true, &t);
+                        if (r < 0)
+                                return r;
+
+                        image_policy_free(*pp);
+                        *pp = TAKE_PTR(p);
+
+                        unit_write_settingf(
+                                        u, flags, name,
+                                        "%s=%s",
+                                        name,
+                                        t); /* no escaping necessary */
+                }
+
+                return 1;
         }
 
         return 0;
index c53834140e5fb82b294c5d8669b35586ff9372fe..5926bdb4b139a99e52dad887292bddcf0348181a 100644 (file)
@@ -28,6 +28,8 @@ int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *inte
 int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
 int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
 int bus_property_get_exec_ex_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
+int bus_property_get_exec_preserve_mode(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error);
 
 int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
 int bus_set_transient_exec_command(Unit *u, const char *name, ExecCommand **exec_command, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+int bus_set_transient_exec_preserve_mode(Unit *u, const char *name, ExecPreserveMode *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
index ca65bbf106cd98e9f6da9a3c26e333fca69ef408..2feec0bd7df3ba018d069e86e4913c12ac5c7380 100644 (file)
@@ -11,7 +11,7 @@
 #include "bus-common-errors.h"
 #include "bus-get-properties.h"
 #include "bus-log-control-api.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "data-fd-util.h"
 #include "dbus-cgroup.h"
 #include "dbus-execute.h"
@@ -1768,7 +1768,7 @@ static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_er
                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
                                                  "Path to init binary '%s' not absolute.", init);
 
-                r = chase_symlinks_and_access(init, root, CHASE_PREFIX_ROOT, X_OK, NULL);
+                r = chase_and_access(init, root, CHASE_PREFIX_ROOT, X_OK, NULL);
                 if (r == -EACCES)
                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
                                                  "Init binary %s is not executable.", init);
@@ -2094,7 +2094,7 @@ static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata,
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         Manager *m = ASSERT_PTR(userdata);
         UnitFileList *item;
-        Hashmap *h;
+        _cleanup_(hashmap_freep) Hashmap *h = NULL;
         int r;
 
         assert(message);
@@ -2109,36 +2109,30 @@ static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata,
         if (r < 0)
                 return r;
 
-        h = hashmap_new(&string_hash_ops);
+        h = hashmap_new(&unit_file_list_hash_ops_free);
         if (!h)
                 return -ENOMEM;
 
         r = unit_file_get_list(m->runtime_scope, NULL, h, states, patterns);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = sd_bus_message_open_container(reply, 'a', "(ss)");
         if (r < 0)
-                goto fail;
+                return r;
 
         HASHMAP_FOREACH(item, h) {
 
                 r = sd_bus_message_append(reply, "(ss)", item->path, unit_file_state_to_string(item->state));
                 if (r < 0)
-                        goto fail;
+                        return r;
         }
 
-        unit_file_list_free(h);
-
         r = sd_bus_message_close_container(reply);
         if (r < 0)
                 return r;
 
         return sd_bus_send(NULL, reply, NULL);
-
-fail:
-        unit_file_list_free(h);
-        return r;
 }
 
 static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -2234,7 +2228,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 +2240,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 +2702,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 +2719,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) {
@@ -2840,6 +2812,10 @@ static int method_set_show_status(sd_bus_message *message, void *userdata, sd_bu
         return sd_bus_reply_method_return(message, NULL);
 }
 
+static int method_dump_unit_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return method_generic_unit_operation(message, userdata, error, bus_service_method_dump_file_descriptor_store, 0);
+}
+
 const sd_bus_vtable bus_manager_vtable[] = {
         SD_BUS_VTABLE_START(0),
 
@@ -3385,6 +3361,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
                                 SD_BUS_RESULT("a(us)", users),
                                 method_get_dynamic_users,
                                 SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("DumpUnitFileDescriptorStore",
+                                SD_BUS_ARGS("s", name),
+                                SD_BUS_RESULT("a(suuutuusu)", entries),
+                                method_dump_unit_descriptor_store,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
 
         SD_BUS_SIGNAL_WITH_ARGS("UnitNew",
                                 SD_BUS_ARGS("s", id, "o", unit),
index 079cb9b92d2bef92ea6776e6edf2c9ea9f890aa9..e368fcc6ef782b65e9d959886344a2dd256d5681 100644 (file)
@@ -4,6 +4,7 @@
 
 #include "alloc-util.h"
 #include "async.h"
+#include "bus-common-errors.h"
 #include "bus-get-properties.h"
 #include "dbus-cgroup.h"
 #include "dbus-execute.h"
@@ -16,6 +17,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "locale-util.h"
+#include "missing_fcntl.h"
 #include "mount-util.h"
 #include "open-file.h"
 #include "parse-util.h"
@@ -31,8 +33,9 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exit_type, service_exit_type, ServiceExitType);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart);
-static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
+static BUS_DEFINE_PROPERTY_GET2(property_get_notify_access, "s", Service, service_get_notify_access, notify_access_to_string);
+static BUS_DEFINE_PROPERTY_GET(property_get_restart_usec_next, "t", Service, service_restart_usec_next);
 static BUS_DEFINE_PROPERTY_GET(property_get_timeout_abort_usec, "t", Service, service_timeout_abort_usec);
 static BUS_DEFINE_PROPERTY_GET(property_get_watchdog_usec, "t", Service, service_get_watchdog_usec);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode);
@@ -194,15 +197,23 @@ static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_
 
         propagate_directory = strjoina("/run/systemd/propagate/", u->id);
         if (is_image)
-                r = mount_image_in_namespace(unit_pid,
-                                             propagate_directory,
-                                             "/run/systemd/incoming/",
-                                             src, dest, read_only, make_file_or_directory, options);
+                r = mount_image_in_namespace(
+                                unit_pid,
+                                propagate_directory,
+                                "/run/systemd/incoming/",
+                                src, dest,
+                                read_only,
+                                make_file_or_directory,
+                                options,
+                                c->mount_image_policy ?: &image_policy_service);
         else
-                r = bind_mount_in_namespace(unit_pid,
-                                            propagate_directory,
-                                            "/run/systemd/incoming/",
-                                            src, dest, read_only, make_file_or_directory);
+                r = bind_mount_in_namespace(
+                                unit_pid,
+                                propagate_directory,
+                                "/run/systemd/incoming/",
+                                src, dest,
+                                read_only,
+                                make_file_or_directory);
         if (r < 0)
                 return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);
 
@@ -217,14 +228,106 @@ int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_b
         return bus_service_method_mount(message, userdata, error, true);
 }
 
+int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        Service *s = ASSERT_PTR(userdata);
+        int r;
+
+        assert(message);
+
+        r = mac_selinux_unit_access_check(UNIT(s), message, "status", error);
+        if (r < 0)
+                return r;
+
+        if (s->n_fd_store_max == 0 && s->n_fd_store == 0)
+                return sd_bus_error_setf(error, BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED, "File descriptor store not enabled for %s.", UNIT(s)->id);
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(suuutuusu)");
+        if (r < 0)
+                return r;
+
+        LIST_FOREACH(fd_store, i, s->fd_store) {
+                _cleanup_free_ char *path = NULL;
+                struct stat st;
+                int flags;
+
+                if (fstat(i->fd, &st) < 0) {
+                        log_debug_errno(errno, "Failed to stat() file descriptor entry '%s', skipping.", strna(i->fdname));
+                        continue;
+                }
+
+                flags = fcntl(i->fd, F_GETFL);
+                if (flags < 0) {
+                        log_debug_errno(errno, "Failed to issue F_GETFL on file descriptor entry '%s', skipping.", strna(i->fdname));
+                        continue;
+                }
+
+                /* glibc implies O_LARGEFILE everywhere on 64bit off_t builds, but forgets to hide it away on
+                 * F_GETFL, but provides no definition to check for that. Let's mask the flag away manually,
+                 * to not confuse clients. */
+                flags &= ~RAW_O_LARGEFILE;
+
+                (void) fd_get_path(i->fd, &path);
+
+                r = sd_bus_message_append(
+                                reply,
+                                "(suuutuusu)",
+                                i->fdname,
+                                (uint32_t) st.st_mode,
+                                (uint32_t) major(st.st_dev), (uint32_t) minor(st.st_dev),
+                                (uint64_t) st.st_ino,
+                                (uint32_t) major(st.st_rdev), (uint32_t) minor(st.st_rdev),
+                                path,
+                                (uint32_t) flags);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_send(NULL, reply, NULL);
+}
+
+#if __SIZEOF_SIZE_T__ == 8
+static int property_get_size_as_uint32(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        size_t *value = ASSERT_PTR(userdata);
+        uint32_t sz = *value >= UINT32_MAX ? UINT32_MAX : (uint32_t) *value;
+
+        /* Returns a size_t as a D-Bus "u" type, i.e. as 32bit value, even if size_t is 64bit. We'll saturate if it doesn't fit. */
+
+        return sd_bus_message_append_basic(reply, 'u', &sz);
+}
+#elif __SIZEOF_SIZE_T__ == 4
+#define property_get_size_as_uint32 ((sd_bus_property_get_t) NULL)
+#else
+#error "Unexpected size of size_t"
+#endif
+
 const sd_bus_vtable bus_service_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("ExitType", "s", property_get_exit_type, offsetof(Service, exit_type), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST),
-        SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("RestartSteps", "u", bus_property_get_unsigned, offsetof(Service, restart_steps), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("RestartUSecMax", "t", bus_property_get_usec, offsetof(Service, restart_usec_max), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("RestartUSecNext", "t", property_get_restart_usec_next, 0, 0),
         SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("TimeoutAbortUSec", "t", property_get_timeout_abort_usec, 0, 0),
@@ -245,7 +348,8 @@ const sd_bus_vtable bus_service_vtable[] = {
         SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Service, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST),
-        SD_BUS_PROPERTY("NFileDescriptorStore", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store), 0),
+        SD_BUS_PROPERTY("NFileDescriptorStore", "u", property_get_size_as_uint32, offsetof(Service, n_fd_store), 0),
+        SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0),
         SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -288,6 +392,12 @@ const sd_bus_vtable bus_service_vtable[] = {
                                  bus_service_method_mount_image,
                                  SD_BUS_VTABLE_UNPRIVILEGED),
 
+        SD_BUS_METHOD_WITH_ARGS("DumpFileDescriptorStore",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_ARGS("a(suuutuusu)", entries),
+                                bus_service_method_dump_file_descriptor_store,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+
         /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
         SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
         SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
@@ -448,6 +558,12 @@ static int bus_service_set_transient_property(
         if (streq(name, "RestartUSec"))
                 return bus_set_transient_usec(u, name, &s->restart_usec, message, flags, error);
 
+        if (streq(name, "RestartSteps"))
+                return bus_set_transient_unsigned(u, name, &s->restart_steps, message, flags, error);
+
+        if (streq(name, "RestartUSecMax"))
+                return bus_set_transient_usec(u, name, &s->restart_usec_max, message, flags, error);
+
         if (streq(name, "TimeoutStartUSec")) {
                 r = bus_set_transient_usec(u, name, &s->timeout_start_usec, message, flags, error);
                 if (r >= 0 && !UNIT_WRITE_FLAGS_NOOP(flags))
@@ -484,6 +600,9 @@ static int bus_service_set_transient_property(
         if (streq(name, "FileDescriptorStoreMax"))
                 return bus_set_transient_unsigned(u, name, &s->n_fd_store_max, message, flags, error);
 
+        if (streq(name, "FileDescriptorStorePreserve"))
+                return bus_set_transient_exec_preserve_mode(u, name, &s->fd_store_preserve_mode, message, flags, error);
+
         if (streq(name, "NotifyAccess"))
                 return bus_set_transient_notify_access(u, name, &s->notify_access, message, flags, error);
 
@@ -551,7 +670,8 @@ static int bus_service_set_transient_property(
                 return bus_set_transient_exit_status(u, name, &s->success_status, message, flags, error);
 
         ci = service_exec_command_from_string(name);
-        ci = (ci >= 0) ? ci : service_exec_ex_command_from_string(name);
+        if (ci < 0)
+                ci = service_exec_ex_command_from_string(name);
         if (ci >= 0)
                 return bus_set_transient_exec_command(u, name, &s->exec_command[ci], message, flags, error);
 
@@ -639,7 +759,7 @@ int bus_service_set_property(
                 return r;
 
         if (u->transient && u->load_state == UNIT_STUB) {
-                /* This is a transient unit, let's load a little more */
+                /* This is a transient unit, let's allow a little more */
 
                 r = bus_service_set_transient_property(s, name, message, flags, error);
                 if (r != 0)
index 9a054658025b9d5d3368530d68ca5e8fa882c1fd..aea6cf77f30a2bd2fe46c42493a3c79705709a81 100644 (file)
@@ -12,3 +12,4 @@ int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitW
 int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_service_commit_properties(Unit *u);
+int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error);
index a9e63b0678821f6c461a73c1376b5320d9106f6f..dd36cae860d8a8763c9b1f88657a48fc176eb7ef 100644 (file)
@@ -90,6 +90,12 @@ static int property_get_can_clean(
                         return r;
         }
 
+        if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) {
+                r = sd_bus_message_append(reply, "s", "fdstore");
+                if (r < 0)
+                        return r;
+        }
+
         return sd_bus_message_close_container(reply);
 }
 
@@ -229,9 +235,7 @@ static int property_get_unit_file_preset(
 
         r = unit_get_unit_file_preset(u);
 
-        return sd_bus_message_append(reply, "s",
-                                     r < 0 ? NULL:
-                                     r > 0 ? "enabled" : "disabled");
+        return sd_bus_message_append(reply, "s", preset_action_past_tense_to_string(r));
 }
 
 static int property_get_job(
@@ -696,6 +700,7 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error
                 return r;
 
         for (;;) {
+                ExecCleanMask m;
                 const char *i;
 
                 r = sd_bus_message_read(message, "s", &i);
@@ -704,17 +709,11 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error
                 if (r == 0)
                         break;
 
-                if (streq(i, "all"))
-                        mask |= EXEC_CLEAN_ALL;
-                else {
-                        ExecDirectoryType t;
-
-                        t = exec_resource_type_from_string(i);
-                        if (t < 0)
-                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
+                m = exec_clean_mask_from_string(i);
+                if (m < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
 
-                        mask |= 1U << t;
-                }
+                mask |= m;
         }
 
         r = sd_bus_message_exit_container(message);
@@ -2407,14 +2406,13 @@ int bus_unit_set_properties(
         assert(u);
         assert(message);
 
-        /* We iterate through the array twice. First run we just check
-         * if all passed data is valid, second run actually applies
-         * it. This is to implement transaction-like behaviour without
-         * actually providing full transactions. */
+        /* We iterate through the array twice. First run just checks if all passed data is valid, second run
+         * actually applies it. This implements transaction-like behaviour without actually providing full
+         * transactions. */
 
         r = sd_bus_message_enter_container(message, 'a', "(sv)");
         if (r < 0)
-                return r;
+                goto error;
 
         for (;;) {
                 const char *name;
@@ -2422,7 +2420,7 @@ int bus_unit_set_properties(
 
                 r = sd_bus_message_enter_container(message, 'r', "sv");
                 if (r < 0)
-                        return r;
+                        goto error;
                 if (r == 0) {
                         if (for_real || UNIT_WRITE_FLAGS_NOOP(flags))
                                 break;
@@ -2430,7 +2428,7 @@ int bus_unit_set_properties(
                         /* Reached EOF. Let's try again, and this time for realz... */
                         r = sd_bus_message_rewind(message, false);
                         if (r < 0)
-                                return r;
+                                goto error;
 
                         for_real = true;
                         continue;
@@ -2438,11 +2436,11 @@ int bus_unit_set_properties(
 
                 r = sd_bus_message_read(message, "s", &name);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 r = sd_bus_message_enter_container(message, 'v', NULL);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 /* If not for real, then mask out the two target flags */
                 f = for_real ? flags : (flags & ~(UNIT_RUNTIME|UNIT_PERSISTENT));
@@ -2456,7 +2454,7 @@ int bus_unit_set_properties(
                 if (r == 0)
                         r = bus_unit_set_live_property(u, name, message, f, error);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 if (r == 0)
                         return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY,
@@ -2464,23 +2462,32 @@ int bus_unit_set_properties(
 
                 r = sd_bus_message_exit_container(message);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 r = sd_bus_message_exit_container(message);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 n += for_real;
         }
 
         r = sd_bus_message_exit_container(message);
         if (r < 0)
-                return r;
+                goto error;
 
         if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties)
                 UNIT_VTABLE(u)->bus_commit_properties(u);
 
         return n;
+
+ error:
+        /* Pretty much any of the calls above can fail if the message is not formed properly
+         * or if it has unexpected contents. Fill in a more informative error message here. */
+        if (sd_bus_error_is_set(error))
+                return r;
+        return sd_bus_error_set_errnof(error, r,
+                                       r == -ENXIO ? "Failed to set unit properties: Unexpected message contents"
+                                                   : "Failed to set unit properties: %m");
 }
 
 int bus_unit_validate_load_state(Unit *u, sd_bus_error *error) {
index 4f6ecf4d7f9a1e537caf0f4a013de961a25c59ec..1449867e357225644abc0b9f6385e46033591907 100644 (file)
@@ -55,24 +55,31 @@ static int device_by_path(Manager *m, const char *path, Unit **ret) {
 
 static void device_unset_sysfs(Device *d) {
         Hashmap *devices;
-        Device *first;
 
         assert(d);
 
         if (!d->sysfs)
                 return;
 
-        /* Remove this unit from the chain of devices which share the
-         * same sysfs path. */
+        /* Remove this unit from the chain of devices which share the same sysfs path. */
+
         devices = UNIT(d)->manager->devices_by_sysfs;
-        first = hashmap_get(devices, d->sysfs);
-        LIST_REMOVE(same_sysfs, first, d);
 
-        if (first)
-                hashmap_remove_and_replace(devices, d->sysfs, first->sysfs, first);
+        if (d->same_sysfs_prev)
+                /* If this is not the first unit, then simply remove this unit. */
+                d->same_sysfs_prev->same_sysfs_next = d->same_sysfs_next;
+        else if (d->same_sysfs_next)
+                /* If this is the first unit, replace with the next unit. */
+                assert_se(hashmap_replace(devices, d->same_sysfs_next->sysfs, d->same_sysfs_next) >= 0);
         else
+                /* Otherwise, remove the entry. */
                 hashmap_remove(devices, d->sysfs);
 
+        if (d->same_sysfs_next)
+                d->same_sysfs_next->same_sysfs_prev = d->same_sysfs_prev;
+
+        d->same_sysfs_prev = d->same_sysfs_next = NULL;
+
         d->sysfs = mfree(d->sysfs);
 }
 
index 3824ae747fdfe28acf19fabb8d854ab4308f16a1..19cf3faca6c6ef664961bccea69e658b042c2a94 100644 (file)
@@ -739,18 +739,28 @@ int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) {
         return r;
 }
 
-int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group) {
+int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicCreds **ret) {
+        _cleanup_(dynamic_creds_unrefp) DynamicCreds *creds = NULL;
         bool acquired = false;
         int r;
 
-        assert(creds);
         assert(m);
+        assert(ret);
+
+        if (!user && !group) {
+                *ret = NULL;
+                return 0;
+        }
+
+        creds = new0(DynamicCreds, 1);
+        if (!creds)
+                return -ENOMEM;
 
         /* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some
          * services use different user and groups. For cases like that there's DynamicCreds containing a pair of user
          * and group. This call allocates a pair. */
 
-        if (!creds->user && user) {
+        if (user) {
                 r = dynamic_user_acquire(m, user, &creds->user);
                 if (r < 0)
                         return r;
@@ -758,20 +768,19 @@ int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, con
                 acquired = true;
         }
 
-        if (!creds->group) {
-
-                if (creds->user && (!group || streq_ptr(user, group)))
-                        creds->group = dynamic_user_ref(creds->user);
-                else if (group) {
-                        r = dynamic_user_acquire(m, group, &creds->group);
-                        if (r < 0) {
-                                if (acquired)
-                                        creds->user = dynamic_user_unref(creds->user);
-                                return r;
-                        }
+        if (creds->user && (!group || streq_ptr(user, group)))
+                creds->group = dynamic_user_ref(creds->user);
+        else if (group) {
+                r = dynamic_user_acquire(m, group, &creds->group);
+                if (r < 0) {
+                        if (acquired)
+                                creds->user = dynamic_user_unref(creds->user);
+                        return r;
                 }
         }
 
+        *ret = TAKE_PTR(creds);
+
         return 0;
 }
 
@@ -803,16 +812,22 @@ int dynamic_creds_realize(DynamicCreds *creds, char **suggested_paths, uid_t *ui
         return 0;
 }
 
-void dynamic_creds_unref(DynamicCreds *creds) {
-        assert(creds);
+DynamicCreds* dynamic_creds_unref(DynamicCreds *creds) {
+        if (!creds)
+                return NULL;
 
         creds->user = dynamic_user_unref(creds->user);
         creds->group = dynamic_user_unref(creds->group);
+
+        return mfree(creds);
 }
 
-void dynamic_creds_destroy(DynamicCreds *creds) {
-        assert(creds);
+DynamicCreds* dynamic_creds_destroy(DynamicCreds *creds) {
+        if (!creds)
+                return NULL;
 
         creds->user = dynamic_user_destroy(creds->user);
         creds->group = dynamic_user_destroy(creds->group);
+
+        return mfree(creds);
 }
index 847ef475ca555d300a8a00a70a240d3ebdf430e6..6539d17571b2112afc2ca3e587a1caefdf4b3396 100644 (file)
@@ -33,8 +33,11 @@ int dynamic_user_current(DynamicUser *d, uid_t *ret);
 int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret);
 int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret);
 
-int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group);
+int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicCreds **ret);
 int dynamic_creds_realize(DynamicCreds *creds, char **suggested_paths, uid_t *uid, gid_t *gid);
 
-void dynamic_creds_unref(DynamicCreds *creds);
-void dynamic_creds_destroy(DynamicCreds *creds);
+DynamicCreds *dynamic_creds_unref(DynamicCreds *creds);
+DynamicCreds *dynamic_creds_destroy(DynamicCreds *creds);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DynamicCreds*, dynamic_creds_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DynamicCreds*, dynamic_creds_destroy);
index 093d1ad5b474514cd96d69b180138ab3acf5e3b3..60f7a6439c1e47ff304871e2bef121d18d63fae7 100644 (file)
@@ -46,7 +46,7 @@
 #include "cap-list.h"
 #include "capability-util.h"
 #include "cgroup-setup.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "chown-recursive.h"
 #include "constants.h"
 #include "cpu-set-util.h"
@@ -80,6 +80,7 @@
 #include "namespace.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "proc-cmdline.h"
 #include "process-util.h"
 #include "psi-util.h"
 #include "random-util.h"
@@ -201,6 +202,66 @@ static const char *exec_context_tty_path(const ExecContext *context) {
         return "/dev/console";
 }
 
+static int exec_context_tty_size(const ExecContext *context, unsigned *ret_rows, unsigned *ret_cols) {
+        _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
+        unsigned rows, cols;
+        const char *tty;
+        int r;
+
+        assert(context);
+        assert(ret_rows);
+        assert(ret_cols);
+
+        rows = context->tty_rows;
+        cols = context->tty_cols;
+
+        tty = exec_context_tty_path(context);
+        if (!tty || (rows != UINT_MAX && cols != UINT_MAX)) {
+                *ret_rows = rows;
+                *ret_cols = cols;
+                return 0;
+        }
+
+        tty = skip_dev_prefix(tty);
+        if (!in_charset(tty, ALPHANUMERICAL)) {
+                log_debug("%s contains non-alphanumeric characters, ignoring", tty);
+                *ret_rows = rows;
+                *ret_cols = cols;
+                return 0;
+        }
+
+        rowskey = strjoin("systemd.tty.rows.", tty);
+        if (!rowskey)
+                return -ENOMEM;
+
+        colskey = strjoin("systemd.tty.columns.", tty);
+        if (!colskey)
+                return -ENOMEM;
+
+        r = proc_cmdline_get_key_many(/* flags = */ 0,
+                                      rowskey, &rowsvalue,
+                                      colskey, &colsvalue);
+        if (r < 0)
+                log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline, ignoring: %m", tty);
+
+        if (rows == UINT_MAX && rowsvalue) {
+                r = safe_atou(rowsvalue, &rows);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse %s=%s, ignoring: %m", rowskey, rowsvalue);
+        }
+
+        if (cols == UINT_MAX && colsvalue) {
+                r = safe_atou(colsvalue, &cols);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse %s=%s, ignoring: %m", colskey, colsvalue);
+        }
+
+        *ret_rows = rows;
+        *ret_cols = cols;
+
+        return 0;
+}
+
 static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) {
         const char *path;
 
@@ -222,8 +283,12 @@ static void exec_context_tty_reset(const ExecContext *context, const ExecParamet
                         (void) reset_terminal(path);
         }
 
-        if (p && p->stdin_fd >= 0)
-                (void) terminal_set_size_fd(p->stdin_fd, path, context->tty_rows, context->tty_cols);
+        if (p && p->stdin_fd >= 0) {
+                unsigned rows = context->tty_rows, cols = context->tty_cols;
+
+                (void) exec_context_tty_size(context, &rows, &cols);
+                (void) terminal_set_size_fd(p->stdin_fd, path, rows, cols);
+        }
 
         if (context->tty_vt_disallocate && path)
                 (void) vt_disallocate(path);
@@ -481,9 +546,12 @@ static int setup_input(
 
                 /* Try to make this the controlling tty, if it is a tty, and reset it */
                 if (isatty(STDIN_FILENO)) {
+                        unsigned rows = context->tty_rows, cols = context->tty_cols;
+
+                        (void) exec_context_tty_size(context, &rows, &cols);
                         (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE);
                         (void) reset_terminal_fd(STDIN_FILENO, true);
-                        (void) terminal_set_size_fd(STDIN_FILENO, NULL, context->tty_rows, context->tty_cols);
+                        (void) terminal_set_size_fd(STDIN_FILENO, NULL, rows, cols);
                 }
 
                 return STDIN_FILENO;
@@ -499,6 +567,7 @@ static int setup_input(
         case EXEC_INPUT_TTY:
         case EXEC_INPUT_TTY_FORCE:
         case EXEC_INPUT_TTY_FAIL: {
+                unsigned rows, cols;
                 int fd;
 
                 fd = acquire_terminal(exec_context_tty_path(context),
@@ -509,7 +578,11 @@ static int setup_input(
                 if (fd < 0)
                         return fd;
 
-                r = terminal_set_size_fd(fd, exec_context_tty_path(context), context->tty_rows, context->tty_cols);
+                r = exec_context_tty_size(context, &rows, &cols);
+                if (r < 0)
+                        return r;
+
+                r = terminal_set_size_fd(fd, exec_context_tty_path(context), rows, cols);
                 if (r < 0)
                         return r;
 
@@ -772,6 +845,7 @@ static int setup_confirm_stdio(
                 int *ret_saved_stdout) {
 
         _cleanup_close_ int fd = -EBADF, saved_stdin = -EBADF, saved_stdout = -EBADF;
+        unsigned rows, cols;
         int r;
 
         assert(ret_saved_stdin);
@@ -797,7 +871,11 @@ static int setup_confirm_stdio(
         if (r < 0)
                 return r;
 
-        r = terminal_set_size_fd(fd, vc, context->tty_rows, context->tty_cols);
+        r = exec_context_tty_size(context, &rows, &cols);
+        if (r < 0)
+                return r;
+
+        r = terminal_set_size_fd(fd, vc, rows, cols);
         if (r < 0)
                 return r;
 
@@ -1837,6 +1915,7 @@ static int build_environment(
         _cleanup_strv_free_ char **our_env = NULL;
         size_t n_env = 0;
         char *x;
+        int r;
 
         assert(u);
         assert(c);
@@ -1927,6 +2006,7 @@ static int build_environment(
         }
 
         if (exec_context_needs_term(c)) {
+                _cleanup_free_ char *cmdline = NULL;
                 const char *tty_path, *term = NULL;
 
                 tty_path = exec_context_tty_path(c);
@@ -1937,6 +2017,19 @@ static int build_environment(
 
                 if (path_equal_ptr(tty_path, "/dev/console") && getppid() == 1)
                         term = getenv("TERM");
+                else if (tty_path && in_charset(skip_dev_prefix(tty_path), ALPHANUMERICAL)) {
+                        _cleanup_free_ char *key = NULL;
+
+                        key = strjoin("systemd.tty.term.", skip_dev_prefix(tty_path));
+                        if (!key)
+                                return -ENOMEM;
+
+                        r = proc_cmdline_get_key(key, 0, &cmdline);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to read %s from kernel cmdline, ignoring: %m", key);
+                        else if (r > 0)
+                                term = cmdline;
+                }
 
                 if (!term)
                         term = default_term_for_tty(tty_path);
@@ -2117,7 +2210,7 @@ bool exec_needs_mount_namespace(
         if (!IN_SET(context->mount_propagation_flag, 0, MS_SHARED))
                 return true;
 
-        if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir))
+        if (context->private_tmp && runtime && runtime->shared && (runtime->shared->tmp_dir || runtime->shared->var_tmp_dir))
                 return true;
 
         if (context->private_devices ||
@@ -2319,6 +2412,8 @@ static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) {
 }
 
 static bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type) {
+        assert(context);
+
         if (!context->dynamic_user)
                 return false;
 
@@ -2509,7 +2604,7 @@ static int setup_exec_directory(
                                  * since they all support the private/ symlink logic at least in some
                                  * configurations, see above. */
 
-                                r = chase_symlinks(target, NULL, 0, &target_resolved, NULL);
+                                r = chase(target, NULL, 0, &target_resolved, NULL);
                                 if (r < 0)
                                         goto fail;
 
@@ -2520,7 +2615,7 @@ static int setup_exec_directory(
                                 }
 
                                 /* /var/lib or friends may be symlinks. So, let's chase them also. */
-                                r = chase_symlinks(q, NULL, CHASE_NONEXISTENT, &q_resolved, NULL);
+                                r = chase(q, NULL, CHASE_NONEXISTENT, &q_resolved, NULL);
                                 if (r < 0)
                                         goto fail;
 
@@ -3351,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;
 
@@ -3361,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])
@@ -3383,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,
@@ -3423,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(). */
@@ -3445,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))
@@ -3458,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,
@@ -3477,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
@@ -3609,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;
 
@@ -3623,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
@@ -3649,16 +3730,16 @@ static int apply_mount_namespace(
                  * that is sticky, and that's the one we want to use here.
                  * This does not apply when we are using /run/systemd/empty as fallback. */
 
-                if (context->private_tmp && runtime) {
-                        if (streq_ptr(runtime->tmp_dir, RUN_SYSTEMD_EMPTY))
-                                tmp_dir = runtime->tmp_dir;
-                        else if (runtime->tmp_dir)
-                                tmp_dir = strjoina(runtime->tmp_dir, "/tmp");
+                if (context->private_tmp && runtime && runtime->shared) {
+                        if (streq_ptr(runtime->shared->tmp_dir, RUN_SYSTEMD_EMPTY))
+                                tmp_dir = runtime->shared->tmp_dir;
+                        else if (runtime->shared->tmp_dir)
+                                tmp_dir = strjoina(runtime->shared->tmp_dir, "/tmp");
 
-                        if (streq_ptr(runtime->var_tmp_dir, RUN_SYSTEMD_EMPTY))
-                                var_tmp_dir = runtime->var_tmp_dir;
-                        else if (runtime->var_tmp_dir)
-                                var_tmp_dir = strjoina(runtime->var_tmp_dir, "/tmp");
+                        if (streq_ptr(runtime->shared->var_tmp_dir, RUN_SYSTEMD_EMPTY))
+                                var_tmp_dir = runtime->shared->var_tmp_dir;
+                        else if (runtime->shared->var_tmp_dir)
+                                var_tmp_dir = strjoina(runtime->shared->var_tmp_dir, "/tmp");
                 }
 
                 ns_info = (NamespaceInfo) {
@@ -3698,66 +3779,63 @@ 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, root_image, context->root_image_options,
-                            &ns_info, read_write_paths,
-                            needs_sandboxing ? context->read_only_paths : NULL,
-                            needs_sandboxing ? context->inaccessible_paths : NULL,
-                            needs_sandboxing ? context->exec_paths : NULL,
-                            needs_sandboxing ? context->no_exec_paths : NULL,
-                            empty_directories,
-                            symlinks,
-                            bind_mounts,
-                            n_bind_mounts,
-                            context->temporary_filesystems,
-                            context->n_temporary_filesystems,
-                            context->mount_images,
-                            context->n_mount_images,
-                            tmp_dir,
-                            var_tmp_dir,
-                            creds_path,
-                            context->log_namespace,
-                            context->mount_propagation_flag,
-                            context->root_hash, context->root_hash_size, context->root_hash_path,
-                            context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
-                            context->root_verity,
-                            context->extension_images,
-                            context->n_extension_images,
-                            context->extension_directories,
-                            propagate_dir,
-                            incoming_dir,
-                            extension_dir,
-                            root_dir || root_image ? params->notify_socket : NULL,
-                            error_path);
+        r = setup_namespace(
+                        root_dir,
+                        root_image,
+                        context->root_image_options,
+                        context->root_image_policy ?: &image_policy_service,
+                        &ns_info,
+                        read_write_paths,
+                        needs_sandboxing ? context->read_only_paths : NULL,
+                        needs_sandboxing ? context->inaccessible_paths : NULL,
+                        needs_sandboxing ? context->exec_paths : NULL,
+                        needs_sandboxing ? context->no_exec_paths : NULL,
+                        empty_directories,
+                        symlinks,
+                        bind_mounts,
+                        n_bind_mounts,
+                        context->temporary_filesystems,
+                        context->n_temporary_filesystems,
+                        context->mount_images,
+                        context->n_mount_images,
+                        context->mount_image_policy ?: &image_policy_service,
+                        tmp_dir,
+                        var_tmp_dir,
+                        creds_path,
+                        context->log_namespace,
+                        context->mount_propagation_flag,
+                        context->root_hash, context->root_hash_size, context->root_hash_path,
+                        context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
+                        context->root_verity,
+                        context->extension_images,
+                        context->n_extension_images,
+                        context->extension_image_policy ?: &image_policy_sysext,
+                        context->extension_directories,
+                        propagate_dir,
+                        incoming_dir,
+                        extension_dir,
+                        root_dir || root_image ? params->notify_socket : NULL,
+                        error_path);
 
         /* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports
          * that with a special, recognizable error ENOANO. In this case, silently proceed, but only if exclusively
@@ -3768,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;
 }
 
@@ -3964,7 +4044,6 @@ static void append_socket_pair(int *array, size_t *n, const int pair[static 2])
 static int close_remaining_fds(
                 const ExecParameters *params,
                 const ExecRuntime *runtime,
-                const DynamicCreds *dcreds,
                 int user_lookup_fd,
                 int socket_fd,
                 const int *fds, size_t n_fds) {
@@ -3988,16 +4067,16 @@ static int close_remaining_fds(
                 n_dont_close += n_fds;
         }
 
-        if (runtime) {
-                append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket);
-                append_socket_pair(dont_close, &n_dont_close, runtime->ipcns_storage_socket);
+        if (runtime && runtime->shared) {
+                append_socket_pair(dont_close, &n_dont_close, runtime->shared->netns_storage_socket);
+                append_socket_pair(dont_close, &n_dont_close, runtime->shared->ipcns_storage_socket);
         }
 
-        if (dcreds) {
-                if (dcreds->user)
-                        append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket);
-                if (dcreds->group)
-                        append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket);
+        if (runtime && runtime->dynamic_creds) {
+                if (runtime->dynamic_creds->user)
+                        append_socket_pair(dont_close, &n_dont_close, runtime->dynamic_creds->user->storage_socket);
+                if (runtime->dynamic_creds->group)
+                        append_socket_pair(dont_close, &n_dont_close, runtime->dynamic_creds->group->storage_socket);
         }
 
         if (user_lookup_fd >= 0)
@@ -4305,13 +4384,66 @@ static int collect_open_file_fds(
         return 0;
 }
 
+static void log_command_line(Unit *unit, const char *msg, const char *executable, char **argv) {
+        assert(unit);
+        assert(msg);
+        assert(executable);
+
+        if (!DEBUG_LOGGING)
+                return;
+
+        _cleanup_free_ char *cmdline = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
+
+        log_unit_struct(unit, LOG_DEBUG,
+                        "EXECUTABLE=%s", executable,
+                        LOG_UNIT_MESSAGE(unit, "%s: %s", msg, strnull(cmdline)),
+                        LOG_UNIT_INVOCATION_ID(unit));
+}
+
+static bool exec_context_need_unprivileged_private_users(const ExecContext *context, const Manager *manager) {
+        assert(context);
+        assert(manager);
+
+        /* These options require PrivateUsers= when used in user units, as we need to be in a user namespace
+         * to have permission to enable them when not running as root. If we have effective CAP_SYS_ADMIN
+         * (system manager) then we have privileges and don't need this. */
+        if (MANAGER_IS_SYSTEM(manager))
+                return false;
+
+        return context->private_users ||
+               context->private_tmp ||
+               context->private_devices ||
+               context->private_network ||
+               context->network_namespace_path ||
+               context->private_ipc ||
+               context->ipc_namespace_path ||
+               context->private_mounts ||
+               context->mount_apivfs ||
+               context->n_bind_mounts > 0 ||
+               context->n_temporary_filesystems > 0 ||
+               context->root_directory ||
+               !strv_isempty(context->extension_directories) ||
+               context->protect_system != PROTECT_SYSTEM_NO ||
+               context->protect_home != PROTECT_HOME_NO ||
+               context->protect_kernel_tunables ||
+               context->protect_kernel_modules ||
+               context->protect_kernel_logs ||
+               context->protect_control_groups ||
+               context->protect_clock ||
+               context->protect_hostname ||
+               !strv_isempty(context->read_write_paths) ||
+               !strv_isempty(context->read_only_paths) ||
+               !strv_isempty(context->inaccessible_paths) ||
+               !strv_isempty(context->exec_paths) ||
+               !strv_isempty(context->no_exec_paths);
+}
+
 static int exec_child(
                 Unit *unit,
                 const ExecCommand *command,
                 const ExecContext *context,
                 const ExecParameters *params,
                 ExecRuntime *runtime,
-                DynamicCreds *dcreds,
                 const CGroupContext *cgroup_context,
                 int socket_fd,
                 const int named_iofds[static 3],
@@ -4395,6 +4527,7 @@ static int exec_child(
 
         log_forget_fds();
         log_set_open_when_needed(true);
+        log_settle_target();
 
         /* In case anything used libc syslog(), close this here, too */
         closelog();
@@ -4443,7 +4576,7 @@ static int exec_child(
         }
 #endif
 
-        r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, keep_fds, n_keep_fds);
+        r = close_remaining_fds(params, runtime, user_lookup_fd, socket_fd, keep_fds, n_keep_fds);
         if (r < 0) {
                 *exit_status = EXIT_FDS;
                 return log_unit_error_errno(unit, r, "Failed to close unwanted file descriptors: %m");
@@ -4489,7 +4622,7 @@ static int exec_child(
                 return log_unit_error_errno(unit, errno, "Failed to update environment: %m");
         }
 
-        if (context->dynamic_user && dcreds) {
+        if (context->dynamic_user && runtime && runtime->dynamic_creds) {
                 _cleanup_strv_free_ char **suggested_paths = NULL;
 
                 /* On top of that, make sure we bypass our own NSS module nss-systemd comprehensively for any NSS
@@ -4505,7 +4638,7 @@ static int exec_child(
                         return log_oom();
                 }
 
-                r = dynamic_creds_realize(dcreds, suggested_paths, &uid, &gid);
+                r = dynamic_creds_realize(runtime->dynamic_creds, suggested_paths, &uid, &gid);
                 if (r < 0) {
                         *exit_status = EXIT_USER;
                         if (r == -EILSEQ)
@@ -4524,8 +4657,8 @@ static int exec_child(
                         return log_unit_error_errno(unit, SYNTHETIC_ERRNO(ESRCH), "GID validation failed for \""GID_FMT"\"", gid);
                 }
 
-                if (dcreds->user)
-                        username = dcreds->user->name;
+                if (runtime->dynamic_creds->user)
+                        username = runtime->dynamic_creds->user->name;
 
         } else {
                 r = get_fixed_user(context, &username, &uid, &gid, &home, &shell);
@@ -4563,8 +4696,7 @@ static int exec_child(
                 return log_unit_error_errno(unit, r, "Failed to determine $HOME for user: %m");
         }
 
-        /* If a socket is connected to STDIN/STDOUT/STDERR, we
-         * must sure to drop O_NONBLOCK */
+        /* If a socket is connected to STDIN/STDOUT/STDERR, we must drop O_NONBLOCK */
         if (socket_fd >= 0)
                 (void) fd_nonblock(socket_fd, false);
 
@@ -4592,16 +4724,16 @@ static int exec_child(
                 }
         }
 
-        if (context->network_namespace_path && runtime && runtime->netns_storage_socket[0] >= 0) {
-                r = open_shareable_ns_path(runtime->netns_storage_socket, context->network_namespace_path, CLONE_NEWNET);
+        if (context->network_namespace_path && runtime && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
+                r = open_shareable_ns_path(runtime->shared->netns_storage_socket, context->network_namespace_path, CLONE_NEWNET);
                 if (r < 0) {
                         *exit_status = EXIT_NETWORK;
                         return log_unit_error_errno(unit, r, "Failed to open network namespace path %s: %m", context->network_namespace_path);
                 }
         }
 
-        if (context->ipc_namespace_path && runtime && runtime->ipcns_storage_socket[0] >= 0) {
-                r = open_shareable_ns_path(runtime->ipcns_storage_socket, context->ipc_namespace_path, CLONE_NEWIPC);
+        if (context->ipc_namespace_path && runtime && runtime->shared && runtime->shared->ipcns_storage_socket[0] >= 0) {
+                r = open_shareable_ns_path(runtime->shared->ipcns_storage_socket, context->ipc_namespace_path, CLONE_NEWIPC);
                 if (r < 0) {
                         *exit_status = EXIT_NAMESPACE;
                         return log_unit_error_errno(unit, r, "Failed to open IPC namespace path %s: %m", context->ipc_namespace_path);
@@ -4938,23 +5070,28 @@ static int exec_child(
                 }
         }
 
-        if (needs_sandboxing && context->private_users && have_effective_cap(CAP_SYS_ADMIN) <= 0) {
+        if (needs_sandboxing && exec_context_need_unprivileged_private_users(context, unit->manager)) {
                 /* If we're unprivileged, set up the user namespace first to enable use of the other namespaces.
                  * Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to
                  * set up the all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */
 
-                userns_set_up = true;
                 r = setup_private_users(saved_uid, saved_gid, uid, gid);
-                if (r < 0) {
+                /* If it was requested explicitly and we can't set it up, fail early. Otherwise, continue and let
+                 * the actual requested operations fail (or silently continue). */
+                if (r < 0 && context->private_users) {
                         *exit_status = EXIT_USER;
                         return log_unit_error_errno(unit, r, "Failed to set up user namespacing for unprivileged user: %m");
                 }
+                if (r < 0)
+                        log_unit_info_errno(unit, r, "Failed to set up user namespacing for unprivileged user, ignoring: %m");
+                else
+                        userns_set_up = true;
         }
 
-        if (exec_needs_network_namespace(context) && runtime && runtime->netns_storage_socket[0] >= 0) {
+        if (exec_needs_network_namespace(context) && runtime && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
 
                 if (ns_type_supported(NAMESPACE_NET)) {
-                        r = setup_shareable_ns(runtime->netns_storage_socket, CLONE_NEWNET);
+                        r = setup_shareable_ns(runtime->shared->netns_storage_socket, CLONE_NEWNET);
                         if (r < 0) {
                                 if (ERRNO_IS_PRIVILEGE(r))
                                         log_unit_warning_errno(unit, r,
@@ -4972,10 +5109,10 @@ static int exec_child(
                         log_unit_warning(unit, "PrivateNetwork=yes is configured, but the kernel does not support network namespaces, ignoring.");
         }
 
-        if (exec_needs_ipc_namespace(context) && runtime && runtime->ipcns_storage_socket[0] >= 0) {
+        if (exec_needs_ipc_namespace(context) && runtime && runtime->shared && runtime->shared->ipcns_storage_socket[0] >= 0) {
 
                 if (ns_type_supported(NAMESPACE_IPC)) {
-                        r = setup_shareable_ns(runtime->ipcns_storage_socket, CLONE_NEWIPC);
+                        r = setup_shareable_ns(runtime->shared->ipcns_storage_socket, CLONE_NEWIPC);
                         if (r == -EPERM)
                                 log_unit_warning_errno(unit, r,
                                                        "PrivateIPC=yes is configured, but IPC namespace setup failed, ignoring: %m");
@@ -5105,9 +5242,10 @@ static int exec_child(
         }
 #endif
 
-        /* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that we are
-         * more aggressive this time since socket_fd and the netns and ipcns fds we don't need anymore. We do keep the exec_fd
-         * however if we have it as we want to keep it open until the final execve(). */
+        /* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that
+         * we are more aggressive this time, since we don't need socket_fd and the netns and ipcns fds any
+         * more. We do keep exec_fd however, if we have it, since we need to keep it open until the final
+         * execve(). */
 
         r = close_all_fds(keep_fds, n_keep_fds);
         if (r >= 0)
@@ -5129,9 +5267,9 @@ static int exec_child(
         if (needs_sandboxing) {
                 uint64_t bset;
 
-                /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly
-                 * requested. (Note this is placed after the general resource limit initialization, see
-                 * above, in order to take precedence.) */
+                /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly requested.
+                 * (Note this is placed after the general resource limit initialization, see above, in order
+                 * to take precedence.) */
                 if (context->restrict_realtime && !context->rlimit[RLIMIT_RTPRIO]) {
                         if (setrlimit(RLIMIT_RTPRIO, &RLIMIT_MAKE_CONST(0)) < 0) {
                                 *exit_status = EXIT_LIMITS;
@@ -5404,19 +5542,7 @@ static int exec_child(
         } else
                 final_argv = command->argv;
 
-        if (DEBUG_LOGGING) {
-                _cleanup_free_ char *line = NULL;
-
-                line = quote_command_line(final_argv, SHELL_ESCAPE_EMPTY);
-                if (!line) {
-                        *exit_status = EXIT_MEMORY;
-                        return log_oom();
-                }
-
-                log_unit_struct(unit, LOG_DEBUG,
-                                "EXECUTABLE=%s", executable,
-                                LOG_UNIT_MESSAGE(unit, "Executing: %s", line));
-        }
+        log_command_line(unit, "Executing", executable, final_argv);
 
         if (exec_fd >= 0) {
                 uint8_t hot = 1;
@@ -5456,7 +5582,6 @@ int exec_spawn(Unit *unit,
                const ExecContext *context,
                const ExecParameters *params,
                ExecRuntime *runtime,
-               DynamicCreds *dcreds,
                const CGroupContext *cgroup_context,
                pid_t *ret) {
 
@@ -5464,7 +5589,6 @@ int exec_spawn(Unit *unit,
         _cleanup_free_ char *subcgroup_path = NULL;
         _cleanup_strv_free_ char **files_env = NULL;
         size_t n_storage_fds = 0, n_socket_fds = 0;
-        _cleanup_free_ char *line = NULL;
         pid_t pid;
 
         assert(unit);
@@ -5474,6 +5598,8 @@ int exec_spawn(Unit *unit,
         assert(params);
         assert(params->fds || (params->n_socket_fds + params->n_storage_fds <= 0));
 
+        LOG_CONTEXT_PUSH_UNIT(unit);
+
         if (context->std_input == EXEC_INPUT_SOCKET ||
             context->std_output == EXEC_OUTPUT_SOCKET ||
             context->std_error == EXEC_OUTPUT_SOCKET) {
@@ -5500,21 +5626,13 @@ int exec_spawn(Unit *unit,
         if (r < 0)
                 return log_unit_error_errno(unit, r, "Failed to load environment files: %m");
 
-        line = quote_command_line(command->argv, SHELL_ESCAPE_EMPTY);
-        if (!line)
-                return log_oom();
-
         /* Fork with up-to-date SELinux label database, so the child inherits the up-to-date db
            and, until the next SELinux policy changes, we save further reloads in future children. */
         mac_selinux_maybe_reload();
 
-        log_unit_struct(unit, LOG_DEBUG,
-                        LOG_UNIT_MESSAGE(unit, "About to execute %s", line),
-                        "EXECUTABLE=%s", command->path, /* We won't know the real executable path until we create
-                                                           the mount namespace in the child, but we want to log
-                                                           from the parent, so we need to use the (possibly
-                                                           inaccurate) path here. */
-                        LOG_UNIT_INVOCATION_ID(unit));
+        /* We won't know the real executable path until we create the mount namespace in the child, but we
+           want to log from the parent, so we use the possibly inaccurate path here. */
+        log_command_line(unit, "About to execute", command->path, command->argv);
 
         if (params->cgroup_path) {
                 r = exec_parameters_get_cgroup_path(params, &subcgroup_path);
@@ -5544,7 +5662,6 @@ int exec_spawn(Unit *unit,
                                context,
                                params,
                                runtime,
-                               dcreds,
                                cgroup_context,
                                socket_fd,
                                named_iofds,
@@ -5700,6 +5817,10 @@ void exec_context_done(ExecContext *c) {
 
         c->load_credentials = hashmap_free(c->load_credentials);
         c->set_credentials = hashmap_free(c->set_credentials);
+
+        c->root_image_policy = image_policy_free(c->root_image_policy);
+        c->mount_image_policy = image_policy_free(c->mount_image_policy);
+        c->extension_image_policy = image_policy_free(c->extension_image_policy);
 }
 
 int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
@@ -6686,6 +6807,23 @@ int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) {
         return 0;
 }
 
+bool exec_context_has_encrypted_credentials(ExecContext *c) {
+        ExecLoadCredential *load_cred;
+        ExecSetCredential *set_cred;
+
+        assert(c);
+
+        HASHMAP_FOREACH(load_cred, c->load_credentials)
+                if (load_cred->encrypted)
+                        return true;
+
+        HASHMAP_FOREACH(set_cred, c->set_credentials)
+                if (set_cred->encrypted)
+                        return true;
+
+        return false;
+}
+
 void exec_status_start(ExecStatus *s, pid_t pid) {
         assert(s);
 
@@ -6786,7 +6924,7 @@ void exec_command_append_list(ExecCommand **l, ExecCommand *e) {
                 end = LIST_FIND_TAIL(command, *l);
                 LIST_INSERT_AFTER(command, *l, end, e);
         } else
-              *l = e;
+                *l = e;
 }
 
 int exec_command_set(ExecCommand *c, const char *path, ...) {
@@ -6843,18 +6981,37 @@ static void *remove_tmpdir_thread(void *p) {
         return NULL;
 }
 
-static ExecRuntime* exec_runtime_free(ExecRuntime *rt, bool destroy) {
+static ExecSharedRuntime* exec_shared_runtime_free(ExecSharedRuntime *rt) {
+        if (!rt)
+                return NULL;
+
+        if (rt->manager)
+                (void) hashmap_remove(rt->manager->exec_shared_runtime_by_id, rt->id);
+
+        rt->id = mfree(rt->id);
+        rt->tmp_dir = mfree(rt->tmp_dir);
+        rt->var_tmp_dir = mfree(rt->var_tmp_dir);
+        safe_close_pair(rt->netns_storage_socket);
+        safe_close_pair(rt->ipcns_storage_socket);
+        return mfree(rt);
+}
+
+DEFINE_TRIVIAL_UNREF_FUNC(ExecSharedRuntime, exec_shared_runtime, exec_shared_runtime_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSharedRuntime*, exec_shared_runtime_free);
+
+ExecSharedRuntime* exec_shared_runtime_destroy(ExecSharedRuntime *rt) {
         int r;
 
         if (!rt)
                 return NULL;
 
-        if (rt->manager)
-                (void) hashmap_remove(rt->manager->exec_runtime_by_id, rt->id);
+        assert(rt->n_ref > 0);
+        rt->n_ref--;
 
-        /* When destroy is true, then rm_rf tmp_dir and var_tmp_dir. */
+        if (rt->n_ref > 0)
+                return NULL;
 
-        if (destroy && rt->tmp_dir && !streq(rt->tmp_dir, RUN_SYSTEMD_EMPTY)) {
+        if (rt->tmp_dir && !streq(rt->tmp_dir, RUN_SYSTEMD_EMPTY)) {
                 log_debug("Spawning thread to nuke %s", rt->tmp_dir);
 
                 r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir);
@@ -6864,7 +7021,7 @@ static ExecRuntime* exec_runtime_free(ExecRuntime *rt, bool destroy) {
                         rt->tmp_dir = NULL;
         }
 
-        if (destroy && rt->var_tmp_dir && !streq(rt->var_tmp_dir, RUN_SYSTEMD_EMPTY)) {
+        if (rt->var_tmp_dir && !streq(rt->var_tmp_dir, RUN_SYSTEMD_EMPTY)) {
                 log_debug("Spawning thread to nuke %s", rt->var_tmp_dir);
 
                 r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir);
@@ -6874,21 +7031,12 @@ static ExecRuntime* exec_runtime_free(ExecRuntime *rt, bool destroy) {
                         rt->var_tmp_dir = NULL;
         }
 
-        rt->id = mfree(rt->id);
-        rt->tmp_dir = mfree(rt->tmp_dir);
-        rt->var_tmp_dir = mfree(rt->var_tmp_dir);
-        safe_close_pair(rt->netns_storage_socket);
-        safe_close_pair(rt->ipcns_storage_socket);
-        return mfree(rt);
-}
-
-static void exec_runtime_freep(ExecRuntime **rt) {
-        (void) exec_runtime_free(*rt, false);
+        return exec_shared_runtime_free(rt);
 }
 
-static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
+static int exec_shared_runtime_allocate(ExecSharedRuntime **ret, const char *id) {
         _cleanup_free_ char *id_copy = NULL;
-        ExecRuntime *n;
+        ExecSharedRuntime *n;
 
         assert(ret);
 
@@ -6896,11 +7044,11 @@ static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
         if (!id_copy)
                 return -ENOMEM;
 
-        n = new(ExecRuntime, 1);
+        n = new(ExecSharedRuntime, 1);
         if (!n)
                 return -ENOMEM;
 
-        *n = (ExecRuntime) {
+        *n = (ExecSharedRuntime) {
                 .id = TAKE_PTR(id_copy),
                 .netns_storage_socket = PIPE_EBADF,
                 .ipcns_storage_socket = PIPE_EBADF,
@@ -6910,16 +7058,16 @@ static int exec_runtime_allocate(ExecRuntime **ret, const char *id) {
         return 0;
 }
 
-static int exec_runtime_add(
+static int exec_shared_runtime_add(
                 Manager *m,
                 const char *id,
                 char **tmp_dir,
                 char **var_tmp_dir,
                 int netns_storage_socket[2],
                 int ipcns_storage_socket[2],
-                ExecRuntime **ret) {
+                ExecSharedRuntime **ret) {
 
-        _cleanup_(exec_runtime_freep) ExecRuntime *rt = NULL;
+        _cleanup_(exec_shared_runtime_freep) ExecSharedRuntime *rt = NULL;
         int r;
 
         assert(m);
@@ -6927,11 +7075,11 @@ static int exec_runtime_add(
 
         /* tmp_dir, var_tmp_dir, {net,ipc}ns_storage_socket fds are donated on success */
 
-        r = exec_runtime_allocate(&rt, id);
+        r = exec_shared_runtime_allocate(&rt, id);
         if (r < 0)
                 return r;
 
-        r = hashmap_ensure_put(&m->exec_runtime_by_id, &string_hash_ops, rt->id, rt);
+        r = hashmap_ensure_put(&m->exec_shared_runtime_by_id, &string_hash_ops, rt->id, rt);
         if (r < 0)
                 return r;
 
@@ -6953,16 +7101,16 @@ static int exec_runtime_add(
 
         if (ret)
                 *ret = rt;
-        /* do not remove created ExecRuntime object when the operation succeeds. */
+        /* do not remove created ExecSharedRuntime object when the operation succeeds. */
         TAKE_PTR(rt);
         return 0;
 }
 
-static int exec_runtime_make(
+static int exec_shared_runtime_make(
                 Manager *m,
                 const ExecContext *c,
                 const char *id,
-                ExecRuntime **ret) {
+                ExecSharedRuntime **ret) {
 
         _cleanup_(namespace_cleanup_tmpdirp) char *tmp_dir = NULL, *var_tmp_dir = NULL;
         _cleanup_close_pair_ int netns_storage_socket[2] = PIPE_EBADF, ipcns_storage_socket[2] = PIPE_EBADF;
@@ -6972,7 +7120,7 @@ static int exec_runtime_make(
         assert(c);
         assert(id);
 
-        /* It is not necessary to create ExecRuntime object. */
+        /* It is not necessary to create ExecSharedRuntime object. */
         if (!exec_needs_network_namespace(c) && !exec_needs_ipc_namespace(c) && !c->private_tmp) {
                 *ret = NULL;
                 return 0;
@@ -6997,24 +7145,24 @@ static int exec_runtime_make(
                         return -errno;
         }
 
-        r = exec_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret);
+        r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret);
         if (r < 0)
                 return r;
 
         return 1;
 }
 
-int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool create, ExecRuntime **ret) {
-        ExecRuntime *rt;
+int exec_shared_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool create, ExecSharedRuntime **ret) {
+        ExecSharedRuntime *rt;
         int r;
 
         assert(m);
         assert(id);
         assert(ret);
 
-        rt = hashmap_get(m->exec_runtime_by_id, id);
+        rt = hashmap_get(m->exec_shared_runtime_by_id, id);
         if (rt)
-                /* We already have an ExecRuntime object, let's increase the ref count and reuse it */
+                /* We already have an ExecSharedRuntime object, let's increase the ref count and reuse it */
                 goto ref;
 
         if (!create) {
@@ -7023,11 +7171,11 @@ int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *id, bool
         }
 
         /* If not found, then create a new object. */
-        r = exec_runtime_make(m, c, id, &rt);
+        r = exec_shared_runtime_make(m, c, id, &rt);
         if (r < 0)
                 return r;
         if (r == 0) {
-                /* When r == 0, it is not necessary to create ExecRuntime object. */
+                /* When r == 0, it is not necessary to create ExecSharedRuntime object. */
                 *ret = NULL;
                 return 0;
         }
@@ -7039,27 +7187,14 @@ ref:
         return 1;
 }
 
-ExecRuntime *exec_runtime_unref(ExecRuntime *rt, bool destroy) {
-        if (!rt)
-                return NULL;
-
-        assert(rt->n_ref > 0);
-
-        rt->n_ref--;
-        if (rt->n_ref > 0)
-                return NULL;
-
-        return exec_runtime_free(rt, destroy);
-}
-
-int exec_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
-        ExecRuntime *rt;
+int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
+        ExecSharedRuntime *rt;
 
         assert(m);
         assert(f);
         assert(fds);
 
-        HASHMAP_FOREACH(rt, m->exec_runtime_by_id) {
+        HASHMAP_FOREACH(rt, m->exec_shared_runtime_by_id) {
                 fprintf(f, "exec-runtime=%s", rt->id);
 
                 if (rt->tmp_dir)
@@ -7114,33 +7249,33 @@ int exec_runtime_serialize(const Manager *m, FILE *f, FDSet *fds) {
         return 0;
 }
 
-int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds) {
-        _cleanup_(exec_runtime_freep) ExecRuntime *rt_create = NULL;
-        ExecRuntime *rt;
+int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds) {
+        _cleanup_(exec_shared_runtime_freep) ExecSharedRuntime *rt_create = NULL;
+        ExecSharedRuntime *rt;
         int r;
 
         /* This is for the migration from old (v237 or earlier) deserialization text.
          * Due to the bug #7790, this may not work with the units that use JoinsNamespaceOf=.
-         * Even if the ExecRuntime object originally created by the other unit, we cannot judge
+         * Even if the ExecSharedRuntime object originally created by the other unit, we cannot judge
          * so or not from the serialized text, then we always creates a new object owned by this. */
 
         assert(u);
         assert(key);
         assert(value);
 
-        /* Manager manages ExecRuntime objects by the unit id.
+        /* Manager manages ExecSharedRuntime objects by the unit id.
          * So, we omit the serialized text when the unit does not have id (yet?)... */
         if (isempty(u->id)) {
                 log_unit_debug(u, "Invocation ID not found. Dropping runtime parameter.");
                 return 0;
         }
 
-        if (hashmap_ensure_allocated(&u->manager->exec_runtime_by_id, &string_hash_ops) < 0)
+        if (hashmap_ensure_allocated(&u->manager->exec_shared_runtime_by_id, &string_hash_ops) < 0)
                 return log_oom();
 
-        rt = hashmap_get(u->manager->exec_runtime_by_id, u->id);
+        rt = hashmap_get(u->manager->exec_shared_runtime_by_id, u->id);
         if (!rt) {
-                if (exec_runtime_allocate(&rt_create, u->id) < 0)
+                if (exec_shared_runtime_allocate(&rt_create, u->id) < 0)
                         return log_oom();
 
                 rt = rt_create;
@@ -7179,9 +7314,9 @@ int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value,
         } else
                 return 0;
 
-        /* If the object is newly created, then put it to the hashmap which manages ExecRuntime objects. */
+        /* If the object is newly created, then put it to the hashmap which manages ExecSharedRuntime objects. */
         if (rt_create) {
-                r = hashmap_put(u->manager->exec_runtime_by_id, rt_create->id, rt_create);
+                r = hashmap_put(u->manager->exec_shared_runtime_by_id, rt_create->id, rt_create);
                 if (r < 0) {
                         log_unit_debug_errno(u, r, "Failed to put runtime parameter to manager's storage: %m");
                         return 0;
@@ -7196,7 +7331,7 @@ int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value,
         return 1;
 }
 
-int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
+int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
         _cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL;
         char *id = NULL;
         int r, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1};
@@ -7308,27 +7443,68 @@ int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
         }
 
 finalize:
-        r = exec_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL);
+        r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL);
         if (r < 0)
                 return log_debug_errno(r, "Failed to add exec-runtime: %m");
         return 0;
 }
 
-void exec_runtime_vacuum(Manager *m) {
-        ExecRuntime *rt;
+void exec_shared_runtime_vacuum(Manager *m) {
+        ExecSharedRuntime *rt;
 
         assert(m);
 
-        /* Free unreferenced ExecRuntime objects. This is used after manager deserialization process. */
+        /* Free unreferenced ExecSharedRuntime objects. This is used after manager deserialization process. */
 
-        HASHMAP_FOREACH(rt, m->exec_runtime_by_id) {
+        HASHMAP_FOREACH(rt, m->exec_shared_runtime_by_id) {
                 if (rt->n_ref > 0)
                         continue;
 
-                (void) exec_runtime_free(rt, false);
+                (void) exec_shared_runtime_free(rt);
         }
 }
 
+int exec_runtime_make(ExecSharedRuntime *shared, DynamicCreds *creds, ExecRuntime **ret) {
+        _cleanup_(exec_runtime_freep) ExecRuntime *rt = NULL;
+
+        assert(ret);
+
+        if (!shared && !creds) {
+                *ret = NULL;
+                return 0;
+        }
+
+        rt = new(ExecRuntime, 1);
+        if (!rt)
+                return -ENOMEM;
+
+        *rt = (ExecRuntime) {
+                .shared = shared,
+                .dynamic_creds = creds,
+        };
+
+        *ret = TAKE_PTR(rt);
+        return 1;
+}
+
+ExecRuntime* exec_runtime_free(ExecRuntime *rt) {
+        if (!rt)
+                return NULL;
+
+        exec_shared_runtime_unref(rt->shared);
+        dynamic_creds_unref(rt->dynamic_creds);
+        return mfree(rt);
+}
+
+ExecRuntime* exec_runtime_destroy(ExecRuntime *rt) {
+        if (!rt)
+                return NULL;
+
+        rt->shared = exec_shared_runtime_destroy(rt->shared);
+        rt->dynamic_creds = dynamic_creds_destroy(rt->dynamic_creds);
+        return exec_runtime_free(rt);
+}
+
 void exec_params_clear(ExecParameters *p) {
         if (!p)
                 return;
@@ -7449,6 +7625,23 @@ void exec_directory_sort(ExecDirectory *d) {
                         }
 }
 
+ExecCleanMask exec_clean_mask_from_string(const char *s) {
+        ExecDirectoryType t;
+
+        assert(s);
+
+        if (streq(s, "all"))
+                return EXEC_CLEAN_ALL;
+        if (streq(s, "fdstore"))
+                return EXEC_CLEAN_FDSTORE;
+
+        t = exec_resource_type_from_string(s);
+        if (t < 0)
+                return (ExecCleanMask) t;
+
+        return 1U << t;
+}
+
 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
 
index 1d264782fcecaa258261d57c9b68e4853117dcfd..d2f5507405818fcb430285a8adf29c45163ac6b3 100644 (file)
@@ -4,6 +4,8 @@
 typedef struct ExecStatus ExecStatus;
 typedef struct ExecCommand ExecCommand;
 typedef struct ExecContext ExecContext;
+typedef struct ExecSharedRuntime ExecSharedRuntime;
+typedef struct DynamicCreds DynamicCreds;
 typedef struct ExecRuntime ExecRuntime;
 typedef struct ExecParameters ExecParameters;
 typedef struct Manager Manager;
@@ -106,7 +108,7 @@ struct ExecCommand {
  * invocations of commands. Specifically, this allows sharing of /tmp and /var/tmp data as well as network namespaces
  * between invocations of commands. This is a reference counted object, with one reference taken by each currently
  * active command invocation that wants to share this runtime. */
-struct ExecRuntime {
+struct ExecSharedRuntime {
         unsigned n_ref;
 
         Manager *manager;
@@ -124,6 +126,11 @@ struct ExecRuntime {
         int ipcns_storage_socket[2];
 };
 
+struct ExecRuntime {
+        ExecSharedRuntime *shared;
+        DynamicCreds *dynamic_creds;
+};
+
 typedef enum ExecDirectoryType {
         EXEC_DIRECTORY_RUNTIME = 0,
         EXEC_DIRECTORY_STATE,
@@ -154,8 +161,9 @@ typedef enum ExecCleanMask {
         EXEC_CLEAN_CACHE         = 1U << EXEC_DIRECTORY_CACHE,
         EXEC_CLEAN_LOGS          = 1U << EXEC_DIRECTORY_LOGS,
         EXEC_CLEAN_CONFIGURATION = 1U << EXEC_DIRECTORY_CONFIGURATION,
+        EXEC_CLEAN_FDSTORE       = 1U << _EXEC_DIRECTORY_TYPE_MAX,
         EXEC_CLEAN_NONE          = 0,
-        EXEC_CLEAN_ALL           = (1U << _EXEC_DIRECTORY_TYPE_MAX) - 1,
+        EXEC_CLEAN_ALL           = (1U << (_EXEC_DIRECTORY_TYPE_MAX+1)) - 1,
         _EXEC_CLEAN_MASK_INVALID = -EINVAL,
 } ExecCleanMask;
 
@@ -352,6 +360,8 @@ struct ExecContext {
 
         Hashmap *set_credentials; /* output id → ExecSetCredential */
         Hashmap *load_credentials; /* output id → ExecLoadCredential */
+
+        ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy;
 };
 
 static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
@@ -440,7 +450,6 @@ int exec_spawn(Unit *unit,
                const ExecContext *context,
                const ExecParameters *exec_params,
                ExecRuntime *runtime,
-               DynamicCreds *dynamic_creds,
                const CGroupContext *cgroup_context,
                pid_t *ret);
 
@@ -466,6 +475,7 @@ const char* exec_context_fdname(const ExecContext *c, int fd_index);
 
 bool exec_context_may_touch_console(const ExecContext *c);
 bool exec_context_maintains_privileges(const ExecContext *c);
+bool exec_context_has_encrypted_credentials(ExecContext *c);
 
 int exec_context_get_effective_ioprio(const ExecContext *c);
 bool exec_context_get_effective_mount_apivfs(const ExecContext *c);
@@ -482,13 +492,20 @@ void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int
 void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix);
 void exec_status_reset(ExecStatus *s);
 
-int exec_runtime_acquire(Manager *m, const ExecContext *c, const char *name, bool create, ExecRuntime **ret);
-ExecRuntime *exec_runtime_unref(ExecRuntime *r, bool destroy);
+int exec_shared_runtime_acquire(Manager *m, const ExecContext *c, const char *name, bool create, ExecSharedRuntime **ret);
+ExecSharedRuntime *exec_shared_runtime_destroy(ExecSharedRuntime *r);
+ExecSharedRuntime *exec_shared_runtime_unref(ExecSharedRuntime *r);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSharedRuntime*, exec_shared_runtime_unref);
+
+int exec_shared_runtime_serialize(const Manager *m, FILE *f, FDSet *fds);
+int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds);
+int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds);
+void exec_shared_runtime_vacuum(Manager *m);
 
-int exec_runtime_serialize(const Manager *m, FILE *f, FDSet *fds);
-int exec_runtime_deserialize_compat(Unit *u, const char *key, const char *value, FDSet *fds);
-int exec_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds);
-void exec_runtime_vacuum(Manager *m);
+int exec_runtime_make(ExecSharedRuntime *shared, DynamicCreds *creds, ExecRuntime **ret);
+ExecRuntime* exec_runtime_free(ExecRuntime *rt);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecRuntime*, exec_runtime_free);
+ExecRuntime* exec_runtime_destroy(ExecRuntime *rt);
 
 void exec_params_clear(ExecParameters *p);
 
@@ -504,6 +521,8 @@ void exec_directory_done(ExecDirectory *d);
 int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink);
 void exec_directory_sort(ExecDirectory *d);
 
+ExecCleanMask exec_clean_mask_from_string(const char *s);
+
 extern const struct hash_ops exec_set_credential_hash_ops;
 extern const struct hash_ops exec_load_credential_hash_ops;
 
index 334fbf770e49d4df0aa77e0c3a92e3336cfb33ff..826ef6809c144f1351280b47c1195556330c1216 100644 (file)
@@ -1051,6 +1051,12 @@ finish:
                         job_add_to_gc_queue(other->job);
                 }
 
+        /* Ensure that when an upheld/unneeded/bound unit activation job fails we requeue it, if it still
+         * necessary. If there are no state changes in the triggerer, it would not be retried otherwise. */
+        unit_submit_to_start_when_upheld_queue(u);
+        unit_submit_to_stop_when_bound_queue(u);
+        unit_submit_to_stop_when_unneeded_queue(u);
+
         manager_check_finished(u->manager);
 
         return 0;
index e84374377788227fecafabab20e26e3211112be9..c09e17f568bac85a981511deef0f32dfe78393a1 100644 (file)
@@ -11,6 +11,7 @@
 #include "macro.h"
 #include "recurse-dir.h"
 #include "string-util.h"
+#include "strv.h"
 #include "virt.h"
 
 #if HAVE_KMOD
@@ -30,7 +31,7 @@ static void systemd_kmod_log(
         REENABLE_WARNING;
 }
 
-static int has_virtio_rng_recurse_dir_cb(
+static int match_modalias_recurse_dir_cb(
                 RecurseDirEvent event,
                 const char *path,
                 int dir_fd,
@@ -40,6 +41,7 @@ static int has_virtio_rng_recurse_dir_cb(
                 void *userdata) {
 
         _cleanup_free_ char *alias = NULL;
+        char **modaliases = ASSERT_PTR(userdata);
         int r;
 
         if (event != RECURSE_DIR_ENTRY)
@@ -57,10 +59,7 @@ static int has_virtio_rng_recurse_dir_cb(
                 return RECURSE_DIR_LEAVE_DIRECTORY;
         }
 
-        if (startswith(alias, "pci:v00001AF4d00001005"))
-                return 1;
-
-        if (startswith(alias, "pci:v00001AF4d00001044"))
+        if (startswith_strv(alias, modaliases))
                 return 1;
 
         return RECURSE_DIR_LEAVE_DIRECTORY;
@@ -69,20 +68,45 @@ static int has_virtio_rng_recurse_dir_cb(
 static bool has_virtio_rng(void) {
         int r;
 
+        /* Directory traversal might be slow, hence let's do a cheap check first if it's even worth it */
+        if (detect_vm() == VIRTUALIZATION_NONE)
+                return false;
+
         r = recurse_dir_at(
                         AT_FDCWD,
                         "/sys/devices/pci0000:00",
                         /* statx_mask= */ 0,
                         /* n_depth_max= */ 2,
                         RECURSE_DIR_ENSURE_TYPE,
-                        has_virtio_rng_recurse_dir_cb,
-                        NULL);
+                        match_modalias_recurse_dir_cb,
+                        STRV_MAKE("pci:v00001AF4d00001005", "pci:v00001AF4d00001044"));
         if (r < 0)
                 log_debug_errno(r, "Failed to determine whether host has virtio-rng device, ignoring: %m");
 
         return r > 0;
 }
 
+static bool has_virtio_console(void) {
+        int r;
+
+        /* Directory traversal might be slow, hence let's do a cheap check first if it's even worth it */
+        if (detect_vm() == VIRTUALIZATION_NONE)
+                return false;
+
+        r = recurse_dir_at(
+                        AT_FDCWD,
+                        "/sys/devices/pci0000:00",
+                        /* statx_mask= */ 0,
+                        /* n_depth_max= */ 3,
+                        RECURSE_DIR_ENSURE_TYPE,
+                        match_modalias_recurse_dir_cb,
+                        STRV_MAKE("virtio:d00000003v", "virtio:d0000000Bv"));
+        if (r < 0)
+                log_debug_errno(r, "Failed to determine whether host has virtio-console device, ignoring: %m");
+
+        return r > 0;
+}
+
 static bool in_qemu(void) {
         return IN_SET(detect_vm(), VIRTUALIZATION_KVM, VIRTUALIZATION_QEMU);
 }
@@ -100,31 +124,35 @@ int kmod_setup(void) {
         } kmod_table[] = {
                 /* This one we need to load explicitly, since auto-loading on use doesn't work
                  * before udev created the ghost device nodes, and we need it earlier than that. */
-                { "autofs4",     "/sys/class/misc/autofs",    true,  false, NULL           },
+                { "autofs4",         "/sys/class/misc/autofs",    true,  false, NULL               },
 
                 /* This one we need to load explicitly, since auto-loading of IPv6 is not done when
                  * we try to configure ::1 on the loopback device. */
-                { "ipv6",        "/sys/module/ipv6",          false, true,  NULL           },
+                { "ipv6",            "/sys/module/ipv6",          false, true,  NULL               },
 
                 /* This should never be a module */
-                { "unix",        "/proc/net/unix",            true,  true,  NULL           },
+                { "unix",            "/proc/net/unix",            true,  true,  NULL               },
 
 #if HAVE_LIBIPTC
                 /* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */
-                { "ip_tables",   "/proc/net/ip_tables_names", false, false, NULL           },
+                { "ip_tables",       "/proc/net/ip_tables_names", false, false, NULL               },
 #endif
                 /* virtio_rng would be loaded by udev later, but real entropy might be needed very early */
-                { "virtio_rng",  NULL,                        false, false, has_virtio_rng },
+                { "virtio_rng",      NULL,                        false, false, has_virtio_rng     },
+
+                /* we want early logging to hvc consoles if possible, and make sure systemd-getty-generator
+                 * can rely on all consoles being probed already.*/
+                { "virtio_console",  NULL,                        false, false, has_virtio_console },
 
                 /* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */
-                { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu        },
+                { "qemu_fw_cfg",     "/sys/firmware/qemu_fw_cfg", false, false, in_qemu            },
 
                 /* dmi-sysfs is needed to import credentials from it super early */
-                { "dmi-sysfs",   "/sys/firmware/dmi/entries", false, false, NULL           },
+                { "dmi-sysfs",       "/sys/firmware/dmi/entries", false, false, NULL               },
 
 #if HAVE_TPM2
                 /* Make sure the tpm subsystem is available which ConditionSecurity=tpm2 depends on. */
-                { "tpm",         "/sys/class/tpmrm",          false, false, efi_has_tpm2   },
+                { "tpm",             "/sys/class/tpmrm",          false, false, efi_has_tpm2       },
 #endif
         };
         _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
index 9a3ec7faf69ccbeffee51d510785d8eefc2859fc..8a2823b075a0ebbc6e68b29c7ea9af61219ec175 100644 (file)
@@ -6,12 +6,15 @@
 {{type}}.RootDirectory,                    config_parse_unit_path_printf,               true,                               offsetof({{type}}, exec_context.root_directory)
 {{type}}.RootImage,                        config_parse_unit_path_printf,               true,                               offsetof({{type}}, exec_context.root_image)
 {{type}}.RootImageOptions,                 config_parse_root_image_options,             0,                                  offsetof({{type}}, exec_context)
+{{type}}.RootImagePolicy,                  config_parse_image_policy,                   0,                                  offsetof({{type}}, exec_context.root_image_policy)
 {{type}}.RootHash,                         config_parse_exec_root_hash,                 0,                                  offsetof({{type}}, exec_context)
 {{type}}.RootHashSignature,                config_parse_exec_root_hash_sig,             0,                                  offsetof({{type}}, exec_context)
 {{type}}.RootVerity,                       config_parse_unit_path_printf,               true,                               offsetof({{type}}, exec_context.root_verity)
 {{type}}.ExtensionDirectories,             config_parse_namespace_path_strv,            0,                                  offsetof({{type}}, exec_context.extension_directories)
 {{type}}.ExtensionImages,                  config_parse_extension_images,               0,                                  offsetof({{type}}, exec_context)
+{{type}}.ExtensionImagePolicy,             config_parse_image_policy,                   0,                                  offsetof({{type}}, exec_context.extension_image_policy)
 {{type}}.MountImages,                      config_parse_mount_images,                   0,                                  offsetof({{type}}, exec_context)
+{{type}}.MountImagePolicy,                 config_parse_image_policy,                   0,                                  offsetof({{type}}, exec_context.mount_image_policy)
 {{type}}.User,                             config_parse_user_group_compat,              0,                                  offsetof({{type}}, exec_context.user)
 {{type}}.Group,                            config_parse_user_group_compat,              0,                                  offsetof({{type}}, exec_context.group)
 {{type}}.SupplementaryGroups,              config_parse_user_group_strv_compat,         0,                                  offsetof({{type}}, exec_context.supplementary_groups)
 {{type}}.MountFlags,                       config_parse_exec_mount_propagation_flag,    0,                                  offsetof({{type}}, exec_context.mount_propagation_flag)
 {{type}}.MountAPIVFS,                      config_parse_exec_mount_apivfs,              0,                                  offsetof({{type}}, exec_context)
 {{type}}.Personality,                      config_parse_personality,                    0,                                  offsetof({{type}}, exec_context.personality)
-{{type}}.RuntimeDirectoryPreserve,         config_parse_runtime_preserve_mode,          0,                                  offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
+{{type}}.RuntimeDirectoryPreserve,         config_parse_exec_preserve_mode,             0,                                  offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
 {{type}}.RuntimeDirectoryMode,             config_parse_mode,                           0,                                  offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode)
 {{type}}.RuntimeDirectory,                 config_parse_exec_directories,               0,                                  offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME])
 {{type}}.StateDirectoryMode,               config_parse_mode,                           0,                                  offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode)
@@ -260,6 +263,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
 #include <stddef.h>
 #include "all-units.h"
 #include "conf-parser.h"
+#include "image-policy.h"
 #include "in-addr-prefix-util.h"
 #include "load-fragment.h"
 %}
@@ -399,6 +403,8 @@ Service.ExecReload,                      config_parse_exec,
 Service.ExecStop,                        config_parse_exec,                           SERVICE_EXEC_STOP,                  offsetof(Service, exec_command)
 Service.ExecStopPost,                    config_parse_exec,                           SERVICE_EXEC_STOP_POST,             offsetof(Service, exec_command)
 Service.RestartSec,                      config_parse_sec,                            0,                                  offsetof(Service, restart_usec)
+Service.RestartSteps,                    config_parse_unsigned,                       0,                                  offsetof(Service, restart_steps)
+Service.RestartSecMax,                   config_parse_sec,                            0,                                  offsetof(Service, restart_usec_max)
 Service.TimeoutSec,                      config_parse_service_timeout,                0,                                  0
 Service.TimeoutStartSec,                 config_parse_service_timeout,                0,                                  0
 Service.TimeoutStopSec,                  config_parse_sec_fix_0,                      0,                                  offsetof(Service, timeout_stop_usec)
@@ -428,6 +434,7 @@ Service.SysVStartPriority,               config_parse_warn_compat,
 Service.NonBlocking,                     config_parse_bool,                           0,                                  offsetof(Service, exec_context.non_blocking)
 Service.BusName,                         config_parse_bus_name,                       0,                                  offsetof(Service, bus_name)
 Service.FileDescriptorStoreMax,          config_parse_unsigned,                       0,                                  offsetof(Service, n_fd_store_max)
+Service.FileDescriptorStorePreserve,     config_parse_exec_preserve_mode,             0,                                  offsetof(Service, fd_store_preserve_mode)
 Service.NotifyAccess,                    config_parse_notify_access,                  0,                                  offsetof(Service, notify_access)
 Service.Sockets,                         config_parse_service_sockets,                0,                                  0
 Service.BusPolicy,                       config_parse_warn_compat,                    DISABLED_LEGACY,                    0
index 533c09f72eab79eede6f4a2db551f6a9b832ef09..581a051d460dd08857a748233872da7584028349 100644 (file)
@@ -138,7 +138,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to pa
 DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
 DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome, "Failed to parse protect home value");
 DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem, "Failed to parse protect system value");
-DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse runtime directory preserve mode");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse resource preserve mode");
 DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type");
 DEFINE_CONFIG_PARSE_ENUM(config_parse_service_exit_type, service_exit_type, ServiceExitType, "Failed to parse service exit type");
 DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier");
@@ -800,7 +800,7 @@ int config_parse_exec_coredump_filter(
         }
 
         c->coredump_filter |= f;
-        c->oom_score_adjust_set = true;
+        c->coredump_filter_set = true;
         return 0;
 }
 
index 91dc91745848f2864ec904d98cb078b453ab5a07..a38d697338adfc48f5a43f357a3f1807ff1ef0d7 100644 (file)
@@ -100,7 +100,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_selinux_context);
 CONFIG_PARSER_PROTOTYPE(config_parse_exec_apparmor_profile);
 CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
 CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
-CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode);
+CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
 CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
 CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
 CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
index 5f8f251e9a2004aa49446b1359d38370f68f906b..932ea64e4578d28132f098e9d1bf18bded501534 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) {
 
@@ -1506,7 +1458,6 @@ static void redirect_telinit(int argc, char *argv[]) {
 }
 
 static int become_shutdown(int objective, int retval) {
-
         static const char* const table[_MANAGER_OBJECTIVE_MAX] = {
                 [MANAGER_EXIT]     = "exit",
                 [MANAGER_REBOOT]   = "reboot",
@@ -1515,46 +1466,47 @@ static int become_shutdown(int objective, int retval) {
                 [MANAGER_KEXEC]    = "kexec",
         };
 
-        char log_level[DECIMAL_STR_MAX(int) + 1],
-                exit_code[DECIMAL_STR_MAX(uint8_t) + 1],
-                timeout[DECIMAL_STR_MAX(usec_t) + 1];
-
-        const char* command_line[13] = {
-                SYSTEMD_SHUTDOWN_BINARY_PATH,
-                table[objective],
-                "--timeout", timeout,
-                "--log-level", log_level,
-                "--log-target",
-        };
+        char log_level[STRLEN("--log-level=") + DECIMAL_STR_MAX(int)],
+             timeout[STRLEN("--timeout=") + DECIMAL_STR_MAX(usec_t) + STRLEN("us")],
+             exit_code[STRLEN("--exit-code=") + DECIMAL_STR_MAX(uint8_t)];
 
         _cleanup_strv_free_ char **env_block = NULL;
         usec_t watchdog_timer = 0;
-        size_t pos = 7;
         int r;
 
         assert(objective >= 0 && objective < _MANAGER_OBJECTIVE_MAX);
         assert(table[objective]);
-        assert(!command_line[pos]);
-        env_block = strv_copy(environ);
 
-        xsprintf(log_level, "%d", log_get_max_level());
-        xsprintf(timeout, "%" PRI_USEC "us", arg_default_timeout_stop_usec);
+        xsprintf(log_level, "--log-level=%d", log_get_max_level());
+        xsprintf(timeout, "--timeout=%" PRI_USEC "us", arg_default_timeout_stop_usec);
+
+        const char* command_line[10] = {
+                SYSTEMD_SHUTDOWN_BINARY_PATH,
+                table[objective],
+                log_level,
+                timeout,
+                /* Note that the last position is a terminator and must contain NULL. */
+        };
+        size_t pos = 4;
+
+        assert(command_line[pos-1]);
+        assert(!command_line[pos]);
 
         switch (log_get_target()) {
 
         case LOG_TARGET_KMSG:
         case LOG_TARGET_JOURNAL_OR_KMSG:
         case LOG_TARGET_SYSLOG_OR_KMSG:
-                command_line[pos++] = "kmsg";
+                command_line[pos++] = "--log-target=kmsg";
                 break;
 
         case LOG_TARGET_NULL:
-                command_line[pos++] = "null";
+                command_line[pos++] = "--log-target=null";
                 break;
 
         case LOG_TARGET_CONSOLE:
         default:
-                command_line[pos++] = "console";
+                command_line[pos++] = "--log-target=console";
                 break;
         };
 
@@ -1568,13 +1520,14 @@ static int become_shutdown(int objective, int retval) {
                 command_line[pos++] = "--log-time";
 
         if (objective == MANAGER_EXIT) {
-                command_line[pos++] = "--exit-code";
+                xsprintf(exit_code, "--exit-code=%d", retval);
                 command_line[pos++] = exit_code;
-                xsprintf(exit_code, "%d", retval);
         }
 
         assert(pos < ELEMENTSOF(command_line));
 
+        /* The watchdog: */
+
         if (objective == MANAGER_REBOOT)
                 watchdog_timer = arg_reboot_watchdog;
         else if (objective == MANAGER_KEXEC)
@@ -1588,6 +1541,10 @@ static int become_shutdown(int objective, int retval) {
         r = watchdog_setup(watchdog_timer);
         watchdog_close(r < 0);
 
+        /* The environment block: */
+
+        env_block = strv_copy(environ);
+
         /* Tell the binary how often to ping, ignore failure */
         (void) strv_extendf(&env_block, "WATCHDOG_USEC="USEC_FMT, watchdog_timer);
 
@@ -1698,7 +1655,6 @@ static void cmdline_take_random_seed(void) {
 }
 
 static void initialize_coredump(bool skip_setup) {
-#if ENABLE_COREDUMP
         if (getpid_cached() != 1)
                 return;
 
@@ -1712,7 +1668,6 @@ static void initialize_coredump(bool skip_setup) {
          * command line later so core dumps can still be generated during early startup and in initrd. */
         if (!skip_setup)
                 disable_coredumps();
-#endif
 }
 
 static void initialize_core_pattern(bool skip_setup) {
@@ -1847,11 +1802,11 @@ static int do_reexecute(
                         log_error_errno(r, "Failed to switch root, trying to continue: %m");
         }
 
-        args_size = argc + 6;
+        args_size = argc + 5;
         args = newa(const char*, args_size);
 
         if (!switch_root_init) {
-                char sfd[DECIMAL_STR_MAX(int)];
+                char sfd[STRLEN("--deserialize=") + DECIMAL_STR_MAX(int)];
 
                 /* First try to spawn ourselves with the right path, and with full serialization. We do this
                  * only if the user didn't specify an explicit init to spawn. */
@@ -1859,7 +1814,7 @@ static int do_reexecute(
                 assert(arg_serialization);
                 assert(fds);
 
-                xsprintf(sfd, "%i", fileno(arg_serialization));
+                xsprintf(sfd, "--deserialize=%i", fileno(arg_serialization));
 
                 i = 1;         /* Leave args[0] empty for now. */
                 filter_args(args, &i, argv, argc);
@@ -1867,7 +1822,6 @@ static int do_reexecute(
                 if (switch_root_dir)
                         args[i++] = "--switched-root";
                 args[i++] = runtime_scope_cmdline_option_to_string(arg_runtime_scope);
-                args[i++] = "--deserialize";
                 args[i++] = sfd;
                 args[i++] = NULL;
 
@@ -2742,7 +2696,7 @@ static bool early_skip_setup_check(int argc, char *argv[]) {
         for (int i = 1; i < argc; i++)
                 if (streq(argv[i], "--switched-root"))
                         return false; /* If we switched root, don't skip the setup. */
-                else if (streq(argv[i], "--deserialize"))
+                else if (startswith(argv[i], "--deserialize=") || streq(argv[i], "--deserialize"))
                         found_deserialize = true;
 
         return found_deserialize; /* When we are deserializing, then we are reexecuting, hence avoid the extensive setup */
@@ -2844,12 +2798,18 @@ int main(int argc, char *argv[]) {
                                         error_message = "Failed to mount early API filesystems";
                                         goto finish;
                                 }
+                        }
+
+                        /* We might have just mounted /proc, so let's try to parse the kernel
+                         * command line log arguments immediately. */
+                        log_parse_environment();
 
-                                /* Let's open the log backend a second time, in case the first time didn't
-                                 * work. Quite possibly we have mounted /dev just now, so /dev/kmsg became
-                                 * available, and it previously wasn't. */
-                                log_open();
+                        /* Let's open the log backend a second time, in case the first time didn't
+                         * work. Quite possibly we have mounted /dev just now, so /dev/kmsg became
+                         * available, and it previously wasn't. */
+                        log_open();
 
+                        if (!skip_setup) {
                                 disable_printk_ratelimit();
 
                                 r = initialize_security(
@@ -3142,6 +3102,9 @@ finish:
         __lsan_do_leak_check();
 #endif
 
+        if (r < 0)
+                (void) sd_notifyf(0, "ERRNO=%i", -r);
+
         /* Try to invoke the shutdown binary unless we already failed.
          * If we failed above, we want to freeze after finishing cleanup. */
         if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM &&
@@ -3151,6 +3114,10 @@ finish:
                 error_message = "Failed to execute shutdown binary";
         }
 
+        /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with
+         * a mechanism to pick up systemd's exit status in the VM. */
+        (void) sd_notifyf(0, "EXIT_STATUS=%i", retval);
+
         watchdog_free_device();
         arg_watchdog_device = mfree(arg_watchdog_device);
 
index 61a464c06bfe041b652702e2ca9e6beb1a7e91d6..080383ced2b40ab9cb34ec7637ea9e39c6eec099 100644 (file)
@@ -174,7 +174,7 @@ int manager_serialize(
         manager_serialize_uid_refs(m, f);
         manager_serialize_gid_refs(m, f);
 
-        r = exec_runtime_serialize(m, f, fds);
+        r = exec_shared_runtime_serialize(m, f, fds);
         if (r < 0)
                 return r;
 
@@ -519,7 +519,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                 else if ((val = startswith(l, "destroy-ipc-gid=")))
                         manager_deserialize_gid_refs_one(m, val);
                 else if ((val = startswith(l, "exec-runtime=")))
-                        (void) exec_runtime_deserialize_one(m, val, fds);
+                        (void) exec_shared_runtime_deserialize_one(m, val, fds);
                 else if ((val = startswith(l, "subscribed="))) {
 
                         if (strv_extend(&m->deserialized_subscribed, val) < 0)
index 14fb2a0f1f890b89e52c07f7c921d28d00aeba98..94416665ec5235c5b6ba0b02c29bba0ca97d5607 100644 (file)
@@ -1250,6 +1250,26 @@ static unsigned manager_dispatch_cleanup_queue(Manager *m) {
         return n;
 }
 
+static unsigned manager_dispatch_release_resources_queue(Manager *m) {
+        unsigned n = 0;
+        Unit *u;
+
+        assert(m);
+
+        while ((u = m->release_resources_queue)) {
+                assert(u->in_release_resources_queue);
+
+                LIST_REMOVE(release_resources_queue, m->release_resources_queue, u);
+                u->in_release_resources_queue = false;
+
+                n++;
+
+                unit_release_resources(u);
+        }
+
+        return n;
+}
+
 enum {
         GC_OFFSET_IN_PATH,  /* This one is on the path we were traveling */
         GC_OFFSET_UNSURE,   /* No clue */
@@ -1389,6 +1409,48 @@ static unsigned manager_dispatch_gc_job_queue(Manager *m) {
         return n;
 }
 
+static int manager_ratelimit_requeue(sd_event_source *s, uint64_t usec, void *userdata) {
+        Unit *u = userdata;
+
+        assert(u);
+        assert(s == u->auto_start_stop_event_source);
+
+        u->auto_start_stop_event_source = sd_event_source_unref(u->auto_start_stop_event_source);
+
+        /* Re-queue to all queues, if the rate limit hit we might have been throttled on any of them. */
+        unit_submit_to_stop_when_unneeded_queue(u);
+        unit_submit_to_start_when_upheld_queue(u);
+        unit_submit_to_stop_when_bound_queue(u);
+
+        return 0;
+}
+
+static int manager_ratelimit_check_and_queue(Unit *u) {
+        int r;
+
+        assert(u);
+
+        if (ratelimit_below(&u->auto_start_stop_ratelimit))
+                return 1;
+
+        /* Already queued, no need to requeue */
+        if (u->auto_start_stop_event_source)
+                return 0;
+
+        r = sd_event_add_time(
+                        u->manager->event,
+                        &u->auto_start_stop_event_source,
+                        CLOCK_MONOTONIC,
+                        ratelimit_end(&u->auto_start_stop_ratelimit),
+                        0,
+                        manager_ratelimit_requeue,
+                        u);
+        if (r < 0)
+                return log_unit_error_errno(u, r, "Failed to queue timer on event loop: %m");
+
+        return 0;
+}
+
 static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
         unsigned n = 0;
         Unit *u;
@@ -1413,8 +1475,11 @@ static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
                 /* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
                  * service being unnecessary after a while. */
 
-                if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
-                        log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
+                r = manager_ratelimit_check_and_queue(u);
+                if (r <= 0) {
+                        log_unit_warning(u,
+                                         "Unit not needed anymore, but not stopping since we tried this too often recently.%s",
+                                         r == 0 ? " Will retry later." : "");
                         continue;
                 }
 
@@ -1452,8 +1517,12 @@ static unsigned manager_dispatch_start_when_upheld_queue(Manager *m) {
                 /* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
                  * service being unnecessary after a while. */
 
-                if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
-                        log_unit_warning(u, "Unit needs to be started because active unit %s upholds it, but not starting since we tried this too often recently.", culprit->id);
+                r = manager_ratelimit_check_and_queue(u);
+                if (r <= 0) {
+                        log_unit_warning(u,
+                                         "Unit needs to be started because active unit %s upholds it, but not starting since we tried this too often recently.%s",
+                                         culprit->id,
+                                         r == 0 ? " Will retry later." : "");
                         continue;
                 }
 
@@ -1490,8 +1559,12 @@ static unsigned manager_dispatch_stop_when_bound_queue(Manager *m) {
                 /* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
                  * service being unnecessary after a while. */
 
-                if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
-                        log_unit_warning(u, "Unit needs to be stopped because it is bound to inactive unit %s it, but not stopping since we tried this too often recently.", culprit->id);
+                r = manager_ratelimit_check_and_queue(u);
+                if (r <= 0) {
+                        log_unit_warning(u,
+                                         "Unit needs to be stopped because it is bound to inactive unit %s it, but not stopping since we tried this too often recently.%s",
+                                         culprit->id,
+                                         r == 0 ? " Will retry later." : "");
                         continue;
                 }
 
@@ -1527,6 +1600,7 @@ static void manager_clear_jobs_and_units(Manager *m) {
         assert(!m->stop_when_unneeded_queue);
         assert(!m->start_when_upheld_queue);
         assert(!m->stop_when_bound_queue);
+        assert(!m->release_resources_queue);
 
         assert(hashmap_isempty(m->jobs));
         assert(hashmap_isempty(m->units));
@@ -1555,8 +1629,8 @@ Manager* manager_free(Manager *m) {
         bus_done(m);
         manager_varlink_done(m);
 
-        exec_runtime_vacuum(m);
-        hashmap_free(m->exec_runtime_by_id);
+        exec_shared_runtime_vacuum(m);
+        hashmap_free(m->exec_shared_runtime_by_id);
 
         dynamic_user_vacuum(m, false);
         hashmap_free(m->dynamic_users);
@@ -1861,6 +1935,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo
                 /* This block is (optionally) done with the reloading counter bumped */
                 _unused_ _cleanup_(manager_reloading_stopp) Manager *reloading = NULL;
 
+                /* Make sure we don't have a left-over from a previous run */
+                if (!serialization)
+                        (void) rm_rf(m->lookup_paths.transient, 0);
+
                 /* If we will deserialize make sure that during enumeration this is already known, so we increase the
                  * counter here already */
                 if (serialization)
@@ -2552,7 +2630,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
                 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
 
                         assert(!fd_array);
-                        fd_array = (int*) CMSG_DATA(cmsg);
+                        fd_array = CMSG_TYPED_DATA(cmsg, int);
                         n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
 
                 } else if (cmsg->cmsg_level == SOL_SOCKET &&
@@ -2560,7 +2638,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
                            cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
 
                         assert(!ucred);
-                        ucred = (struct ucred*) CMSG_DATA(cmsg);
+                        ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
                 }
         }
 
@@ -3154,6 +3232,9 @@ int manager_loop(Manager *m) {
                 if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
                         continue;
 
+                if (manager_dispatch_release_resources_queue(m) > 0)
+                        continue;
+
                 if (manager_dispatch_dbus_queue(m) > 0)
                         continue;
 
@@ -3459,7 +3540,7 @@ int manager_reload(Manager *m) {
         manager_clear_jobs_and_units(m);
         lookup_paths_flush_generator(&m->lookup_paths);
         lookup_paths_free(&m->lookup_paths);
-        exec_runtime_vacuum(m);
+        exec_shared_runtime_vacuum(m);
         dynamic_user_vacuum(m, false);
         m->uid_refs = hashmap_free(m->uid_refs);
         m->gid_refs = hashmap_free(m->gid_refs);
@@ -4540,7 +4621,7 @@ static void manager_vacuum(Manager *m) {
         manager_vacuum_gid_refs(m);
 
         /* Release any runtimes no longer referenced */
-        exec_runtime_vacuum(m);
+        exec_shared_runtime_vacuum(m);
 }
 
 int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
index 25fcd275d4e647240bfd20ce754970d27840e9c3..486eda11b604be3e9f2cfd285883e0ae8f9c3b26 100644 (file)
@@ -195,6 +195,9 @@ struct Manager {
         /* Units that have BindsTo= another unit, and might need to be shutdown because the bound unit is not active. */
         LIST_HEAD(Unit, stop_when_bound_queue);
 
+        /* Units that have resources open, and where it might be good to check if they can be released now */
+        LIST_HEAD(Unit, release_resources_queue);
+
         sd_event *event;
 
         /* This maps PIDs we care about to units that are interested in. We allow multiple units to be interested in
@@ -429,8 +432,8 @@ struct Manager {
         Hashmap *uid_refs;
         Hashmap *gid_refs;
 
-        /* ExecRuntime, indexed by their owner unit id */
-        Hashmap *exec_runtime_by_id;
+        /* ExecSharedRuntime, indexed by their owner unit id */
+        Hashmap *exec_shared_runtime_by_id;
 
         /* When the user hits C-A-D more than 7 times per 2s, do something immediately... */
         RateLimit ctrl_alt_del_ratelimit;
index e68c55917f8ae53f48a3f662f7c6d6e4cb3acd6d..1a1b8b310d642a8d542f2ab5b499da0a9e5e0d01 100644 (file)
@@ -74,18 +74,15 @@ if conf.get('BPF_FRAMEWORK') == 1
         )
 endif
 
-subdir('bpf')
-
 subdir('bpf/socket_bind')
-if conf.get('BPF_FRAMEWORK') == 1
-        libcore_sources += [socket_bind_skel_h]
-        subdir('bpf/restrict_fs')
-        libcore_sources += [restrict_fs_skel_h]
-endif
-
+subdir('bpf/restrict_fs')
 subdir('bpf/restrict_ifaces')
+
 if conf.get('BPF_FRAMEWORK') == 1
-        libcore_sources += [restrict_ifaces_skel_h]
+        libcore_sources += [
+                socket_bind_skel_h,
+                restrict_fs_skel_h,
+                restrict_ifaces_skel_h]
 endif
 
 load_fragment_gperf_gperf = custom_target(
@@ -126,6 +123,7 @@ libcore = shared_library(
                         libblkid,
                         libdl,
                         libkmod,
+                        libm,
                         libmount,
                         libpam,
                         librt,
index bbe22269469ffe77e641e16632f8cf31b6388067..586151bf67bbacdd30affef314dfedd23f7def65 100644 (file)
@@ -254,12 +254,10 @@ static void mount_done(Unit *u) {
         mount_parameters_done(&m->parameters_proc_self_mountinfo);
         mount_parameters_done(&m->parameters_fragment);
 
-        m->exec_runtime = exec_runtime_unref(m->exec_runtime, false);
+        m->exec_runtime = exec_runtime_free(m->exec_runtime);
         exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX);
         m->control_command = NULL;
 
-        dynamic_creds_unref(&m->dynamic_creds);
-
         mount_unwatch_control_pid(m);
 
         m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source);
@@ -790,10 +788,8 @@ static int mount_coldplug(Unit *u) {
                         return r;
         }
 
-        if (!IN_SET(m->deserialized_state, MOUNT_DEAD, MOUNT_FAILED)) {
-                (void) unit_setup_dynamic_creds(u);
+        if (!IN_SET(m->deserialized_state, MOUNT_DEAD, MOUNT_FAILED))
                 (void) unit_setup_exec_runtime(u);
-        }
 
         mount_set_state(m, m->deserialized_state);
         return 0;
@@ -922,7 +918,6 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
                        &m->exec_context,
                        &exec_params,
                        m->exec_runtime,
-                       &m->dynamic_creds,
                        &m->cgroup_context,
                        &pid);
         if (r < 0)
@@ -948,14 +943,12 @@ static void mount_enter_dead(Mount *m, MountResult f) {
 
         mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
 
-        m->exec_runtime = exec_runtime_unref(m->exec_runtime, true);
+        m->exec_runtime = exec_runtime_destroy(m->exec_runtime);
 
         unit_destroy_runtime_data(UNIT(m), &m->exec_context);
 
         unit_unref_uid_gid(UNIT(m), true);
 
-        dynamic_creds_destroy(&m->dynamic_creds);
-
         /* Any dependencies based on /proc/self/mountinfo are now stale. Let's re-generate dependencies from
          * .mount unit. */
         (void) mount_add_non_exec_dependencies(m);
@@ -2268,7 +2261,6 @@ const UnitVTable mount_vtable = {
         .cgroup_context_offset = offsetof(Mount, cgroup_context),
         .kill_context_offset = offsetof(Mount, kill_context),
         .exec_runtime_offset = offsetof(Mount, exec_runtime),
-        .dynamic_creds_offset = offsetof(Mount, dynamic_creds),
 
         .sections =
                 "Unit\0"
index 1a0d9fc5e5921ece427617dd57dbaab32f1d1adb..d6d6d335a45d4879b898fb02428d0b801ebce631 100644 (file)
@@ -76,7 +76,6 @@ struct Mount {
         CGroupContext cgroup_context;
 
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         MountState state, deserialized_state;
 
index 90c0f8442040b1572d7c701e0c6255133f0a884b..1d19685d2eaeb154576da13c5847e9054cce8a2e 100644 (file)
 
 #include "alloc-util.h"
 #include "base-filesystem.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dev-setup.h"
 #include "devnum-util.h"
 #include "env-util.h"
 #include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "format-util.h"
 #include "glyph-util.h"
@@ -1240,7 +1240,10 @@ static int mount_mqueuefs(const MountEntry *m) {
         return 0;
 }
 
-static int mount_image(const MountEntry *m, const char *root_directory) {
+static int mount_image(
+                const MountEntry *m,
+                const char *root_directory,
+                const ImagePolicy *image_policy) {
 
         _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
                             *host_os_release_sysext_level = NULL;
@@ -1262,8 +1265,15 @@ static int mount_image(const MountEntry *m, const char *root_directory) {
         }
 
         r = verity_dissect_and_mount(
-                        /* src_fd= */ -1, mount_entry_source(m), mount_entry_path(m), m->image_options,
-                        host_os_release_id, host_os_release_version_id, host_os_release_sysext_level, NULL);
+                        /* src_fd= */ -1,
+                        mount_entry_source(m),
+                        mount_entry_path(m),
+                        m->image_options,
+                        image_policy,
+                        host_os_release_id,
+                        host_os_release_version_id,
+                        host_os_release_sysext_level,
+                        NULL);
         if (r == -ENOENT && m->ignore)
                 return 0;
         if (r == -ESTALE && host_os_release_id)
@@ -1312,13 +1322,13 @@ static int follow_symlink(
          * a time by specifying CHASE_STEP. This function returns 0 if we resolved one step, and > 0 if we reached the
          * end and already have a fully normalized name. */
 
-        r = chase_symlinks(mount_entry_path(m), root_directory, CHASE_STEP|CHASE_NONEXISTENT, &target, NULL);
+        r = chase(mount_entry_path(m), root_directory, CHASE_STEP|CHASE_NONEXISTENT, &target, NULL);
         if (r < 0)
                 return log_debug_errno(r, "Failed to chase symlinks '%s': %m", mount_entry_path(m));
         if (r > 0) /* Reached the end, nothing more to resolve */
                 return 1;
 
-        if (m->n_followed >= CHASE_SYMLINKS_MAX) /* put a boundary on things */
+        if (m->n_followed >= CHASE_MAX) /* put a boundary on things */
                 return log_debug_errno(SYNTHETIC_ERRNO(ELOOP),
                                        "Symlink loop on '%s'.",
                                        mount_entry_path(m));
@@ -1336,6 +1346,8 @@ static int follow_symlink(
 static int apply_one_mount(
                 const char *root_directory,
                 MountEntry *m,
+                const ImagePolicy *mount_image_policy,
+                const ImagePolicy *extension_image_policy,
                 const NamespaceInfo *ns_info) {
 
         _cleanup_free_ char *inaccessible = NULL;
@@ -1423,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)
@@ -1435,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)
@@ -1453,9 +1466,9 @@ static int apply_one_mount(
 
                 /* Since mount() will always follow symlinks we chase the symlinks on our own first. Note
                  * that bind mount source paths are always relative to the host root, hence we pass NULL as
-                 * root directory to chase_symlinks() here. */
+                 * root directory to chase() here. */
 
-                r = chase_symlinks(mount_entry_source(m), NULL, CHASE_TRAIL_SLASH, &chased, NULL);
+                r = chase(mount_entry_source(m), NULL, CHASE_TRAIL_SLASH, &chased, NULL);
                 if (r == -ENOENT && m->ignore) {
                         log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_source(m));
                         return 0;
@@ -1505,10 +1518,10 @@ static int apply_one_mount(
                 return mount_mqueuefs(m);
 
         case MOUNT_IMAGES:
-                return mount_image(m, NULL);
+                return mount_image(m, NULL, mount_image_policy);
 
         case EXTENSION_IMAGES:
-                return mount_image(m, root_directory);
+                return mount_image(m, root_directory, extension_image_policy);
 
         case OVERLAY_MOUNT:
                 return mount_overlay(m);
@@ -1778,6 +1791,8 @@ static int create_symlinks_from_tuples(const char *root, char **strv_symlinks) {
 
 static int apply_mounts(
                 const char *root,
+                const ImagePolicy *mount_image_policy,
+                const ImagePolicy *extension_image_policy,
                 const NamespaceInfo *ns_info,
                 MountEntry *mounts,
                 size_t *n_mounts,
@@ -1832,7 +1847,7 @@ static int apply_mounts(
                                 break;
                         }
 
-                        r = apply_one_mount(root, m, ns_info);
+                        r = apply_one_mount(root, m, mount_image_policy, extension_image_policy, ns_info);
                         if (r < 0) {
                                 if (error_path && mount_entry_path(m))
                                         *error_path = strdup(mount_entry_path(m));
@@ -2011,7 +2026,8 @@ static int verity_settings_prepare(
 int setup_namespace(
                 const char* root_directory,
                 const char* root_image,
-                const MountOptions *root_image_options,
+                const MountOptions *root_image_mount_options,
+                const ImagePolicy *root_image_policy,
                 const NamespaceInfo *ns_info,
                 char** read_write_paths,
                 char** read_only_paths,
@@ -2026,6 +2042,7 @@ int setup_namespace(
                 size_t n_temporary_filesystems,
                 const MountImage *mount_images,
                 size_t n_mount_images,
+                const ImagePolicy *mount_image_policy,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 const char *creds_path,
@@ -2040,6 +2057,7 @@ int setup_namespace(
                 const char *verity_data_path,
                 const MountImage *extension_images,
                 size_t n_extension_images,
+                const ImagePolicy *extension_image_policy,
                 char **extension_directories,
                 const char *propagate_dir,
                 const char *incoming_dir,
@@ -2113,7 +2131,8 @@ int setup_namespace(
                 r = dissect_loop_device(
                                 loop_device,
                                 &verity,
-                                root_image_options,
+                                root_image_mount_options,
+                                root_image_policy,
                                 dissect_image_flags,
                                 &dissected_image);
                 if (r < 0)
@@ -2155,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;
         }
@@ -2501,7 +2520,7 @@ int setup_namespace(
                 (void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
 
         /* Now make the magic happen */
-        r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
+        r = apply_mounts(root, mount_image_policy, extension_image_policy, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
         if (r < 0)
                 goto finish;
 
index 1cd4fdd921332509d58560221f85c3cc6ad5ae49..39b510f41d9723c87f90820d1639ea45f815daef 100644 (file)
@@ -103,6 +103,7 @@ int setup_namespace(
                 const char *root_directory,
                 const char *root_image,
                 const MountOptions *root_image_options,
+                const ImagePolicy *root_image_policy,
                 const NamespaceInfo *ns_info,
                 char **read_write_paths,
                 char **read_only_paths,
@@ -117,6 +118,7 @@ int setup_namespace(
                 size_t n_temporary_filesystems,
                 const MountImage *mount_images,
                 size_t n_mount_images,
+                const ImagePolicy *mount_image_policy,
                 const char *tmp_dir,
                 const char *var_tmp_dir,
                 const char *creds_path,
@@ -131,6 +133,7 @@ int setup_namespace(
                 const char *root_verity,
                 const MountImage *extension_images,
                 size_t n_extension_images,
+                const ImagePolicy *extension_image_policy,
                 char **extension_directories,
                 const char *propagate_dir,
                 const char *incoming_dir,
index 510bb28c9375f875bbd3456bd8a07caede6ca73a..5f3b62e02194f62da80f4d8986bc60bf4de26407 100644 (file)
@@ -116,7 +116,7 @@ static void scope_set_state(Scope *s, ScopeState state) {
         old_state = s->state;
         s->state = state;
 
-        if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL, SCOPE_START_CHOWN))
+        if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL, SCOPE_START_CHOWN, SCOPE_RUNNING))
                 s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
 
         if (IN_SET(state, SCOPE_DEAD, SCOPE_FAILED)) {
index addd4e66a730eb9a9945eed3f0c5adeda38e1d58..3e4febeaa2bf6be0468b8dc8f2a83417343341f2 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <errno.h>
+#include <math.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include "bus-error.h"
 #include "bus-kernel.h"
 #include "bus-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "constants.h"
 #include "dbus-service.h"
 #include "dbus-unit.h"
+#include "devnum-util.h"
 #include "env-util.h"
 #include "escape.h"
 #include "exit-status.h"
@@ -65,6 +67,9 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
         [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
         [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
         [SERVICE_FAILED] = UNIT_FAILED,
+        [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+        [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
+        [SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE,
         [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
         [SERVICE_CLEANING] = UNIT_MAINTENANCE,
 };
@@ -91,6 +96,9 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
         [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
         [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
         [SERVICE_FAILED] = UNIT_FAILED,
+        [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE,
+        [SERVICE_FAILED_BEFORE_AUTO_RESTART] = UNIT_FAILED,
+        [SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE,
         [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
         [SERVICE_CLEANING] = UNIT_MAINTENANCE,
 };
@@ -114,6 +122,7 @@ static void service_init(Unit *u) {
         s->timeout_abort_usec = u->manager->default_timeout_abort_usec;
         s->timeout_abort_set = u->manager->default_timeout_abort_set;
         s->restart_usec = u->manager->default_restart_usec;
+        s->restart_usec_max = USEC_INFINITY;
         s->runtime_max_usec = USEC_INFINITY;
         s->type = _SERVICE_TYPE_INVALID;
         s->socket_fd = -EBADF;
@@ -125,11 +134,15 @@ static void service_init(Unit *u) {
         s->exec_context.keyring_mode = MANAGER_IS_SYSTEM(u->manager) ?
                 EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT;
 
+        s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+
         s->watchdog_original_usec = USEC_INFINITY;
 
         s->oom_policy = _OOM_POLICY_INVALID;
         s->reload_begin_usec = USEC_INFINITY;
         s->reload_signal = SIGHUP;
+
+        s->fd_store_preserve_mode = EXEC_PRESERVE_RESTART;
 }
 
 static void service_unwatch_control_pid(Service *s) {
@@ -187,9 +200,14 @@ static int service_set_main_pid(Service *s, pid_t pid) {
         return 0;
 }
 
-void service_close_socket_fd(Service *s) {
+void service_release_socket_fd(Service *s) {
         assert(s);
 
+        if (s->socket_fd < 0 && !UNIT_ISSET(s->accept_socket) && !s->socket_peer)
+                return;
+
+        log_unit_debug(UNIT(s), "Closing connection socket.");
+
         /* Undo the effect of service_set_socket_fd(). */
 
         s->socket_fd = asynchronous_close(s->socket_fd);
@@ -202,6 +220,15 @@ void service_close_socket_fd(Service *s) {
         s->socket_peer = socket_peer_unref(s->socket_peer);
 }
 
+static void service_override_notify_access(Service *s, NotifyAccess notify_access_override) {
+        assert(s);
+
+        s->notify_access_override = notify_access_override;
+
+        log_unit_debug(UNIT(s), "notify_access=%s", notify_access_to_string(s->notify_access));
+        log_unit_debug(UNIT(s), "notify_access_override=%s", notify_access_to_string(s->notify_access_override));
+}
+
 static void service_stop_watchdog(Service *s) {
         assert(s);
 
@@ -251,6 +278,40 @@ static void service_start_watchdog(Service *s) {
                 log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m");
 }
 
+usec_t service_restart_usec_next(Service *s) {
+        unsigned n_restarts_next;
+        usec_t value;
+
+        assert(s);
+
+        /* When the service state is in SERVICE_*_BEFORE_AUTO_RESTART or SERVICE_AUTO_RESTART,
+         * we still need to add 1 to s->n_restarts manually because s->n_restarts is not updated
+         * until a restart job is enqueued. Note that for SERVICE_AUTO_RESTART, that might have been
+         * the case, i.e. s->n_restarts is already increased. But we assume it's not since the time
+         * between job enqueuing and running is usually neglectable compared to the time we'll be sleeping. */
+        n_restarts_next = s->n_restarts + 1;
+
+        if (n_restarts_next <= 1 ||
+            s->restart_steps == 0 ||
+            s->restart_usec_max == USEC_INFINITY ||
+            s->restart_usec >= s->restart_usec_max)
+                value = s->restart_usec;
+        else if (n_restarts_next > s->restart_steps)
+                value = s->restart_usec_max;
+        else {
+                /* Enforced in service_verify() and above */
+                assert(s->restart_usec_max > s->restart_usec);
+
+                /* ((restart_usec_max - restart_usec)^(1/restart_steps))^(n_restart_next - 1) */
+                value = usec_add(s->restart_usec,
+                                 (usec_t) powl(s->restart_usec_max - s->restart_usec,
+                                               (long double) (n_restarts_next - 1) / s->restart_steps));
+        }
+
+        log_unit_debug(UNIT(s), "Next restart interval calculated as: %s", FORMAT_TIMESPAN(value, 0));
+        return value;
+}
+
 static void service_extend_event_source_timeout(Service *s, sd_event_source *source, usec_t extended) {
         usec_t current;
         int r;
@@ -328,40 +389,36 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
         sd_event_source_disable_unref(fs->event_source);
 
         free(fs->fdname);
-        safe_close(fs->fd);
+        asynchronous_close(fs->fd);
         free(fs);
 }
 
 static void service_release_fd_store(Service *s) {
         assert(s);
 
-        if (s->n_keep_fd_store > 0)
+        if (!s->fd_store)
                 return;
 
         log_unit_debug(UNIT(s), "Releasing all stored fds");
+
         while (s->fd_store)
                 service_fd_store_unlink(s->fd_store);
 
         assert(s->n_fd_store == 0);
 }
 
-static void service_release_resources(Unit *u) {
-        Service *s = SERVICE(u);
-
+static void service_release_stdio_fd(Service *s) {
         assert(s);
 
-        if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
+        if (s->stdin_fd < 0 && s->stdout_fd < 0 && s->stdout_fd < 0)
                 return;
 
-        log_unit_debug(u, "Releasing resources.");
+        log_unit_debug(UNIT(s), "Releasing stdin/stdout/stderr file descriptors.");
 
-        s->stdin_fd = safe_close(s->stdin_fd);
-        s->stdout_fd = safe_close(s->stdout_fd);
-        s->stderr_fd = safe_close(s->stderr_fd);
-
-        service_release_fd_store(s);
+        s->stdin_fd = asynchronous_close(s->stdin_fd);
+        s->stdout_fd = asynchronous_close(s->stdout_fd);
+        s->stderr_fd = asynchronous_close(s->stderr_fd);
 }
-
 static void service_done(Unit *u) {
         Service *s = SERVICE(u);
 
@@ -372,13 +429,11 @@ static void service_done(Unit *u) {
         s->pid_file = mfree(s->pid_file);
         s->status_text = mfree(s->status_text);
 
-        s->exec_runtime = exec_runtime_unref(s->exec_runtime, false);
+        s->exec_runtime = exec_runtime_free(s->exec_runtime);
         exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
         s->control_command = NULL;
         s->main_command = NULL;
 
-        dynamic_creds_unref(&s->dynamic_creds);
-
         exit_status_set_free(&s->restart_prevent_status);
         exit_status_set_free(&s->restart_force_status);
         exit_status_set_free(&s->success_status);
@@ -399,10 +454,6 @@ static void service_done(Unit *u) {
         s->usb_function_descriptors = mfree(s->usb_function_descriptors);
         s->usb_function_strings = mfree(s->usb_function_strings);
 
-        service_close_socket_fd(s);
-
-        unit_ref_unset(&s->accept_socket);
-
         service_stop_watchdog(s);
 
         s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
@@ -410,7 +461,9 @@ static void service_done(Unit *u) {
 
         s->bus_name_pid_lookup_slot = sd_bus_slot_unref(s->bus_name_pid_lookup_slot);
 
-        service_release_resources(u);
+        service_release_socket_fd(s);
+        service_release_stdio_fd(s);
+        service_release_fd_store(s);
 }
 
 static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
@@ -428,6 +481,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us
 }
 
 static int service_add_fd_store(Service *s, int fd, const char *name, bool do_poll) {
+        struct stat st;
         ServiceFDStore *fs;
         int r;
 
@@ -436,17 +490,23 @@ static int service_add_fd_store(Service *s, int fd, const char *name, bool do_po
         assert(s);
         assert(fd >= 0);
 
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        log_unit_debug(UNIT(s), "Trying to stash fd for dev=" DEVNUM_FORMAT_STR "/inode=%" PRIu64, DEVNUM_FORMAT_VAL(st.st_dev), (uint64_t) st.st_ino);
+
         if (s->n_fd_store >= s->n_fd_store_max)
-                return -EXFULL; /* Our store is full.
-                                 * Use this errno rather than E[NM]FILE to distinguish from
-                                 * the case where systemd itself hits the file limit. */
+                /* Our store is full.  Use this errno rather than E[NM]FILE to distinguish from the case
+                 * where systemd itself hits the file limit. */
+                return log_unit_debug_errno(UNIT(s), SYNTHETIC_ERRNO(EXFULL), "Hit fd store limit.");
 
         LIST_FOREACH(fd_store, i, s->fd_store) {
                 r = same_fd(i->fd, fd);
                 if (r < 0)
                         return r;
                 if (r > 0) {
-                        safe_close(fd);
+                        log_unit_debug(UNIT(s), "Suppressing duplicate fd in fd store.");
+                        asynchronous_close(fd);
                         return 0; /* fd already included */
                 }
         }
@@ -489,7 +549,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo
         assert(s);
 
         while (fdset_size(fds) > 0) {
-                _cleanup_close_ int fd = -EBADF;
+                _cleanup_(asynchronous_closep) int fd = -EBADF;
 
                 fd = fdset_steal_first(fds);
                 if (fd < 0)
@@ -504,7 +564,8 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo
                         return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m");
                 if (r > 0)
                         log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fd, strna(name));
-                fd = -EBADF;
+
+                TAKE_FD(fd);
         }
 
         return 0;
@@ -633,6 +694,17 @@ static int service_verify(Service *s) {
         if (s->exit_type == SERVICE_EXIT_CGROUP && cg_unified() < CGROUP_UNIFIED_SYSTEMD)
                 log_unit_warning(UNIT(s), "Service has ExitType=cgroup set, but we are running with legacy cgroups v1, which might not work correctly. Continuing.");
 
+        if (s->restart_usec_max == USEC_INFINITY && s->restart_steps > 0)
+                log_unit_warning(UNIT(s), "Service has RestartSteps= but no RestartSecMax= setting. Ignoring.");
+
+        if (s->restart_usec_max != USEC_INFINITY && s->restart_steps == 0)
+                log_unit_warning(UNIT(s), "Service has RestartSecMax= but no RestartSteps= setting. Ignoring.");
+
+        if (s->restart_usec_max < s->restart_usec) {
+                log_unit_warning(UNIT(s), "RestartSecMax= has a value smaller than RestartSec=, resetting RestartSec= to RestartSecMax=.");
+                s->restart_usec = s->restart_usec_max;
+        }
+
         return 0;
 }
 
@@ -815,8 +887,43 @@ static int service_load(Unit *u) {
         return service_verify(s);
 }
 
+static void service_dump_fdstore(Service *s, FILE *f, const char *prefix) {
+        assert(s);
+        assert(f);
+        assert(prefix);
+
+        LIST_FOREACH(fd_store, i, s->fd_store) {
+                _cleanup_free_ char *path = NULL;
+                struct stat st;
+                int flags;
+
+                if (fstat(i->fd, &st) < 0) {
+                        log_debug_errno(errno, "Failed to stat fdstore entry: %m");
+                        continue;
+                }
+
+                flags = fcntl(i->fd, F_GETFL);
+                if (flags < 0) {
+                        log_debug_errno(errno, "Failed to get fdstore entry flags: %m");
+                        continue;
+                }
+
+                (void) fd_get_path(i->fd, &path);
+
+                fprintf(f,
+                        "%s%s '%s' (type=%s; dev=" DEVNUM_FORMAT_STR "; inode=%" PRIu64 "; rdev=" DEVNUM_FORMAT_STR "; path=%s; access=%s)\n",
+                        prefix, i == s->fd_store ? "File Descriptor Store Entry:" : "                            ",
+                        i->fdname,
+                        inode_type_to_string(st.st_mode),
+                        DEVNUM_FORMAT_VAL(st.st_dev),
+                        (uint64_t) st.st_ino,
+                        DEVNUM_FORMAT_VAL(st.st_rdev),
+                        strna(path),
+                        accmode_to_string(flags));
+        }
+}
+
 static void service_dump(Unit *u, FILE *f, const char *prefix) {
-        ServiceExecCommand c;
         Service *s = SERVICE(u);
         const char *prefix2;
 
@@ -850,7 +957,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, yes_no(s->guess_main_pid),
                 prefix, service_type_to_string(s->type),
                 prefix, service_restart_to_string(s->restart),
-                prefix, notify_access_to_string(s->notify_access),
+                prefix, notify_access_to_string(service_get_notify_access(s)),
                 prefix, notify_state_to_string(s->notify_state),
                 prefix, oom_policy_to_string(s->oom_policy),
                 prefix, signal_to_string(s->reload_signal));
@@ -888,11 +995,15 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
 
         fprintf(f,
                 "%sRestartSec: %s\n"
+                "%sRestartSteps: %u\n"
+                "%sRestartSecMax: %s\n"
                 "%sTimeoutStartSec: %s\n"
                 "%sTimeoutStopSec: %s\n"
                 "%sTimeoutStartFailureMode: %s\n"
                 "%sTimeoutStopFailureMode: %s\n",
                 prefix, FORMAT_TIMESPAN(s->restart_usec, USEC_PER_SEC),
+                prefix, s->restart_steps,
+                prefix, FORMAT_TIMESPAN(s->restart_usec_max, USEC_PER_SEC),
                 prefix, FORMAT_TIMESPAN(s->timeout_start_usec, USEC_PER_SEC),
                 prefix, FORMAT_TIMESPAN(s->timeout_stop_usec, USEC_PER_SEC),
                 prefix, service_timeout_failure_mode_to_string(s->timeout_start_failure_mode),
@@ -914,8 +1025,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
         kill_context_dump(&s->kill_context, f, prefix);
         exec_context_dump(&s->exec_context, f, prefix);
 
-        for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) {
-
+        for (ServiceExecCommand c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) {
                 if (!s->exec_command[c])
                         continue;
 
@@ -932,10 +1042,14 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
         if (s->n_fd_store_max > 0)
                 fprintf(f,
                         "%sFile Descriptor Store Max: %u\n"
+                        "%sFile Descriptor Store Pin: %s\n"
                         "%sFile Descriptor Store Current: %zu\n",
                         prefix, s->n_fd_store_max,
+                        prefix, exec_preserve_mode_to_string(s->fd_store_preserve_mode),
                         prefix, s->n_fd_store);
 
+        service_dump_fdstore(s, f, prefix);
+
         if (s->open_files)
                 LIST_FOREACH(open_files, of, s->open_files) {
                         _cleanup_free_ char *ofs = NULL;
@@ -996,21 +1110,21 @@ static int service_load_pid_file(Service *s, bool may_warn) {
 
         prio = may_warn ? LOG_INFO : LOG_DEBUG;
 
-        r = chase_symlinks(s->pid_file, NULL, CHASE_SAFE, NULL, &fd);
+        r = chase(s->pid_file, NULL, CHASE_SAFE, NULL, &fd);
         if (r == -ENOLINK) {
                 log_unit_debug_errno(UNIT(s), r,
                                      "Potentially unsafe symlink chain, will now retry with relaxed checks: %s", s->pid_file);
 
                 questionable_pid_file = true;
 
-                r = chase_symlinks(s->pid_file, NULL, 0, NULL, &fd);
+                r = chase(s->pid_file, NULL, 0, NULL, &fd);
         }
         if (r < 0)
                 return log_unit_full_errno(UNIT(s), prio, fd,
                                            "Can't open PID file %s (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state));
 
         /* Let's read the PID file now that we chased it down. But we need to convert the O_PATH fd
-         * chase_symlinks() returned us into a proper fd first. */
+         * chase() returned us into a proper fd first. */
         r = read_one_line_file(FORMAT_PROC_FD_PATH(fd), &k);
         if (r < 0)
                 return log_unit_error_errno(UNIT(s), r,
@@ -1141,20 +1255,14 @@ static void service_set_state(Service *s, ServiceState state) {
                 s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
         }
 
-        if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
+        if (IN_SET(state,
+                   SERVICE_DEAD, SERVICE_FAILED,
+                   SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
+                   SERVICE_DEAD_RESOURCES_PINNED)) {
                 unit_unwatch_all_pids(UNIT(s));
                 unit_dequeue_rewatch_pids(UNIT(s));
         }
 
-        if (!IN_SET(state,
-                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
-                    SERVICE_RUNNING,
-                    SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
-                    SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
-                    SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) &&
-            !(state == SERVICE_DEAD && UNIT(s)->job))
-                service_close_socket_fd(s);
-
         if (state != SERVICE_START)
                 s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
 
@@ -1204,7 +1312,7 @@ static usec_t service_coldplug_timeout(Service *s) {
                 return usec_add(UNIT(s)->state_change_timestamp.monotonic, service_timeout_abort_usec(s));
 
         case SERVICE_AUTO_RESTART:
-                return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec);
+                return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, service_restart_usec_next(s));
 
         case SERVICE_CLEANING:
                 return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->exec_context.timeout_clean_usec);
@@ -1254,9 +1362,12 @@ static int service_coldplug(Unit *u) {
                         return r;
         }
 
-        if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) {
+        if (!IN_SET(s->deserialized_state,
+                    SERVICE_DEAD, SERVICE_FAILED,
+                    SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART,
+                    SERVICE_CLEANING,
+                    SERVICE_DEAD_RESOURCES_PINNED)) {
                 (void) unit_enqueue_rewatch_pids(u);
-                (void) unit_setup_dynamic_creds(u);
                 (void) unit_setup_exec_runtime(u);
         }
 
@@ -1303,10 +1414,9 @@ static int service_collect_fds(
 
                 /* Pass the per-connection socket */
 
-                rfds = new(int, 1);
+                rfds = newdup(int, &s->socket_fd, 1);
                 if (!rfds)
                         return -ENOMEM;
-                rfds[0] = s->socket_fd;
 
                 rfd_names = strv_new("connection");
                 if (!rfd_names)
@@ -1465,11 +1575,11 @@ static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) {
 
         if (flags & EXEC_IS_CONTROL)
                 /* A control process */
-                return IN_SET(s->notify_access, NOTIFY_EXEC, NOTIFY_ALL);
+                return IN_SET(service_get_notify_access(s), NOTIFY_EXEC, NOTIFY_ALL);
 
         /* We only spawn main processes and control processes, so any
          * process that is not a control process is a main process */
-        return s->notify_access != NOTIFY_NONE;
+        return service_get_notify_access(s) != NOTIFY_NONE;
 }
 
 static Service *service_get_triggering_service(Service *s) {
@@ -1574,7 +1684,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;
 
@@ -1583,6 +1693,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)
@@ -1708,7 +1822,6 @@ static int service_spawn_internal(
                        &s->exec_context,
                        &exec_params,
                        s->exec_runtime,
-                       &s->dynamic_creds,
                        &s->cgroup_context,
                        &pid);
         if (r < 0)
@@ -1835,14 +1948,20 @@ static bool service_will_restart(Unit *u) {
 
         if (s->will_auto_restart)
                 return true;
-        if (s->state == SERVICE_AUTO_RESTART)
+        if (IN_SET(s->state, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
                 return true;
 
         return unit_will_restart_default(u);
 }
 
+static ServiceState service_determine_dead_state(Service *s) {
+        assert(s);
+
+        return s->fd_store && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD;
+}
+
 static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
-        ServiceState end_state;
+        ServiceState end_state, restart_state;
         int r;
 
         assert(s);
@@ -1857,13 +1976,16 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
 
         if (s->result == SERVICE_SUCCESS) {
                 unit_log_success(UNIT(s));
-                end_state = SERVICE_DEAD;
+                end_state = service_determine_dead_state(s);
+                restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
         } else if (s->result == SERVICE_SKIP_CONDITION) {
                 unit_log_skip(UNIT(s), service_result_to_string(s->result));
-                end_state = SERVICE_DEAD;
+                end_state = service_determine_dead_state(s);
+                restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART;
         } else {
                 unit_log_failure(UNIT(s), service_result_to_string(s->result));
                 end_state = SERVICE_FAILED;
+                restart_state = SERVICE_FAILED_BEFORE_AUTO_RESTART;
         }
         unit_warn_leftover_processes(UNIT(s), unit_log_leftover_process_stop);
 
@@ -1881,47 +2003,54 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
                         s->will_auto_restart = true;
         }
 
-        /* Make sure service_release_resources() doesn't destroy our FD store, while we are changing through
-         * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
-        s->n_keep_fd_store ++;
-
-        service_set_state(s, end_state);
-
         if (s->will_auto_restart) {
                 s->will_auto_restart = false;
 
-                r = service_arm_timer(s, /* relative= */ true, s->restart_usec);
-                if (r < 0) {
-                        s->n_keep_fd_store--;
+                /* We make two state changes here: one that maps to the high-level UNIT_INACTIVE/UNIT_FAILED
+                 * state (i.e. a state indicating deactivation), and then one that that maps to the
+                 * high-level UNIT_STARTING state (i.e. a state indicating activation). We do this so that
+                 * external software can watch the state changes and see all service failures, even if they
+                 * are only transitionary and followed by an automatic restart. We have fine-grained
+                 * low-level states for this though so that software can distinguish the permanent UNIT_INACTIVE
+                 * state from this transitionary UNIT_INACTIVE state by looking at the low-level states. */
+                service_set_state(s, restart_state);
+
+                r = service_arm_timer(s, /* relative= */ true, service_restart_usec_next(s));
+                if (r < 0)
                         goto fail;
-                }
 
                 service_set_state(s, SERVICE_AUTO_RESTART);
-        } else
+        } else {
+                service_set_state(s, end_state);
+
                 /* If we shan't restart, then flush out the restart counter. But don't do that immediately, so that the
                  * user can still introspect the counter. Do so on the next start. */
                 s->flush_n_restarts = true;
+        }
 
         /* The new state is in effect, let's decrease the fd store ref counter again. Let's also re-add us to the GC
          * queue, so that the fd store is possibly gc'ed again */
-        s->n_keep_fd_store--;
         unit_add_to_gc_queue(UNIT(s));
 
         /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */
         s->forbid_restart = false;
 
+        /* Reset NotifyAccess override */
+        s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+
         /* We want fresh tmpdirs in case service is started again immediately */
-        s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
+        s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
 
         /* Also, remove the runtime directory */
         unit_destroy_runtime_data(UNIT(s), &s->exec_context);
 
+        /* Also get rid of the fd store, if that's configured. */
+        if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO)
+                service_release_fd_store(s);
+
         /* Get rid of the IPC bits of the user */
         unit_unref_uid_gid(UNIT(s), true);
 
-        /* Release the user, and destroy it if we are the only remaining owner */
-        dynamic_creds_destroy(&s->dynamic_creds);
-
         /* Try to delete the pid file. At this point it will be
          * out-of-date, and some software might be confused by it, so
          * let's remove it. */
@@ -2404,6 +2533,8 @@ static void service_enter_restart(Service *s) {
         s->n_restarts ++;
         s->flush_n_restarts = false;
 
+        s->notify_access_override = _NOTIFY_ACCESS_INVALID;
+
         log_unit_struct(UNIT(s), LOG_INFO,
                         "MESSAGE_ID=" SD_MESSAGE_UNIT_RESTART_SCHEDULED_STR,
                         LOG_UNIT_INVOCATION_ID(UNIT(s)),
@@ -2518,6 +2649,7 @@ static void service_run_next_control(Service *s) {
                           s->control_command,
                           timeout,
                           EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|
+                          (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD) ? EXEC_WRITE_CREDENTIALS : 0)|
                           (IN_SET(s->control_command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
                           (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0)|
                           (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_START) ? EXEC_SETENV_MONITOR_RESULT : 0)|
@@ -2557,7 +2689,7 @@ static void service_run_next_main(Service *s) {
         r = service_spawn(s,
                           s->main_command,
                           s->timeout_start_usec,
-                          EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_SETENV_MONITOR_RESULT,
+                          EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_SETENV_MONITOR_RESULT|EXEC_WRITE_CREDENTIALS,
                           &pid);
         if (r < 0)
                 goto fail;
@@ -2588,17 +2720,14 @@ static int service_start(Unit *u) {
         if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST))
                 return 0;
 
-        /* A service that will be restarted must be stopped first to
-         * trigger BindsTo and/or OnFailure dependencies. If a user
-         * does not want to wait for the holdoff time to elapse, the
-         * service should be manually restarted, not started. We
-         * simply return EAGAIN here, so that any start jobs stay
-         * queued, and assume that the auto restart timer will
-         * eventually trigger the restart. */
-        if (s->state == SERVICE_AUTO_RESTART)
+        /* A service that will be restarted must be stopped first to trigger BindsTo and/or OnFailure
+         * dependencies. If a user does not want to wait for the holdoff time to elapse, the service should
+         * be manually restarted, not started. We simply return EAGAIN here, so that any start jobs stay
+         * queued, and assume that the auto restart timer will eventually trigger the restart. */
+        if (IN_SET(s->state, SERVICE_AUTO_RESTART, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART))
                 return -EAGAIN;
 
-        assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED));
+        assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED));
 
         r = unit_acquire_invocation_id(u);
         if (r < 0)
@@ -2613,6 +2742,7 @@ static int service_start(Unit *u) {
         s->status_text = mfree(s->status_text);
         s->status_errno = 0;
 
+        s->notify_access_override = _NOTIFY_ACCESS_INVALID;
         s->notify_state = NOTIFY_UNKNOWN;
 
         s->watchdog_original_usec = s->watchdog_usec;
@@ -2642,34 +2772,54 @@ static int service_stop(Unit *u) {
         /* Don't create restart jobs from manual stops. */
         s->forbid_restart = true;
 
-        /* Already on it */
-        if (IN_SET(s->state,
-                   SERVICE_STOP, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
-                   SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
+        switch (s->state) {
+
+        case SERVICE_STOP:
+        case SERVICE_STOP_SIGTERM:
+        case SERVICE_STOP_SIGKILL:
+        case SERVICE_STOP_POST:
+        case SERVICE_FINAL_WATCHDOG:
+        case SERVICE_FINAL_SIGTERM:
+        case SERVICE_FINAL_SIGKILL:
+                /* Already on it */
                 return 0;
 
-        /* A restart will be scheduled or is in progress. */
-        if (s->state == SERVICE_AUTO_RESTART) {
-                service_set_state(s, SERVICE_DEAD);
+        case SERVICE_AUTO_RESTART:
+                /* A restart will be scheduled or is in progress. */
+                service_set_state(s, service_determine_dead_state(s));
                 return 0;
-        }
 
-        /* If there's already something running we go directly into kill mode. */
-        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP_WATCHDOG)) {
+        case SERVICE_CONDITION:
+        case SERVICE_START_PRE:
+        case SERVICE_START:
+        case SERVICE_START_POST:
+        case SERVICE_RELOAD:
+        case SERVICE_RELOAD_SIGNAL:
+        case SERVICE_RELOAD_NOTIFY:
+        case SERVICE_STOP_WATCHDOG:
+                /* If there's already something running we go directly into kill mode. */
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
                 return 0;
-        }
 
-        /* If we are currently cleaning, then abort it, brutally. */
-        if (s->state == SERVICE_CLEANING) {
+        case SERVICE_CLEANING:
+                /* If we are currently cleaning, then abort it, brutally. */
                 service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS);
                 return 0;
-        }
 
-        assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
+        case SERVICE_RUNNING:
+        case SERVICE_EXITED:
+                service_enter_stop(s, SERVICE_SUCCESS);
+                return 1;
 
-        service_enter_stop(s, SERVICE_SUCCESS);
-        return 1;
+        case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+        case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+        case SERVICE_DEAD:
+        case SERVICE_FAILED:
+        case SERVICE_DEAD_RESOURCES_PINNED:
+        default:
+                /* Unknown state, or unit_stop() should already have handled these */
+                assert_not_reached();
+        }
 }
 
 static int service_reload(Unit *u) {
@@ -2864,6 +3014,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
                 }
         }
 
+        if (s->notify_access_override >= 0)
+                (void) serialize_item(f, "notify-access-override", notify_access_to_string(s->notify_access_override));
+
         (void) serialize_dual_timestamp(f, "watchdog-timestamp", &s->watchdog_timestamp);
         (void) serialize_bool(f, "forbid-restart", s->forbid_restart);
 
@@ -3144,7 +3297,15 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
                 deserialize_dual_timestamp(value, &s->main_exec_status.start_timestamp);
         else if (streq(key, "main-exec-status-exit"))
                 deserialize_dual_timestamp(value, &s->main_exec_status.exit_timestamp);
-        else if (streq(key, "watchdog-timestamp"))
+        else if (streq(key, "notify-access-override")) {
+                NotifyAccess notify_access;
+
+                notify_access = notify_access_from_string(value);
+                if (notify_access < 0)
+                        log_unit_debug(u, "Failed to parse notify-access-override value: %s", value);
+                else
+                        s->notify_access_override = notify_access;
+        } else if (streq(key, "watchdog-timestamp"))
                 deserialize_dual_timestamp(value, &s->watchdog_timestamp);
         else if (streq(key, "forbid-restart")) {
                 int b;
@@ -3261,6 +3422,11 @@ static bool service_may_gc(Unit *u) {
             control_pid_good(s) > 0)
                 return false;
 
+        /* Only allow collection of actually dead services, i.e. not those that are in the transitionary
+         * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */
+        if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED))
+                return false;
+
         return true;
 }
 
@@ -3301,30 +3467,30 @@ fail:
 }
 
 static int service_demand_pid_file(Service *s) {
-        PathSpec *ps;
+        _cleanup_free_ PathSpec *ps = NULL;
 
         assert(s->pid_file);
         assert(!s->pid_file_pathspec);
 
-        ps = new0(PathSpec, 1);
+        ps = new(PathSpec, 1);
         if (!ps)
                 return -ENOMEM;
 
-        ps->unit = UNIT(s);
-        ps->path = strdup(s->pid_file);
-        if (!ps->path) {
-                free(ps);
+        *ps = (PathSpec) {
+                .unit = UNIT(s),
+                .path = strdup(s->pid_file),
+                /* PATH_CHANGED would not be enough. There are daemons (sendmail) that keep their PID file
+                 * open all the time. */
+                .type = PATH_MODIFIED,
+                .inotify_fd = -EBADF,
+        };
+
+        if (!ps->path)
                 return -ENOMEM;
-        }
 
         path_simplify(ps->path);
 
-        /* PATH_CHANGED would not be enough. There are daemons (sendmail) that
-         * keep their PID file open all the time. */
-        ps->type = PATH_MODIFIED;
-        ps->inotify_fd = -EBADF;
-
-        s->pid_file_pathspec = ps;
+        s->pid_file_pathspec = TAKE_PTR(ps);
 
         return service_watch_pid_file(s);
 }
@@ -3422,11 +3588,9 @@ static void service_notify_cgroup_empty_event(Unit *u) {
 
         switch (s->state) {
 
-                /* Waiting for SIGCHLD is usually more interesting,
-                 * because it includes return codes/signals. Which is
-                 * why we ignore the cgroup events for most cases,
-                 * except when we don't know pid which to expect the
-                 * SIGCHLD for. */
+                /* Waiting for SIGCHLD is usually more interesting, because it includes return
+                 * codes/signals. Which is why we ignore the cgroup events for most cases, except when we
+                 * don't know pid which to expect the SIGCHLD for. */
 
         case SERVICE_START:
                 if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) &&
@@ -3484,6 +3648,10 @@ static void service_notify_cgroup_empty_event(Unit *u) {
          * up the cgroup earlier and should do it now. */
         case SERVICE_DEAD:
         case SERVICE_FAILED:
+        case SERVICE_DEAD_BEFORE_AUTO_RESTART:
+        case SERVICE_FAILED_BEFORE_AUTO_RESTART:
+        case SERVICE_AUTO_RESTART:
+        case SERVICE_DEAD_RESOURCES_PINNED:
                 unit_prune_cgroup(u);
                 break;
 
@@ -3670,7 +3838,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                                  * has been received */
                                                 if (f != SERVICE_SUCCESS)
                                                         service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
-                                                else if (!s->remain_after_exit || s->notify_access == NOTIFY_MAIN)
+                                                else if (!s->remain_after_exit || service_get_notify_access(s) == NOTIFY_MAIN)
                                                         /* The service has never been and will never be active */
                                                         service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL);
                                                 break;
@@ -4088,8 +4256,8 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
         case SERVICE_AUTO_RESTART:
                 if (s->restart_usec > 0)
                         log_unit_debug(UNIT(s),
-                                       "Service RestartSec=%s expired, scheduling restart.",
-                                       FORMAT_TIMESPAN(s->restart_usec, USEC_PER_SEC));
+                                       "Service restart interval %s expired, scheduling restart.",
+                                       FORMAT_TIMESPAN(service_restart_usec_next(s), USEC_PER_SEC));
                 else
                         log_unit_debug(UNIT(s),
                                        "Service has no hold-off time (RestartSec=0), scheduling restart.");
@@ -4137,12 +4305,14 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void
 static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds) {
         assert(s);
 
-        if (s->notify_access == NOTIFY_NONE) {
+        NotifyAccess notify_access = service_get_notify_access(s);
+
+        if (notify_access == NOTIFY_NONE) {
                 log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception is disabled.", pid);
                 return false;
         }
 
-        if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) {
+        if (notify_access == NOTIFY_MAIN && pid != s->main_pid) {
                 if (s->main_pid != 0)
                         log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
                 else
@@ -4151,7 +4321,7 @@ static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds)
                 return false;
         }
 
-        if (s->notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
+        if (notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
                 if (s->main_pid != 0 && s->control_pid != 0)
                         log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT,
                                          pid, s->main_pid, s->control_pid);
@@ -4193,7 +4363,7 @@ static void service_notify_message(
         assert(u);
         assert(ucred);
 
-        if (!service_notify_message_authorized(SERVICE(u), ucred->pid, fds))
+        if (!service_notify_message_authorized(s, ucred->pid, fds))
                 return;
 
         if (DEBUG_LOGGING) {
@@ -4328,6 +4498,25 @@ static void service_notify_message(
                 }
         }
 
+        /* Interpret NOTIFYACCESS= */
+        e = strv_find_startswith(tags, "NOTIFYACCESS=");
+        if (e) {
+                NotifyAccess notify_access;
+
+                notify_access = notify_access_from_string(e);
+                if (notify_access < 0)
+                        log_unit_warning_errno(u, notify_access,
+                                               "Failed to parse NOTIFYACCESS= field value '%s' in notification message, ignoring: %m", e);
+
+                /* We don't need to check whether the new access mode is more strict than what is
+                 * already in use, since only the privileged process is allowed to change it
+                 * in the first place. */
+                if (service_get_notify_access(s) != notify_access) {
+                        service_override_notify_access(s, notify_access);
+                        notify_dbus = true;
+                }
+        }
+
         /* Interpret ERRNO= */
         e = strv_find_startswith(tags, "ERRNO=");
         if (e) {
@@ -4551,7 +4740,7 @@ int service_set_socket_fd(
 
         assert(!s->socket_peer);
 
-        if (s->state != SERVICE_DEAD)
+        if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED))
                 return -EAGAIN;
 
         if (getpeername_pretty(fd, true, &peer_text) >= 0) {
@@ -4588,7 +4777,7 @@ static void service_reset_failed(Unit *u) {
         assert(s);
 
         if (s->state == SERVICE_FAILED)
-                service_set_state(s, SERVICE_DEAD);
+                service_set_state(s, service_determine_dead_state(s));
 
         s->result = SERVICE_SUCCESS;
         s->reload_result = SERVICE_SUCCESS;
@@ -4676,22 +4865,39 @@ static const char* service_status_text(Unit *u) {
 
 static int service_clean(Unit *u, ExecCleanMask mask) {
         _cleanup_strv_free_ char **l = NULL;
+        bool may_clean_fdstore = false;
         Service *s = SERVICE(u);
         int r;
 
         assert(s);
         assert(mask != 0);
 
-        if (s->state != SERVICE_DEAD)
+        if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED))
                 return -EBUSY;
 
+        /* Determine if there's anything we could potentially clean */
         r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l);
         if (r < 0)
                 return r;
 
-        if (strv_isempty(l))
-                return -EUNATCH;
+        if (mask & EXEC_CLEAN_FDSTORE)
+                may_clean_fdstore = s->n_fd_store > 0 || s->n_fd_store_max > 0;
+
+        if (strv_isempty(l) && !may_clean_fdstore)
+                return -EUNATCH; /* Nothing to potentially clean */
+
+        /* Let's clean the stuff we can clean quickly */
+        if (may_clean_fdstore)
+                service_release_fd_store(s);
 
+        /* If we are done, leave quickly */
+        if (strv_isempty(l)) {
+                if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store)
+                        service_set_state(s, SERVICE_DEAD);
+                return 0;
+        }
+
+        /* We need to clean disk stuff. This is slow, hence do it out of process, and change state */
         service_unwatch_control_pid(s);
         s->clean_result = SERVICE_SUCCESS;
         s->control_command = NULL;
@@ -4718,10 +4924,21 @@ fail:
 
 static int service_can_clean(Unit *u, ExecCleanMask *ret) {
         Service *s = SERVICE(u);
+        ExecCleanMask mask = 0;
+        int r;
 
         assert(s);
+        assert(ret);
+
+        r = exec_context_get_clean_mask(&s->exec_context, &mask);
+        if (r < 0)
+                return r;
 
-        return exec_context_get_clean_mask(&s->exec_context, ret);
+        if (s->n_fd_store_max > 0)
+                mask |= EXEC_CLEAN_FDSTORE;
+
+        *ret = mask;
+        return 0;
 }
 
 static const char *service_finished_job(Unit *u, JobType t, JobResult result) {
@@ -4750,6 +4967,30 @@ static int service_can_start(Unit *u) {
         return 1;
 }
 
+static void service_release_resources(Unit *u) {
+        Service *s = SERVICE(ASSERT_PTR(u));
+
+        /* Invoked by the unit state engine, whenever it realizes that unit is dead and there's no job
+         * anymore for it, and it hence is a good idea to release resources */
+
+        /* Don't release resources if this is a transitionary failed/dead state
+         * (i.e. SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART), insist on a permanent
+         * failure state. */
+        if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED))
+                return;
+
+        log_unit_debug(u, "Releasing resources...");
+
+        service_release_socket_fd(s);
+        service_release_stdio_fd(s);
+
+        if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES)
+                service_release_fd_store(s);
+
+        if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store)
+                service_set_state(s, SERVICE_DEAD);
+}
+
 static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
         [SERVICE_RESTART_NO]          = "no",
         [SERVICE_RESTART_ON_SUCCESS]  = "on-success",
@@ -4845,7 +5086,6 @@ const UnitVTable service_vtable = {
         .cgroup_context_offset = offsetof(Service, cgroup_context),
         .kill_context_offset = offsetof(Service, kill_context),
         .exec_runtime_offset = offsetof(Service, exec_runtime),
-        .dynamic_creds_offset = offsetof(Service, dynamic_creds),
 
         .sections =
                 "Unit\0"
index fd242a7cbfd9126621a77ff85bf6bcdf29220d9b..5b7f67457e2c2ccc340d5f8ee02459db40d8d056 100644 (file)
@@ -116,6 +116,8 @@ struct Service {
         char *pid_file;
 
         usec_t restart_usec;
+        unsigned restart_steps;
+        usec_t restart_usec_max;
         usec_t timeout_start_usec;
         usec_t timeout_stop_usec;
         usec_t timeout_abort_usec;
@@ -156,7 +158,6 @@ struct Service {
 
         /* Runtime data of the execution context */
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         pid_t main_pid, control_pid;
 
@@ -195,6 +196,7 @@ struct Service {
         PathSpec *pid_file_pathspec;
 
         NotifyAccess notify_access;
+        NotifyAccess notify_access_override;
         NotifyState notify_state;
 
         sd_bus_slot *bus_name_pid_lookup_slot;
@@ -204,7 +206,7 @@ struct Service {
         ServiceFDStore *fd_store;
         size_t n_fd_store;
         unsigned n_fd_store_max;
-        unsigned n_keep_fd_store;
+        ExecPreserveMode fd_store_preserve_mode;
 
         char *usb_function_descriptors;
         char *usb_function_strings;
@@ -229,6 +231,11 @@ static inline usec_t service_timeout_abort_usec(Service *s) {
         return s->timeout_abort_set ? s->timeout_abort_usec : s->timeout_stop_usec;
 }
 
+static inline NotifyAccess service_get_notify_access(Service *s) {
+        assert(s);
+        return s->notify_access_override < 0 ? s->notify_access : s->notify_access_override;
+}
+
 static inline usec_t service_get_watchdog_usec(Service *s) {
         assert(s);
         return s->watchdog_override_enable ? s->watchdog_override_usec : s->watchdog_original_usec;
@@ -237,7 +244,9 @@ static inline usec_t service_get_watchdog_usec(Service *s) {
 extern const UnitVTable service_vtable;
 
 int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net);
-void service_close_socket_fd(Service *s);
+void service_release_socket_fd(Service *s);
+
+usec_t service_restart_usec_next(Service *s);
 
 const char* service_restart_to_string(ServiceRestart i) _const_;
 ServiceRestart service_restart_from_string(const char *s) _pure_;
index 307a8f206120ea7b14be70f2c7fef90b4d42a13b..891276fd5088ca11b8824ecc995d8116d3ee3d43 100644 (file)
@@ -14,7 +14,7 @@
 #include "bpf-firewall.h"
 #include "bus-error.h"
 #include "bus-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "constants.h"
 #include "copy.h"
 #include "dbus-socket.h"
@@ -150,12 +150,10 @@ static void socket_done(Unit *u) {
 
         s->peers_by_address = set_free(s->peers_by_address);
 
-        s->exec_runtime = exec_runtime_unref(s->exec_runtime, false);
+        s->exec_runtime = exec_runtime_free(s->exec_runtime);
         exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX);
         s->control_command = NULL;
 
-        dynamic_creds_unref(&s->dynamic_creds);
-
         socket_unwatch_control_pid(s);
 
         unit_ref_unset(&s->service);
@@ -1434,7 +1432,7 @@ static int socket_determine_selinux_label(Socket *s, char **ret) {
         if (!c)
                 goto no_label;
 
-        r = chase_symlinks(c->path, SERVICE(service)->exec_context.root_directory, CHASE_PREFIX_ROOT, &path, NULL);
+        r = chase(c->path, SERVICE(service)->exec_context.root_directory, CHASE_PREFIX_ROOT, &path, NULL);
         if (r < 0)
                 goto no_label;
 
@@ -1532,16 +1530,18 @@ static int socket_address_listen_in_cgroup(
 
         if (s->exec_context.network_namespace_path &&
             s->exec_runtime &&
-            s->exec_runtime->netns_storage_socket[0] >= 0) {
-                r = open_shareable_ns_path(s->exec_runtime->netns_storage_socket, s->exec_context.network_namespace_path, CLONE_NEWNET);
+            s->exec_runtime->shared &&
+            s->exec_runtime->shared->netns_storage_socket[0] >= 0) {
+                r = open_shareable_ns_path(s->exec_runtime->shared->netns_storage_socket, s->exec_context.network_namespace_path, CLONE_NEWNET);
                 if (r < 0)
                         return log_unit_error_errno(UNIT(s), r, "Failed to open network namespace path %s: %m", s->exec_context.network_namespace_path);
         }
 
         if (s->exec_context.ipc_namespace_path &&
             s->exec_runtime &&
-            s->exec_runtime->ipcns_storage_socket[0] >= 0) {
-                r = open_shareable_ns_path(s->exec_runtime->ipcns_storage_socket, s->exec_context.ipc_namespace_path, CLONE_NEWIPC);
+            s->exec_runtime->shared &&
+            s->exec_runtime->shared->ipcns_storage_socket[0] >= 0) {
+                r = open_shareable_ns_path(s->exec_runtime->shared->ipcns_storage_socket, s->exec_context.ipc_namespace_path, CLONE_NEWIPC);
                 if (r < 0)
                         return log_unit_error_errno(UNIT(s), r, "Failed to open IPC namespace path %s: %m", s->exec_context.ipc_namespace_path);
         }
@@ -1559,10 +1559,11 @@ static int socket_address_listen_in_cgroup(
 
                 if (exec_needs_network_namespace(&s->exec_context) &&
                     s->exec_runtime &&
-                    s->exec_runtime->netns_storage_socket[0] >= 0) {
+                    s->exec_runtime->shared &&
+                    s->exec_runtime->shared->netns_storage_socket[0] >= 0) {
 
                         if (ns_type_supported(NAMESPACE_NET)) {
-                                r = setup_shareable_ns(s->exec_runtime->netns_storage_socket, CLONE_NEWNET);
+                                r = setup_shareable_ns(s->exec_runtime->shared->netns_storage_socket, CLONE_NEWNET);
                                 if (r < 0) {
                                         log_unit_error_errno(UNIT(s), r, "Failed to join network namespace: %m");
                                         _exit(EXIT_NETWORK);
@@ -1905,10 +1906,8 @@ static int socket_coldplug(Unit *u) {
                         return r;
         }
 
-        if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED, SOCKET_CLEANING)) {
-                (void) unit_setup_dynamic_creds(u);
+        if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED, SOCKET_CLEANING))
                 (void) unit_setup_exec_runtime(u);
-        }
 
         socket_set_state(s, s->deserialized_state);
         return 0;
@@ -1947,7 +1946,6 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
                        &s->exec_context,
                        &exec_params,
                        s->exec_runtime,
-                       &s->dynamic_creds,
                        &s->cgroup_context,
                        &pid);
         if (r < 0)
@@ -2049,13 +2047,11 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
 
         socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
 
-        s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
+        s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
 
         unit_destroy_runtime_data(UNIT(s), &s->exec_context);
 
         unit_unref_uid_gid(UNIT(s), true);
-
-        dynamic_creds_destroy(&s->dynamic_creds);
 }
 
 static void socket_enter_signal(Socket *s, SocketState state, SocketResult f);
@@ -2397,7 +2393,7 @@ static void socket_enter_running(Socket *s, int cfd_in) {
                 if (r < 0) {
                         /* We failed to activate the new service, but it still exists. Let's make sure the
                          * service closes and forgets the connection fd again, immediately. */
-                        service_close_socket_fd(SERVICE(service));
+                        service_release_socket_fd(SERVICE(service));
                         goto fail;
                 }
 
@@ -2488,7 +2484,7 @@ static int socket_start(Unit *u) {
 
                 /* If the service is already active we cannot start the
                  * socket */
-                if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART))
+                if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED_BEFORE_AUTO_RESTART, SERVICE_AUTO_RESTART))
                         return log_unit_error_errno(u, SYNTHETIC_ERRNO(EBUSY), "Socket service %s already active, refusing.", UNIT(service)->id);
         }
 
@@ -3291,7 +3287,7 @@ static void socket_trigger_notify(Unit *u, Unit *other) {
                 return;
 
         if (IN_SET(SERVICE(other)->state,
-                   SERVICE_DEAD, SERVICE_FAILED,
+                   SERVICE_DEAD, SERVICE_DEAD_BEFORE_AUTO_RESTART, SERVICE_FAILED, SERVICE_FAILED_BEFORE_AUTO_RESTART,
                    SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
                    SERVICE_AUTO_RESTART))
                socket_enter_listening(s);
@@ -3468,7 +3464,6 @@ const UnitVTable socket_vtable = {
         .cgroup_context_offset = offsetof(Socket, cgroup_context),
         .kill_context_offset = offsetof(Socket, kill_context),
         .exec_runtime_offset = offsetof(Socket, exec_runtime),
-        .dynamic_creds_offset = offsetof(Socket, dynamic_creds),
 
         .sections =
                 "Unit\0"
index 6813bdcf8c8e1faed413937db02d638e5b01ae5d..308a0727568991425a58791da77639287783f9c8 100644 (file)
@@ -91,7 +91,6 @@ struct Socket {
         CGroupContext cgroup_context;
 
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         /* For Accept=no sockets refers to the one service we'll
          * activate. For Accept=yes sockets is either NULL, or filled
index d0b557cf400c863ec105b051fbf7ac097df6760b..c6e2c8b1bd94536cd2d557bd78607acc491abf7b 100644 (file)
@@ -170,12 +170,10 @@ static void swap_done(Unit *u) {
         s->parameters_fragment.what = mfree(s->parameters_fragment.what);
         s->parameters_fragment.options = mfree(s->parameters_fragment.options);
 
-        s->exec_runtime = exec_runtime_unref(s->exec_runtime, false);
+        s->exec_runtime = exec_runtime_free(s->exec_runtime);
         exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX);
         s->control_command = NULL;
 
-        dynamic_creds_unref(&s->dynamic_creds);
-
         swap_unwatch_control_pid(s);
 
         s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
@@ -593,10 +591,8 @@ static int swap_coldplug(Unit *u) {
                         return r;
         }
 
-        if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED)) {
-                (void) unit_setup_dynamic_creds(u);
+        if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED))
                 (void) unit_setup_exec_runtime(u);
-        }
 
         swap_set_state(s, new_state);
         return 0;
@@ -689,7 +685,6 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
                        &s->exec_context,
                        &exec_params,
                        s->exec_runtime,
-                       &s->dynamic_creds,
                        &s->cgroup_context,
                        &pid);
         if (r < 0)
@@ -719,13 +714,11 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
         unit_warn_leftover_processes(UNIT(s), unit_log_leftover_process_stop);
         swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
 
-        s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
+        s->exec_runtime = exec_runtime_destroy(s->exec_runtime);
 
         unit_destroy_runtime_data(UNIT(s), &s->exec_context);
 
         unit_unref_uid_gid(UNIT(s), true);
-
-        dynamic_creds_destroy(&s->dynamic_creds);
 }
 
 static void swap_enter_active(Swap *s, SwapResult f) {
@@ -1619,7 +1612,6 @@ const UnitVTable swap_vtable = {
         .cgroup_context_offset = offsetof(Swap, cgroup_context),
         .kill_context_offset = offsetof(Swap, kill_context),
         .exec_runtime_offset = offsetof(Swap, exec_runtime),
-        .dynamic_creds_offset = offsetof(Swap, dynamic_creds),
 
         .sections =
                 "Unit\0"
index c0e3f118e18da15333adc7917c388bddf0d02728..d61c7112cf6b4fc51412cc467edded4d81eac5a6 100644 (file)
@@ -68,7 +68,6 @@ struct Swap {
         CGroupContext cgroup_context;
 
         ExecRuntime *exec_runtime;
-        DynamicCreds dynamic_creds;
 
         SwapState state, deserialized_state;
 
index 371e42e0aa030014cdeaa6298032f9f49307ee6e..419416b3255af0847cd52de6030d6e23ec204c78 100644 (file)
@@ -884,6 +884,7 @@ static int timer_can_clean(Unit *u, ExecCleanMask *ret) {
         Timer *t = TIMER(u);
 
         assert(t);
+        assert(ret);
 
         *ret = t->persistent ? EXEC_CLEAN_STATE : 0;
         return 0;
index 21457dc6a5f6b3678e25f9b89981be3007be9e3d..8055d9e5335254bc187f7cad961c84ba67996b21 100644 (file)
@@ -522,7 +522,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
                         continue;
                 }
 
-                r = exec_runtime_deserialize_compat(u, l, v, fds);
+                r = exec_shared_runtime_deserialize_compat(u, l, v, fds);
                 if (r < 0) {
                         log_unit_warning(u, "Failed to deserialize runtime parameter '%s', ignoring.", l);
                         continue;
index ecf3b2b7fc70571fc6b7700a61c1483d951a66be..c7635a291c5d9a2f7858f8ce7ead4dee688ac2ea 100644 (file)
@@ -17,7 +17,7 @@
 #include "bus-util.h"
 #include "cgroup-setup.h"
 #include "cgroup-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "core-varlink.h"
 #include "dbus-unit.h"
 #include "dbus.h"
@@ -36,6 +36,7 @@
 #include "load-dropin.h"
 #include "load-fragment.h"
 #include "log.h"
+#include "logarithm.h"
 #include "macro.h"
 #include "missing_audit.h"
 #include "mkdir-label.h"
@@ -394,37 +395,59 @@ static bool unit_success_failure_handler_has_jobs(Unit *unit) {
         return false;
 }
 
+void unit_release_resources(Unit *u) {
+        UnitActiveState state;
+        ExecContext *ec;
+
+        assert(u);
+
+        if (u->job || u->nop_job)
+                return;
+
+        if (u->perpetual)
+                return;
+
+        state = unit_active_state(u);
+        if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED))
+                return;
+
+        if (unit_will_restart(u))
+                return;
+
+        ec = unit_get_exec_context(u);
+        if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART)
+                exec_context_destroy_runtime_directory(ec, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
+
+        if (UNIT_VTABLE(u)->release_resources)
+                UNIT_VTABLE(u)->release_resources(u);
+}
+
 bool unit_may_gc(Unit *u) {
         UnitActiveState state;
         int r;
 
         assert(u);
 
-        /* Checks whether the unit is ready to be unloaded for garbage collection.
-         * Returns true when the unit may be collected, and false if there's some
-         * reason to keep it loaded.
+        /* Checks whether the unit is ready to be unloaded for garbage collection.  Returns true when the
+         * unit may be collected, and false if there's some reason to keep it loaded.
          *
-         * References from other units are *not* checked here. Instead, this is done
-         * in unit_gc_sweep(), but using markers to properly collect dependency loops.
+         * References from other units are *not* checked here. Instead, this is done in unit_gc_sweep(), but
+         * using markers to properly collect dependency loops.
          */
 
         if (u->job || u->nop_job)
                 return false;
 
-        state = unit_active_state(u);
-
-        /* If the unit is inactive and failed and no job is queued for it, then release its runtime resources */
-        if (UNIT_IS_INACTIVE_OR_FAILED(state) &&
-            UNIT_VTABLE(u)->release_resources)
-                UNIT_VTABLE(u)->release_resources(u);
-
         if (u->perpetual)
                 return false;
 
         if (sd_bus_track_count(u->bus_track) > 0)
                 return false;
 
-        /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
+        state = unit_active_state(u);
+
+        /* But we keep the unit object around for longer when it is referenced or configured to not be
+         * gc'ed */
         switch (u->collect_mode) {
 
         case COLLECT_INACTIVE:
@@ -458,10 +481,10 @@ bool unit_may_gc(Unit *u) {
                         return false;
         }
 
-        if (UNIT_VTABLE(u)->may_gc && !UNIT_VTABLE(u)->may_gc(u))
-                return false;
+        if (!UNIT_VTABLE(u)->may_gc)
+                return true;
 
-        return true;
+        return UNIT_VTABLE(u)->may_gc(u);
 }
 
 void unit_add_to_load_queue(Unit *u) {
@@ -565,6 +588,40 @@ void unit_submit_to_stop_when_bound_queue(Unit *u) {
         u->in_stop_when_bound_queue = true;
 }
 
+static bool unit_can_release_resources(Unit *u) {
+        ExecContext *ec;
+
+        assert(u);
+
+        if (UNIT_VTABLE(u)->release_resources)
+                return true;
+
+        ec = unit_get_exec_context(u);
+        if (ec && ec->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART)
+                return true;
+
+        return false;
+}
+
+void unit_submit_to_release_resources_queue(Unit *u) {
+        assert(u);
+
+        if (u->in_release_resources_queue)
+                return;
+
+        if (u->job || u->nop_job)
+                return;
+
+        if (u->perpetual)
+                return;
+
+        if (!unit_can_release_resources(u))
+                return;
+
+        LIST_PREPEND(release_resources_queue, u->manager->release_resources_queue, u);
+        u->in_release_resources_queue = true;
+}
+
 static void unit_clear_dependencies(Unit *u) {
         assert(u);
 
@@ -676,6 +733,8 @@ Unit* unit_free(Unit *u) {
         if (!u)
                 return NULL;
 
+        sd_event_source_disable_unref(u->auto_start_stop_event_source);
+
         u->transient_file = safe_fclose(u->transient_file);
 
         if (!MANAGER_IS_RELOADING(u->manager))
@@ -781,6 +840,9 @@ Unit* unit_free(Unit *u) {
         if (u->in_stop_when_bound_queue)
                 LIST_REMOVE(stop_when_bound_queue, u->manager->stop_when_bound_queue, u);
 
+        if (u->in_release_resources_queue)
+                LIST_REMOVE(release_resources_queue, u->manager->release_resources_queue, u);
+
         bpf_firewall_close(u);
 
         hashmap_free(u->bpf_foreign_by_key);
@@ -1546,7 +1608,8 @@ static int unit_add_mount_dependencies(Unit *u) {
 
 static int unit_add_oomd_dependencies(Unit *u) {
         CGroupContext *c;
-        bool wants_oomd;
+        CGroupMask mask;
+        int r;
 
         assert(u);
 
@@ -1557,10 +1620,20 @@ static int unit_add_oomd_dependencies(Unit *u) {
         if (!c)
                 return 0;
 
-        wants_oomd = (c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL);
+        bool wants_oomd = c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL;
         if (!wants_oomd)
                 return 0;
 
+        if (!cg_all_unified())
+                return 0;
+
+        r = cg_mask_supported(&mask);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to determine supported controllers: %m");
+
+        if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY))
+                return 0;
+
         return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, "systemd-oomd.service", true, UNIT_DEPENDENCY_FILE);
 }
 
@@ -1879,12 +1952,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 &&
@@ -2556,7 +2629,6 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags)
         assert(j);
 
         if (j->state == JOB_WAITING)
-
                 /* So we reached a different state for this job. Let's see if we can run it now if it failed previously
                  * due to EAGAIN. */
                 job_add_to_run_queue(j);
@@ -2760,6 +2832,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
 
                 /* Maybe the unit should be GC'ed now? */
                 unit_add_to_gc_queue(u);
+
+                /* Maybe we can release some resources now? */
+                unit_submit_to_release_resources_queue(u);
         }
 
         if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) {
@@ -3454,7 +3529,7 @@ static int get_name_owner_handler(sd_bus_message *message, void *userdata, sd_bu
 
         e = sd_bus_message_get_error(message);
         if (e) {
-                if (!sd_bus_error_has_name(e, "org.freedesktop.DBus.Error.NameHasNoOwner")) {
+                if (!sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
                         r = sd_bus_error_get_errno(e);
                         log_unit_error_errno(u, r,
                                              "Unexpected error response from GetNameOwner(): %s",
@@ -4020,7 +4095,7 @@ UnitFileState unit_get_unit_file_state(Unit *u) {
         return u->unit_file_state;
 }
 
-int unit_get_unit_file_preset(Unit *u) {
+PresetAction unit_get_unit_file_preset(Unit *u) {
         int r;
 
         assert(u);
@@ -4216,18 +4291,8 @@ int unit_patch_contexts(Unit *u) {
                         }
 
                         /* If there are encrypted credentials we might need to access the TPM. */
-                        bool allow_tpm = false;
-                        ExecLoadCredential *load_cred;
-                        ExecSetCredential *set_cred;
-                        HASHMAP_FOREACH(load_cred, ec->load_credentials)
-                                if ((allow_tpm |= load_cred->encrypted))
-                                        break;
-                        HASHMAP_FOREACH(set_cred, ec->set_credentials)
-                                if ((allow_tpm |= set_cred->encrypted))
-                                        break;
-
-                        if (allow_tpm) {
-                                r = cgroup_add_device_allow(cc, "/dev/tpmrm0", "rw");
+                        if (exec_context_has_encrypted_credentials(ec)) {
+                                r = cgroup_add_device_allow(cc, "char-tpm", "rw");
                                 if (r < 0)
                                         return r;
                         }
@@ -4309,20 +4374,18 @@ static const char* unit_drop_in_dir(Unit *u, UnitWriteFlags flags) {
         return NULL;
 }
 
-char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
-        assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
+const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
+        assert(s);
+        assert(popcount(flags & (UNIT_ESCAPE_EXEC_SYNTAX_ENV | UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C)) <= 1);
+        assert(buf);
 
         _cleanup_free_ char *t = NULL;
 
-        if (!s)
-                return NULL;
-
-        /* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the
-         * allocated return buffer pointer is also written to *buf, except if no escaping was necessary, in
-         * which case *buf is set to NULL, and the input pointer is returned as-is. This means the return
-         * value always contains a properly escaped version, but *buf when passed only contains a pointer if
-         * an allocation was necessary. If *buf is not specified, then the return value always needs to be
-         * freed. Callers can use this to optimize memory allocations. */
+        /* Returns a string with any escaping done. If no escaping was necessary, *buf is set to NULL, and
+         * the input pointer is returned as-is. If an allocation was needed, the return buffer pointer is
+         * written to *buf. This means the return value always contains a properly escaped version, but *buf
+         * only contains a pointer if an allocation was made. Callers can use this to optimize memory
+         * allocations. */
 
         if (flags & UNIT_ESCAPE_SPECIFIERS) {
                 t = specifier_escape(s);
@@ -4332,11 +4395,20 @@ char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
                 s = t;
         }
 
-        /* We either do c-escaping or shell-escaping, to additionally escape characters that we parse for
-         * ExecStart= and friend, i.e. '$' and ';' and quotes. */
+        /* We either do C-escaping or shell-escaping, to additionally escape characters that we parse for
+         * ExecStart= and friends, i.e. '$' and quotes. */
+
+        if (flags & (UNIT_ESCAPE_EXEC_SYNTAX_ENV | UNIT_ESCAPE_EXEC_SYNTAX)) {
+                char *t2;
+
+                if (flags & UNIT_ESCAPE_EXEC_SYNTAX_ENV) {
+                        t2 = strreplace(s, "$", "$$");
+                        if (!t2)
+                                return NULL;
+                        free_and_replace(t, t2);
+                }
 
-        if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
-                char *t2 = shell_escape(s, "$;'\"");
+                t2 = shell_escape(t ?: s, "\"");
                 if (!t2)
                         return NULL;
                 free_and_replace(t, t2);
@@ -4344,7 +4416,9 @@ char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
                 s = t;
 
         } else if (flags & UNIT_ESCAPE_C) {
-                char *t2 = cescape(s);
+                char *t2;
+
+                t2 = cescape(s);
                 if (!t2)
                         return NULL;
                 free_and_replace(t, t2);
@@ -4352,12 +4426,8 @@ char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
                 s = t;
         }
 
-        if (buf) {
-                *buf = TAKE_PTR(t);
-                return (char*) s;
-        }
-
-        return TAKE_PTR(t) ?: strdup(s);
+        *buf = TAKE_PTR(t);
+        return s;
 }
 
 char* unit_concat_strv(char **l, UnitWriteFlags flags) {
@@ -4801,7 +4871,10 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask)
 }
 
 int unit_setup_exec_runtime(Unit *u) {
+        _cleanup_(exec_shared_runtime_unrefp) ExecSharedRuntime *esr = NULL;
+        _cleanup_(dynamic_creds_unrefp) DynamicCreds *dcreds = NULL;
         ExecRuntime **rt;
+        ExecContext *ec;
         size_t offset;
         Unit *other;
         int r;
@@ -4814,34 +4887,38 @@ int unit_setup_exec_runtime(Unit *u) {
         if (*rt)
                 return 0;
 
+        ec = unit_get_exec_context(u);
+        assert(ec);
+
         /* Try to get it from somebody else */
         UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_JOINS_NAMESPACE_OF) {
-                r = exec_runtime_acquire(u->manager, NULL, other->id, false, rt);
-                if (r == 1)
-                        return 1;
+                r = exec_shared_runtime_acquire(u->manager, NULL, other->id, false, &esr);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        break;
         }
 
-        return exec_runtime_acquire(u->manager, unit_get_exec_context(u), u->id, true, rt);
-}
-
-int unit_setup_dynamic_creds(Unit *u) {
-        ExecContext *ec;
-        DynamicCreds *dcreds;
-        size_t offset;
-
-        assert(u);
+        if (!esr) {
+                r = exec_shared_runtime_acquire(u->manager, ec, u->id, true, &esr);
+                if (r < 0)
+                        return r;
+        }
 
-        offset = UNIT_VTABLE(u)->dynamic_creds_offset;
-        assert(offset > 0);
-        dcreds = (DynamicCreds*) ((uint8_t*) u + offset);
+        if (ec->dynamic_user) {
+                r = dynamic_creds_make(u->manager, ec->user, ec->group, &dcreds);
+                if (r < 0)
+                        return r;
+        }
 
-        ec = unit_get_exec_context(u);
-        assert(ec);
+        r = exec_runtime_make(esr, dcreds, rt);
+        if (r < 0)
+                return r;
 
-        if (!ec->dynamic_user)
-                return 0;
+        TAKE_PTR(esr);
+        TAKE_PTR(dcreds);
 
-        return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group);
+        return r;
 }
 
 bool unit_type_supported(UnitType t) {
@@ -4904,7 +4981,7 @@ int unit_fail_if_noncanonical(Unit *u, const char* where) {
         assert(u);
         assert(where);
 
-        r = chase_symlinks(where, NULL, CHASE_NONEXISTENT, &canonical_where, NULL);
+        r = chase(where, NULL, CHASE_NONEXISTENT, &canonical_where, NULL);
         if (r < 0) {
                 log_unit_debug_errno(u, r, "Failed to check %s for symlinks, ignoring: %m", where);
                 return 0;
@@ -5575,10 +5652,6 @@ int unit_prepare_exec(Unit *u) {
         if (r < 0)
                 return r;
 
-        r = unit_setup_dynamic_creds(u);
-        if (r < 0)
-                return r;
-
         return 0;
 }
 
@@ -5824,8 +5897,8 @@ void unit_destroy_runtime_data(Unit *u, const ExecContext *context) {
         assert(u);
         assert(context);
 
-        if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
-            (context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u)))
+        /* EXEC_PRESERVE_RESTART is handled via unit_release_resources()! */
+        if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO)
                 exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
 
         exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id);
index d5a6d595e293778b5c51035648c2334a36660636..7e85150643ec4c4418abcfa9870139d9d0295be2 100644 (file)
@@ -11,6 +11,7 @@
 #include "bpf-program.h"
 #include "condition.h"
 #include "emergency-action.h"
+#include "install.h"
 #include "list.h"
 #include "show-status.h"
 #include "set.h"
@@ -317,6 +318,9 @@ typedef struct Unit {
         /* Queue of units that have a BindTo= dependency on some other unit, and should possibly be shut down */
         LIST_FIELDS(Unit, stop_when_bound_queue);
 
+        /* Queue of units that should be checked if they can release resources now */
+        LIST_FIELDS(Unit, release_resources_queue);
+
         /* PIDs we keep an eye on. Note that a unit might have many
          * more, but these are the ones we care enough about to
          * process SIGCHLD for */
@@ -347,6 +351,7 @@ typedef struct Unit {
 
         /* Make sure we never enter endless loops with the StopWhenUnneeded=, BindsTo=, Uphold= logic */
         RateLimit auto_start_stop_ratelimit;
+        sd_event_source *auto_start_stop_event_source;
 
         /* Reference to a specific UID/GID */
         uid_t ref_uid;
@@ -354,7 +359,7 @@ typedef struct Unit {
 
         /* Cached unit file state and preset */
         UnitFileState unit_file_state;
-        int unit_file_preset;
+        PresetAction unit_file_preset;
 
         /* Where the cpu.stat or cpuacct.usage was at the time the unit was started */
         nsec_t cpu_usage_base;
@@ -479,6 +484,7 @@ typedef struct Unit {
         bool in_stop_when_unneeded_queue:1;
         bool in_start_when_upheld_queue:1;
         bool in_stop_when_bound_queue:1;
+        bool in_release_resources_queue:1;
 
         bool sent_dbus_new_signal:1;
 
@@ -528,22 +534,25 @@ typedef struct UnitStatusMessageFormats {
 /* Flags used when writing drop-in files or transient unit files */
 typedef enum UnitWriteFlags {
         /* Write a runtime unit file or drop-in (i.e. one below /run) */
-        UNIT_RUNTIME            = 1 << 0,
+        UNIT_RUNTIME                = 1 << 0,
 
         /* Write a persistent drop-in (i.e. one below /etc) */
-        UNIT_PERSISTENT         = 1 << 1,
+        UNIT_PERSISTENT             = 1 << 1,
 
         /* Place this item in the per-unit-type private section, instead of [Unit] */
-        UNIT_PRIVATE            = 1 << 2,
+        UNIT_PRIVATE                = 1 << 2,
+
+        /* Apply specifier escaping */
+        UNIT_ESCAPE_SPECIFIERS      = 1 << 3,
 
-        /* Apply specifier escaping before writing */
-        UNIT_ESCAPE_SPECIFIERS  = 1 << 3,
+        /* Escape elements of ExecStart= syntax, incl. prevention of variable expansion */
+        UNIT_ESCAPE_EXEC_SYNTAX_ENV = 1 << 4,
 
-        /* Escape elements of ExecStart= syntax before writing */
-        UNIT_ESCAPE_EXEC_SYNTAX = 1 << 4,
+        /* Escape elements of ExecStart=: syntax (no variable expansion) */
+        UNIT_ESCAPE_EXEC_SYNTAX     = 1 << 5,
 
         /* Apply C escaping before writing */
-        UNIT_ESCAPE_C           = 1 << 5,
+        UNIT_ESCAPE_C               = 1 << 6,
 } UnitWriteFlags;
 
 /* Returns true if neither persistent, nor runtime storage is requested, i.e. this is a check invocation only */
@@ -570,14 +579,10 @@ typedef struct UnitVTable {
         size_t kill_context_offset;
 
         /* If greater than 0, the offset into the object where the
-         * pointer to ExecRuntime is found, if the unit type has
+         * pointer to ExecSharedRuntime is found, if the unit type has
          * that */
         size_t exec_runtime_offset;
 
-        /* If greater than 0, the offset into the object where the pointer to DynamicCreds is found, if the unit type
-         * has that. */
-        size_t dynamic_creds_offset;
-
         /* The name of the configuration file section with the private settings of this unit */
         const char *private_section;
 
@@ -836,6 +841,8 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c);
 int unit_choose_id(Unit *u, const char *name);
 int unit_set_description(Unit *u, const char *description);
 
+void unit_release_resources(Unit *u);
+
 bool unit_may_gc(Unit *u);
 
 static inline bool unit_is_extrinsic(Unit *u) {
@@ -857,6 +864,7 @@ void unit_add_to_target_deps_queue(Unit *u);
 void unit_submit_to_stop_when_unneeded_queue(Unit *u);
 void unit_submit_to_start_when_upheld_queue(Unit *u);
 void unit_submit_to_stop_when_bound_queue(Unit *u);
+void unit_submit_to_release_resources_queue(Unit *u);
 
 int unit_merge(Unit *u, Unit *other);
 int unit_merge_by_name(Unit *u, const char *other);
@@ -950,7 +958,7 @@ void unit_start_on_failure(Unit *u, const char *dependency_name, UnitDependencyA
 void unit_trigger_notify(Unit *u);
 
 UnitFileState unit_get_unit_file_state(Unit *u);
-int unit_get_unit_file_preset(Unit *u);
+PresetAction unit_get_unit_file_preset(Unit *u);
 
 Unit* unit_ref_set(UnitRef *ref, Unit *source, Unit *target);
 void unit_ref_unset(UnitRef *ref);
@@ -967,9 +975,8 @@ CGroupContext *unit_get_cgroup_context(Unit *u) _pure_;
 ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_;
 
 int unit_setup_exec_runtime(Unit *u);
-int unit_setup_dynamic_creds(Unit *u);
 
-char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf);
+const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf);
 char* unit_concat_strv(char **l, UnitWriteFlags flags);
 
 int unit_write_setting(Unit *u, UnitWriteFlags flags, const char *name, const char *data);
@@ -1208,3 +1215,13 @@ typedef struct UnitForEachDependencyData {
 /* Note: this matches deps that have *any* of the atoms specified in match_atom set */
 #define UNIT_FOREACH_DEPENDENCY(other, u, match_atom) \
         _UNIT_FOREACH_DEPENDENCY(other, u, match_atom, UNIQ_T(data, UNIQ))
+
+#define _LOG_CONTEXT_PUSH_UNIT(unit, u, c)                                                      \
+        const Unit *u = (unit);                                                                 \
+        const ExecContext *c = unit_get_exec_context(u);                                        \
+        LOG_CONTEXT_PUSH_KEY_VALUE(u->manager->unit_log_field, u->id);                          \
+        LOG_CONTEXT_PUSH_KEY_VALUE(u->manager->invocation_log_field, u->invocation_id_string);  \
+        LOG_CONTEXT_PUSH_IOV(c ? c->log_extra_fields : NULL, c ? c->n_log_extra_fields : 0)
+
+#define LOG_CONTEXT_PUSH_UNIT(unit) \
+        _LOG_CONTEXT_PUSH_UNIT(unit, UNIQ_T(u, UNIQ), UNIQ_T(c, UNIQ))
index 266471a9c8282291f1672fc3f92c8cec514aa364..5fdcfa74372163d6fa0f0b3cecd8dcc0ee5f753e 100644 (file)
@@ -335,96 +335,6 @@ static int make_filename(const Context *context, char **ret) {
         return 0;
 }
 
-static int parse_auxv64(
-                const uint64_t *auxv,
-                size_t size_bytes,
-                int *at_secure,
-                uid_t *uid,
-                uid_t *euid,
-                gid_t *gid,
-                gid_t *egid) {
-
-        assert(auxv || size_bytes == 0);
-
-        if (size_bytes % (2 * sizeof(uint64_t)) != 0)
-                return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes);
-
-        size_t words = size_bytes / sizeof(uint64_t);
-
-        /* Note that we set output variables even on error. */
-
-        for (size_t i = 0; i + 1 < words; i += 2)
-                switch (auxv[i]) {
-                case AT_SECURE:
-                        *at_secure = auxv[i + 1] != 0;
-                        break;
-                case AT_UID:
-                        *uid = auxv[i + 1];
-                        break;
-                case AT_EUID:
-                        *euid = auxv[i + 1];
-                        break;
-                case AT_GID:
-                        *gid = auxv[i + 1];
-                        break;
-                case AT_EGID:
-                        *egid = auxv[i + 1];
-                        break;
-                case AT_NULL:
-                        if (auxv[i + 1] != 0)
-                                goto error;
-                        return 0;
-                }
- error:
-        return log_warning_errno(SYNTHETIC_ERRNO(ENODATA),
-                                 "AT_NULL terminator not found, cannot parse auxv structure.");
-}
-
-static int parse_auxv32(
-                const uint32_t *auxv,
-                size_t size_bytes,
-                int *at_secure,
-                uid_t *uid,
-                uid_t *euid,
-                gid_t *gid,
-                gid_t *egid) {
-
-        assert(auxv || size_bytes == 0);
-
-        size_t words = size_bytes / sizeof(uint32_t);
-
-        if (size_bytes % (2 * sizeof(uint32_t)) != 0)
-                return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes);
-
-        /* Note that we set output variables even on error. */
-
-        for (size_t i = 0; i + 1 < words; i += 2)
-                switch (auxv[i]) {
-                case AT_SECURE:
-                        *at_secure = auxv[i + 1] != 0;
-                        break;
-                case AT_UID:
-                        *uid = auxv[i + 1];
-                        break;
-                case AT_EUID:
-                        *euid = auxv[i + 1];
-                        break;
-                case AT_GID:
-                        *gid = auxv[i + 1];
-                        break;
-                case AT_EGID:
-                        *egid = auxv[i + 1];
-                        break;
-                case AT_NULL:
-                        if (auxv[i + 1] != 0)
-                                goto error;
-                        return 0;
-                }
- error:
-        return log_warning_errno(SYNTHETIC_ERRNO(ENODATA),
-                                 "AT_NULL terminator not found, cannot parse auxv structure.");
-}
-
 static int grant_user_access(int core_fd, const Context *context) {
         int at_secure = -1;
         uid_t uid = UID_INVALID, euid = UID_INVALID;
@@ -459,14 +369,11 @@ static int grant_user_access(int core_fd, const Context *context) {
                 return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN),
                                       "Core file has non-native endianness, not adjusting permissions.");
 
-        if (elf[EI_CLASS] == ELFCLASS64)
-                r = parse_auxv64((const uint64_t*) context->meta[META_PROC_AUXV],
-                                 context->meta_size[META_PROC_AUXV],
-                                 &at_secure, &uid, &euid, &gid, &egid);
-        else
-                r = parse_auxv32((const uint32_t*) context->meta[META_PROC_AUXV],
-                                 context->meta_size[META_PROC_AUXV],
-                                 &at_secure, &uid, &euid, &gid, &egid);
+        r = parse_auxv(LOG_WARNING,
+                       /* elf_class= */ elf[EI_CLASS],
+                       context->meta[META_PROC_AUXV],
+                       context->meta_size[META_PROC_AUXV],
+                       &at_secure, &uid, &euid, &gid, &egid);
         if (r < 0)
                 return r;
 
@@ -1187,7 +1094,7 @@ static int process_socket(int fd) {
                         }
 
                         assert(input_fd < 0);
-                        input_fd = *(int*) CMSG_DATA(found);
+                        input_fd = *CMSG_TYPED_DATA(found, int);
                         break;
                 } else
                         cmsg_close_all(&mh);
index 909ffc8d8b822f7901ca9b7d32e7ae30fd91a200..bc52cc0b06335cadb2156b61a747c3469903ed02 100644 (file)
@@ -15,7 +15,7 @@
 #include "bus-error.h"
 #include "bus-locator.h"
 #include "bus-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "compress.h"
 #include "constants.h"
 #include "dissect-image.h"
@@ -64,9 +64,11 @@ static const char* arg_output = NULL;
 static bool arg_reverse = false;
 static bool arg_quiet = false;
 static bool arg_all = false;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static int add_match(sd_journal *j, const char *match) {
         _cleanup_free_ char *p = NULL;
@@ -198,6 +200,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
                "     --all                     Look at all journal files instead of local ones\n"
                "     --root=PATH               Operate on an alternate filesystem root\n"
                "     --image=PATH              Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY     Specify disk image dissection policy\n"
                "\nSee the %2$s for details.\n",
                program_invocation_short_name,
                link,
@@ -219,30 +222,32 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_FILE,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_ALL,
         };
 
         int c, r;
 
         static const struct option options[] = {
-                { "help",               no_argument,       NULL, 'h'           },
-                { "version" ,           no_argument,       NULL, ARG_VERSION   },
-                { "no-pager",           no_argument,       NULL, ARG_NO_PAGER  },
-                { "no-legend",          no_argument,       NULL, ARG_NO_LEGEND },
-                { "debugger",           required_argument, NULL, ARG_DEBUGGER  },
-                { "debugger-arguments", required_argument, NULL, 'A'           },
-                { "output",             required_argument, NULL, 'o'           },
-                { "field",              required_argument, NULL, 'F'           },
-                { "file",               required_argument, NULL, ARG_FILE      },
-                { "directory",          required_argument, NULL, 'D'           },
-                { "reverse",            no_argument,       NULL, 'r'           },
-                { "since",              required_argument, NULL, 'S'           },
-                { "until",              required_argument, NULL, 'U'           },
-                { "quiet",              no_argument,       NULL, 'q'           },
-                { "json",               required_argument, NULL, ARG_JSON      },
-                { "root",               required_argument, NULL, ARG_ROOT      },
-                { "image",              required_argument, NULL, ARG_IMAGE     },
-                { "all",                no_argument,       NULL, ARG_ALL       },
+                { "help",               no_argument,       NULL, 'h'              },
+                { "version" ,           no_argument,       NULL, ARG_VERSION      },
+                { "no-pager",           no_argument,       NULL, ARG_NO_PAGER     },
+                { "no-legend",          no_argument,       NULL, ARG_NO_LEGEND    },
+                { "debugger",           required_argument, NULL, ARG_DEBUGGER     },
+                { "debugger-arguments", required_argument, NULL, 'A'              },
+                { "output",             required_argument, NULL, 'o'              },
+                { "field",              required_argument, NULL, 'F'              },
+                { "file",               required_argument, NULL, ARG_FILE         },
+                { "directory",          required_argument, NULL, 'D'              },
+                { "reverse",            no_argument,       NULL, 'r'              },
+                { "since",              required_argument, NULL, 'S'              },
+                { "until",              required_argument, NULL, 'U'              },
+                { "quiet",              no_argument,       NULL, 'q'              },
+                { "json",               required_argument, NULL, ARG_JSON         },
+                { "root",               required_argument, NULL, ARG_ROOT         },
+                { "image",              required_argument, NULL, ARG_IMAGE        },
+                { "image-policy",       required_argument, NULL, ARG_IMAGE_POLICY },
+                { "all",                no_argument,       NULL, ARG_ALL          },
                 {}
         };
 
@@ -344,6 +349,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case 'r':
                         arg_reverse = true;
                         break;
@@ -505,13 +516,13 @@ static int resolve_filename(const char *root, char **p) {
         if (!*p)
                 return 0;
 
-        r = chase_symlinks(*p, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+        r = chase(*p, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve \"%s%s\": %m", strempty(root), *p);
 
         free_and_replace(*p, resolved);
 
-        /* chase_symlinks() with flag CHASE_NONEXISTENT will return 0 if the file doesn't exist and 1 if it does.
+        /* chase() with flag CHASE_NONEXISTENT will return 0 if the file doesn't exist and 1 if it does.
          * Return that to the caller
          */
         return r;
@@ -981,7 +992,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp)
                         return r;
                 assert(r > 0);
 
-                r = chase_symlinks_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved);
+                r = chase_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved);
                 if (r < 0)
                         return log_error_errno(r, "Cannot access \"%s%s\": %m", strempty(arg_root), filename);
 
@@ -1361,6 +1372,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK |
index 3eb94cd36a291bd6b22aa2a0268b5dbbe3ec061d..670e719d21f36c19a6cee6fd00ffe158a26c89cf 100644 (file)
@@ -506,20 +506,21 @@ static int verb_encrypt(int argc, char **argv, void *userdata) {
         if (base64_size < 0)
                 return base64_size;
 
-        if (arg_pretty) {
+        /* Pretty print makes sense only if we're printing stuff to stdout
+         * and if a cred name is provided via --name= (since we can't use
+         * the output file name as the cred name here) */
+        if (arg_pretty && !output_path && name) {
                 _cleanup_free_ char *escaped = NULL, *indented = NULL, *j = NULL;
 
-                if (name) {
-                        escaped = cescape(name);
-                        if (!escaped)
-                                return log_oom();
-                }
+                escaped = cescape(name);
+                if (!escaped)
+                        return log_oom();
 
                 indented = strreplace(base64_buf, "\n", " \\\n        ");
                 if (!indented)
                         return log_oom();
 
-                j = strjoin("SetCredentialEncrypted=", name, ": \\\n        ", indented, "\n");
+                j = strjoin("SetCredentialEncrypted=", escaped, ": \\\n        ", indented, "\n");
                 if (!j)
                         return log_oom();
 
@@ -807,13 +808,11 @@ static int parse_argv(int argc, char *argv[]) {
                         if (isempty(optarg) || streq(optarg, "auto"))
                                 arg_newline = -1;
                         else {
-                                bool b;
-
-                                r = parse_boolean_argument("--newline=", optarg, &b);
+                                r = parse_boolean_argument("--newline=", optarg, NULL);
                                 if (r < 0)
                                         return r;
 
-                                arg_newline = b;
+                                arg_newline = r;
                         }
                         break;
 
index 62688da3d3b997ae78e374e31039986d41955582..d21df7123b0c852f3b5b5f3146cef7fa4690fac6 100644 (file)
@@ -5,12 +5,13 @@
 #include "format-table.h"
 #include "parse-util.h"
 
-int list_enrolled(struct crypt_device *cd) {
+struct keyslot_metadata {
+        int slot;
+        const char *type;
+};
 
-        struct keyslot_metadata {
-                int slot;
-                const char *type;
-        } *keyslot_metadata = NULL;
+int list_enrolled(struct crypt_device *cd) {
+        _cleanup_free_ struct keyslot_metadata *keyslot_metadata = NULL;
         _cleanup_(table_unrefp) Table *t = NULL;
         size_t n_keyslot_metadata = 0;
         int slot_max, r;
index fc6cc74dadbd79dfaf8dabf9143fd19d06801c3b..4dc3c1794d89a5d2d01a45032fe5a734089c030c 100644 (file)
@@ -142,7 +142,8 @@ int enroll_tpm2(struct crypt_device *cd,
         _cleanup_(erase_and_freep) void *secret = NULL;
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
         _cleanup_(erase_and_freep) char *base64_encoded = NULL;
-        size_t secret_size, blob_size, hash_size, pubkey_size = 0;
+        _cleanup_free_ void *srk_buf = NULL;
+        size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0;
         _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL;
         uint16_t pcr_bank, primary_alg;
         const char *node;
@@ -214,7 +215,9 @@ int enroll_tpm2(struct crypt_device *cd,
                       &blob, &blob_size,
                       &hash, &hash_size,
                       &pcr_bank,
-                      &primary_alg);
+                      &primary_alg,
+                      &srk_buf,
+                      &srk_buf_size);
         if (r < 0)
                 return r;
 
@@ -245,6 +248,7 @@ int enroll_tpm2(struct crypt_device *cd,
                                 primary_alg,
                                 blob, blob_size,
                                 hash, hash_size,
+                                srk_buf, srk_buf_size,
                                 &secret2, &secret2_size);
                 if (r < 0)
                         return r;
@@ -283,6 +287,7 @@ int enroll_tpm2(struct crypt_device *cd,
                         hash, hash_size,
                         use_pin ? binary_salt : NULL,
                         use_pin ? sizeof(binary_salt) : 0,
+                        srk_buf, srk_buf_size,
                         flags,
                         &v);
         if (r < 0)
index 29360ef0fc96387351265eceb836c798ce625713..be57873ee447045cd30ced6ee2686122524c45fb 100644 (file)
@@ -207,38 +207,29 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_VERSION:
                         return version();
 
-                case ARG_FIDO2_WITH_PIN: {
-                        bool lock_with_pin;
-
-                        r = parse_boolean_argument("--fido2-with-client-pin=", optarg, &lock_with_pin);
+                case ARG_FIDO2_WITH_PIN:
+                        r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, lock_with_pin);
+                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r);
                         break;
-                }
-
-                case ARG_FIDO2_WITH_UP: {
-                        bool lock_with_up;
 
-                        r = parse_boolean_argument("--fido2-with-user-presence=", optarg, &lock_with_up);
+                case ARG_FIDO2_WITH_UP:
+                        r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, lock_with_up);
+                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r);
                         break;
-                }
-
-                case ARG_FIDO2_WITH_UV: {
-                        bool lock_with_uv;
 
-                        r = parse_boolean_argument("--fido2-with-user-verification=", optarg, &lock_with_uv);
+                case ARG_FIDO2_WITH_UV:
+                        r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, lock_with_uv);
+                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r);
                         break;
-                }
 
                 case ARG_PASSWORD:
                         if (arg_enroll_type >= 0)
index b5d66e389da6af8d05672f186acb26b199e1af74..aab3a4b4c067f9f56ed6147f3d57c9d89dec0704 100644 (file)
@@ -42,8 +42,8 @@ _public_ int cryptsetup_token_open_pin(
                 void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
 
         _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
-        _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
-        size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0;
+        _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
+        size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
         _cleanup_(erase_and_freep) void *decrypted_key = NULL;
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         uint32_t hash_pcr_mask, pubkey_pcr_mask;
@@ -92,6 +92,8 @@ _public_ int cryptsetup_token_open_pin(
                         &policy_hash_size,
                         &salt,
                         &salt_size,
+                        &srk_buf,
+                        &srk_buf_size,
                         &flags);
         if (r < 0)
                 return log_debug_open_error(cd, r);
@@ -114,6 +116,8 @@ _public_ int cryptsetup_token_open_pin(
                         policy_hash_size,
                         salt,
                         salt_size,
+                        srk_buf,
+                        srk_buf_size,
                         flags,
                         &decrypted_key,
                         &decrypted_key_size);
@@ -172,9 +176,9 @@ _public_ void cryptsetup_token_dump(
                 const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
 
         _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
-        _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
+        _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
-        size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
+        size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
         uint32_t hash_pcr_mask, pubkey_pcr_mask;
         uint16_t pcr_bank, primary_alg;
         TPM2Flags flags = 0;
@@ -201,6 +205,8 @@ _public_ void cryptsetup_token_dump(
                         &policy_hash_size,
                         &salt,
                         &salt_size,
+                        &srk_buf,
+                        &srk_buf_size,
                         &flags);
         if (r < 0)
                 return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
@@ -234,6 +240,7 @@ _public_ void cryptsetup_token_dump(
         crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
         crypt_log(cd, "\ttpm2-pin:         %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
         crypt_log(cd, "\ttpm2-salt:        %s\n", true_false(salt));
+        crypt_log(cd, "\ttpm2-srk:         %s\n", true_false(srk_buf));
 }
 
 /*
index 307488726935ac28b6f5c9ae64c079675e6cb330..e2fa49b94f46feb7d8933aed12225b18e10db5da 100644 (file)
@@ -29,6 +29,8 @@ int acquire_luks2_key(
                 size_t policy_hash_size,
                 const void *salt,
                 size_t salt_size,
+                const void *srk_buf,
+                size_t srk_buf_size,
                 TPM2Flags flags,
                 void **ret_decrypted_key,
                 size_t *ret_decrypted_key_size) {
@@ -89,5 +91,6 @@ int acquire_luks2_key(
                         primary_alg,
                         key_data, key_data_size,
                         policy_hash, policy_hash_size,
+                        srk_buf, srk_buf_size,
                         ret_decrypted_key, ret_decrypted_key_size);
 }
index 36d514caa0d5d5ff6c9c49452bc14e4d0bcc39df..1143f5fd9f53d496f588bf182a090bc133f68fb0 100644 (file)
@@ -22,6 +22,8 @@ int acquire_luks2_key(
                 size_t policy_hash_size,
                 const void *salt,
                 size_t salt_size,
+                const void *srk_buf,
+                size_t srk_buf_size,
                 TPM2Flags flags,
                 void **ret_decrypted_key,
                 size_t *ret_decrypted_key_size);
index a375a227586d87383823e430f9ae1c0d94a9c2f0..5e277b0dd624409c2e5cc355cbc91b8486dc2f7b 100644 (file)
@@ -72,6 +72,8 @@ int acquire_tpm2_key(
                 size_t policy_hash_size,
                 const void *salt,
                 size_t salt_size,
+                const void *srk_buf,
+                size_t srk_buf_size,
                 TPM2Flags flags,
                 usec_t until,
                 bool headless,
@@ -141,6 +143,8 @@ int acquire_tpm2_key(
                                 blob_size,
                                 policy_hash,
                                 policy_hash_size,
+                                srk_buf,
+                                srk_buf_size,
                                 ret_decrypted_key,
                                 ret_decrypted_key_size);
 
@@ -181,6 +185,8 @@ int acquire_tpm2_key(
                                 blob_size,
                                 policy_hash,
                                 policy_hash_size,
+                                srk_buf,
+                                srk_buf_size,
                                 ret_decrypted_key,
                                 ret_decrypted_key_size);
                 /* We get this error in case there is an authentication policy mismatch. This should
@@ -210,6 +216,8 @@ int find_tpm2_auto_data(
                 size_t *ret_policy_hash_size,
                 void **ret_salt,
                 size_t *ret_salt_size,
+                void **ret_srk_buf,
+                size_t *ret_srk_buf_size,
                 TPM2Flags *ret_flags,
                 int *ret_keyslot,
                 int *ret_token) {
@@ -219,9 +227,9 @@ int find_tpm2_auto_data(
         assert(cd);
 
         for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
-                _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
+                _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
                 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
-                size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
+                size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
                 uint32_t hash_pcr_mask, pubkey_pcr_mask;
                 uint16_t pcr_bank, primary_alg;
                 TPM2Flags flags;
@@ -244,6 +252,7 @@ int find_tpm2_auto_data(
                                 &blob, &blob_size,
                                 &policy_hash, &policy_hash_size,
                                 &salt, &salt_size,
+                                &srk_buf, &srk_buf_size,
                                 &flags);
                 if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
                         continue;
@@ -270,6 +279,8 @@ int find_tpm2_auto_data(
                         *ret_salt_size = salt_size;
                         *ret_keyslot = keyslot;
                         *ret_token = token;
+                        *ret_srk_buf = TAKE_PTR(srk_buf);
+                        *ret_srk_buf_size = srk_buf_size;
                         *ret_flags = flags;
                         return 0;
                 }
index f6549b7d1d97aa87c2c14e4a7c459ab9ac260862..a510ac625701cd278f855aa6e8d1d2483689ee6a 100644 (file)
@@ -30,6 +30,8 @@ int acquire_tpm2_key(
                 size_t policy_hash_size,
                 const void *salt,
                 size_t salt_size,
+                const void *srk_buf,
+                size_t salt_srk_buf_size,
                 TPM2Flags flags,
                 usec_t until,
                 bool headless,
@@ -53,6 +55,8 @@ int find_tpm2_auto_data(
                 size_t *ret_policy_hash_size,
                 void **ret_salt,
                 size_t *ret_salt_size,
+                void **ret_srk_buf,
+                size_t *ret_srk_size,
                 TPM2Flags *ret_flags,
                 int *ret_keyslot,
                 int *ret_token);
@@ -78,6 +82,8 @@ static inline int acquire_tpm2_key(
                 size_t policy_hash_size,
                 const void *salt,
                 size_t salt_size,
+                const void *srk_buf,
+                size_t salt_srk_buf_size,
                 TPM2Flags flags,
                 usec_t until,
                 bool headless,
@@ -105,6 +111,8 @@ static inline int find_tpm2_auto_data(
                 size_t *ret_policy_hash_size,
                 void **ret_salt,
                 size_t *ret_salt_size,
+                void **ret_srk_buf,
+                size_t *ret_srk_size,
                 TPM2Flags *ret_flags,
                 int *ret_keyslot,
                 int *ret_token) {
index fa160c1f8cf7e16891af9f34dc45380a7bcbf1de..f9283ce6f48bea9a05b8f9dac4383e8332a2b24c 100644 (file)
@@ -1659,6 +1659,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                                         key_data, key_data_size,
                                         /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
                                         /* salt= */ NULL, /* salt_size= */ 0,
+                                        /* srk_buf= */ NULL, /* srk_buf_size= */ 0,
                                         arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
                                         until,
                                         arg_headless,
@@ -1704,8 +1705,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                          * works. */
 
                         for (;;) {
-                                _cleanup_free_ void *pubkey = NULL, *salt = NULL;
-                                size_t pubkey_size = 0, salt_size = 0;
+                                _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
+                                size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
                                 uint32_t hash_pcr_mask, pubkey_pcr_mask;
                                 uint16_t pcr_bank, primary_alg;
                                 TPM2Flags tpm2_flags;
@@ -1722,6 +1723,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                                                 &blob, &blob_size,
                                                 &policy_hash, &policy_hash_size,
                                                 &salt, &salt_size,
+                                                &srk_buf, &srk_buf_size,
                                                 &tpm2_flags,
                                                 &keyslot,
                                                 &token);
@@ -1752,6 +1754,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
                                                 blob, blob_size,
                                                 policy_hash, policy_hash_size,
                                                 salt, salt_size,
+                                                srk_buf, srk_buf_size,
                                                 tpm2_flags,
                                                 until,
                                                 arg_headless,
index db6e957fc9d51ee9a083a641629033a29e0197ed..eee2111abaadbc9c808a02ae0c457de232e8fd7d 100644 (file)
@@ -7,7 +7,7 @@
 
 #include "alloc-util.h"
 #include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -75,11 +75,11 @@ static int equivalent(const char *a, const char *b) {
         _cleanup_free_ char *x = NULL, *y = NULL;
         int r;
 
-        r = chase_symlinks(a, NULL, CHASE_TRAIL_SLASH, &x, NULL);
+        r = chase(a, NULL, CHASE_TRAIL_SLASH, &x, NULL);
         if (r < 0)
                 return r;
 
-        r = chase_symlinks(b, NULL, CHASE_TRAIL_SLASH, &y, NULL);
+        r = chase(b, NULL, CHASE_TRAIL_SLASH, &y, NULL);
         if (r < 0)
                 return r;
 
@@ -376,7 +376,7 @@ static int should_skip_path(const char *prefix, const char *suffix) {
         if (!dirname)
                 return -ENOMEM;
 
-        if (chase_symlinks(dirname, NULL, 0, &target, NULL) < 0)
+        if (chase(dirname, NULL, 0, &target, NULL) < 0)
                 return false;
 
         NULSTR_FOREACH(p, prefixes) {
index 2658b2da60f16929b3bcaa1fa8321136eefee306..2d33a967ca86d548c858824d1f3fee2a0981ce59 100644 (file)
@@ -13,7 +13,7 @@
 #include "architecture.h"
 #include "blockdev-util.h"
 #include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "copy.h"
 #include "device-util.h"
 #include "devnum-util.h"
@@ -61,6 +61,7 @@ static enum {
         ACTION_COPY_FROM,
         ACTION_COPY_TO,
         ACTION_DISCOVER,
+        ACTION_VALIDATE,
 } arg_action = ACTION_DISSECT;
 static char *arg_image = NULL;
 static char *arg_path = NULL;
@@ -83,6 +84,7 @@ static bool arg_rmdir = false;
 static bool arg_in_memory = false;
 static char **arg_argv = NULL;
 static char *arg_loop_ref = NULL;
+static ImagePolicy* arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
@@ -126,6 +128,8 @@ static int help(void) {
                "                          'base64:'\n"
                "     --verity-data=PATH   Specify data file with hash tree for verity if it is\n"
                "                          not embedded in IMAGE\n"
+               "     --image-policy=POLICY\n"
+               "                          Specify image dissection policy\n"
                "     --json=pretty|short|off\n"
                "                          Generate JSON output\n"
                "     --loop-ref=NAME      Set reference string for loopback device\n"
@@ -145,6 +149,7 @@ static int help(void) {
                "  -x --copy-from          Copy files from image to host\n"
                "  -a --copy-to            Copy files from host to image\n"
                "     --discover           Discover DDIs in well known directories\n"
+               "     --validate           Validate image and image policy\n"
                "\nSee the %2$s for details.\n",
                program_invocation_short_name,
                link,
@@ -221,6 +226,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_ATTACH,
                 ARG_DETACH,
                 ARG_LOOP_REF,
+                ARG_IMAGE_POLICY,
+                ARG_VALIDATE,
         };
 
         static const struct option options[] = {
@@ -250,6 +257,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "json",          required_argument, NULL, ARG_JSON          },
                 { "discover",      no_argument,       NULL, ARG_DISCOVER      },
                 { "loop-ref",      required_argument, NULL, ARG_LOOP_REF      },
+                { "image-policy",  required_argument, NULL, ARG_IMAGE_POLICY  },
+                { "validate",      no_argument,       NULL, ARG_VALIDATE      },
                 {}
         };
 
@@ -457,6 +466,16 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_VALIDATE:
+                        arg_action = ACTION_VALIDATE;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -476,7 +495,8 @@ static int parse_argv(int argc, char *argv[]) {
                 if (r < 0)
                         return r;
 
-                arg_flags |= DISSECT_IMAGE_READ_ONLY;
+                /* when dumping image info be even more liberal than otherwise, do not even require a single valid partition */
+                arg_flags |= DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ALLOW_EMPTY;
                 break;
 
         case ACTION_MOUNT:
@@ -593,7 +613,19 @@ static int parse_argv(int argc, char *argv[]) {
                 if (optind != argc)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "Expected no argument.");
+                break;
 
+        case ACTION_VALIDATE:
+                if (optind + 1 != argc)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Expected an image file path as only argument.");
+
+                r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image);
+                if (r < 0)
+                        return r;
+
+                arg_flags |= DISSECT_IMAGE_READ_ONLY;
+                arg_flags &= ~(DISSECT_IMAGE_PIN_PARTITION_DEVICES|DISSECT_IMAGE_ADD_PARTITION_DEVICES);
                 break;
 
         default:
@@ -1057,7 +1089,7 @@ static const char *pick_color_for_uid_gid(uid_t uid) {
         if (uid_is_system(uid))
                 return ansi_normal();            /* files in disk images are typically owned by root and other system users, no issue there */
         if (uid_is_dynamic(uid))
-                return ansi_highlight_red();     /* files should never be owned persistently by dynamic users, and there are just no execuses */
+                return ansi_highlight_red();     /* files should never be owned persistently by dynamic users, and there are just no excuses */
         if (uid_is_container(uid))
                 return ansi_highlight_cyan();
 
@@ -1237,7 +1269,7 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
         case ACTION_COPY_FROM: {
                 _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF;
 
-                source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+                source_fd = chase_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
                 if (source_fd < 0)
                         return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
 
@@ -1294,7 +1326,7 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) {
                         return log_error_errno(r, "Failed to extract filename from target path '%s': %m", arg_target);
                 is_dir = r == O_DIRECTORY;
 
-                r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
+                r = chase(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
                 if (r < 0)
                         return log_error_errno(r, "Failed to open '%s': %m", dn);
 
@@ -1393,7 +1425,7 @@ static int action_umount(const char *path) {
         _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
         int r;
 
-        fd = chase_symlinks_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
+        fd = chase_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
         if (fd == -ENOTDIR)
                 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
         if (fd < 0)
@@ -1689,6 +1721,31 @@ static int action_detach(const char *path) {
         return 0;
 }
 
+static int action_validate(void) {
+        int r;
+
+        r = dissect_image_file_and_warn(
+                        arg_image,
+                        &arg_verity_settings,
+                        NULL,
+                        arg_image_policy,
+                        arg_flags,
+                        NULL);
+        if (r < 0)
+                return r;
+
+        if (isatty(STDOUT_FILENO) && emoji_enabled())
+                printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES));
+
+        printf("%sOK%s", ansi_highlight_green(), ansi_normal());
+
+        if (isatty(STDOUT_FILENO) && emoji_enabled())
+                printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES));
+
+        putc('\n', stdout);
+        return 0;
+}
+
 static int run(int argc, char *argv[]) {
         _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
         _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@@ -1731,6 +1788,9 @@ static int run(int argc, char *argv[]) {
                                                                 * available we turn off partition table
                                                                 * support */
 
+        if (arg_action == ACTION_VALIDATE)
+                return action_validate();
+
         open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
         loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
 
@@ -1750,7 +1810,8 @@ static int run(int argc, char *argv[]) {
         r = dissect_loop_device_and_warn(
                         d,
                         &arg_verity_settings,
-                        NULL,
+                        /* mount_options= */ NULL,
+                        arg_image_policy,
                         arg_flags,
                         &m);
         if (r < 0)
index 5eaaff7d8417f522d0bfb1be89add2fa734adb7a..71b1e25c9daa0e36002b6ade33f1bf3864dae516 100644 (file)
@@ -10,7 +10,7 @@
 #include "alloc-util.h"
 #include "ask-password-api.h"
 #include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "copy.h"
 #include "creds-util.h"
 #include "dissect-image.h"
@@ -23,6 +23,7 @@
 #include "kbd-util.h"
 #include "libcrypt-util.h"
 #include "locale-util.h"
+#include "lock-util.h"
 #include "main-func.h"
 #include "memory-util.h"
 #include "mkdir.h"
@@ -71,6 +72,8 @@ static bool arg_force = false;
 static bool arg_delete_root_password = false;
 static bool arg_root_password_is_hashed = false;
 static bool arg_welcome = true;
+static bool arg_reset = false;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -80,6 +83,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static bool press_any_key(void) {
         char k = 0;
@@ -96,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");
@@ -228,19 +233,37 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i
         }
 }
 
-static bool locale_is_ok(const char *name) {
+static int should_configure(int dir_fd, const char *filename) {
+        assert(dir_fd >= 0);
+        assert(filename);
+
+        if (faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+                if (errno != ENOENT)
+                        return log_error_errno(errno, "Failed to access %s: %m", filename);
+
+                return true; /* missing */
+        }
 
-        if (arg_root)
-                return locale_is_valid(name);
+        return arg_force; /* exists, but if --force was given we should still configure the file. */
+}
 
+static bool locale_is_installed_bool(const char *name) {
         return locale_is_installed(name) > 0;
 }
 
-static int prompt_locale(void) {
+static bool locale_is_ok(int rfd, const char *name) {
+        assert(rfd >= 0);
+
+        return dir_fd_is_root(rfd) ? locale_is_installed_bool(name) : locale_is_valid(name);
+}
+
+static int prompt_locale(int rfd) {
         _cleanup_strv_free_ char **locales = NULL;
         bool acquired_from_creds = false;
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_locale || arg_locale_messages)
                 return 0;
 
@@ -286,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;
 
@@ -297,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;
 
@@ -309,33 +335,43 @@ static int prompt_locale(void) {
         return 0;
 }
 
-static int process_locale(void) {
-        const char *etc_localeconf;
+static int process_locale(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         char* locales[3];
         unsigned i = 0;
         int r;
 
-        etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
-        if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming locale information has been configured.",
-                          etc_localeconf);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/locale.conf",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m");
 
-        if (arg_copy_locale && arg_root) {
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/locale.conf, assuming locale information has been configured.");
+        if (r <= 0)
+                return r;
 
-                (void) mkdir_parents(etc_localeconf, 0755);
-                r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, 0, COPY_REFLINK);
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+        if (arg_copy_locale && r == 0) {
+                r = copy_file_atomic_at(AT_FDCWD, "/etc/locale.conf", pfd, f, 0644, COPY_REFLINK);
                 if (r != -ENOENT) {
                         if (r < 0)
-                                return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
+                                return log_error_errno(r, "Failed to copy host's /etc/locale.conf: %m");
 
-                        log_info("%s copied.", etc_localeconf);
+                        log_info("Copied host's /etc/locale.conf.");
                         return 0;
                 }
         }
 
-        r = prompt_locale();
+        r = prompt_locale(rfd);
         if (r < 0)
                 return r;
 
@@ -349,19 +385,20 @@ static int process_locale(void) {
 
         locales[i] = NULL;
 
-        (void) mkdir_parents(etc_localeconf, 0755);
-        r = write_env_file(etc_localeconf, locales);
+        r = write_env_file_at(pfd, f, locales);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
+                return log_error_errno(r, "Failed to write /etc/locale.conf: %m");
 
-        log_info("%s written.", etc_localeconf);
+        log_info("/etc/locale.conf written.");
         return 0;
 }
 
-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;
 
@@ -384,38 +421,48 @@ 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);
 }
 
-static int process_keymap(void) {
-        const char *etc_vconsoleconf;
+static int process_keymap(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         char **keymap;
         int r;
 
-        etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
-        if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming console has been configured.",
-                          etc_vconsoleconf);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/vconsole.conf",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m");
 
-        if (arg_copy_keymap && arg_root) {
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/vconsole.conf, assuming console has been configured.");
+        if (r <= 0)
+                return r;
 
-                (void) mkdir_parents(etc_vconsoleconf, 0755);
-                r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, 0, COPY_REFLINK);
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+        if (arg_copy_keymap && r == 0) {
+                r = copy_file_atomic_at(AT_FDCWD, "/etc/vconsole.conf", pfd, f, 0644, COPY_REFLINK);
                 if (r != -ENOENT) {
                         if (r < 0)
-                                return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
+                                return log_error_errno(r, "Failed to copy host's /etc/vconsole.conf: %m");
 
-                        log_info("%s copied.", etc_vconsoleconf);
+                        log_info("Copied host's /etc/vconsole.conf.");
                         return 0;
                 }
         }
 
-        r = prompt_keymap();
+        r = prompt_keymap(rfd);
         if (r == -ENOENT)
                 return 0; /* don't fail if no keymaps are installed */
         if (r < 0)
@@ -426,15 +473,11 @@ static int process_keymap(void) {
 
         keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
 
-        r = mkdir_parents(etc_vconsoleconf, 0755);
-        if (r < 0)
-                return log_error_errno(r, "Failed to create the parent directory of %s: %m", etc_vconsoleconf);
-
-        r = write_env_file(etc_vconsoleconf, keymap);
+        r = write_env_file_at(pfd, f, keymap);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
+                return log_error_errno(r, "Failed to write /etc/vconsole.conf: %m");
 
-        log_info("%s written.", etc_vconsoleconf);
+        log_info("/etc/vconsole.conf written.");
         return 0;
 }
 
@@ -442,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;
 
@@ -466,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);
@@ -476,36 +521,48 @@ static int prompt_timezone(void) {
         return 0;
 }
 
-static int process_timezone(void) {
-        const char *etc_localtime, *e;
+static int process_timezone(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
+        const char *e;
         int r;
 
-        etc_localtime = prefix_roota(arg_root, "/etc/localtime");
-        if (laccess(etc_localtime, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming timezone has been configured.",
-                          etc_localtime);
-                return 0;
-        }
+        assert(rfd >= 0);
 
-        if (arg_copy_timezone && arg_root) {
-                _cleanup_free_ char *p = NULL;
+        pfd = chase_and_open_parent_at(rfd, "/etc/localtime",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/localtime: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/localtime, assuming timezone has been configured.");
+        if (r <= 0)
+                return r;
+
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+
+        if (arg_copy_timezone && r == 0) {
+                _cleanup_free_ char *s = NULL;
 
-                r = readlink_malloc("/etc/localtime", &p);
+                r = readlink_malloc("/etc/localtime", &s);
                 if (r != -ENOENT) {
                         if (r < 0)
-                                return log_error_errno(r, "Failed to read host timezone: %m");
+                                return log_error_errno(r, "Failed to read host's /etc/localtime: %m");
 
-                        (void) mkdir_parents(etc_localtime, 0755);
-                        r = symlink_atomic(p, etc_localtime);
+                        r = symlinkat_atomic_full(s, pfd, f, /* make_relative= */ false);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to create %s symlink: %m", etc_localtime);
+                                return log_error_errno(r, "Failed to create /etc/localtime symlink: %m");
 
-                        log_info("%s copied.", etc_localtime);
+                        log_info("Copied host's /etc/localtime.");
                         return 0;
                 }
         }
 
-        r = prompt_timezone();
+        r = prompt_timezone(rfd);
         if (r < 0)
                 return r;
 
@@ -514,18 +571,19 @@ static int process_timezone(void) {
 
         e = strjoina("../usr/share/zoneinfo/", arg_timezone);
 
-        (void) mkdir_parents(etc_localtime, 0755);
-        r = symlink_atomic(e, etc_localtime);
+        r = symlinkat_atomic_full(e, pfd, f, /* make_relative= */ false);
         if (r < 0)
-                return log_error_errno(r, "Failed to create %s symlink: %m", etc_localtime);
+                return log_error_errno(r, "Failed to create /etc/localtime symlink: %m");
 
-        log_info("%s written", etc_localtime);
+        log_info("/etc/localtime written");
         return 0;
 }
 
-static int prompt_hostname(void) {
+static int prompt_hostname(int rfd) {
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_hostname)
                 return 0;
 
@@ -534,7 +592,7 @@ static int prompt_hostname(void) {
                 return 0;
         }
 
-        print_welcome();
+        print_welcome(rfd);
         putchar('\n');
 
         for (;;) {
@@ -563,64 +621,80 @@ static int prompt_hostname(void) {
         return 0;
 }
 
-static int process_hostname(void) {
-        const char *etc_hostname;
+static int process_hostname(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         int r;
 
-        etc_hostname = prefix_roota(arg_root, "/etc/hostname");
-        if (laccess(etc_hostname, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming hostname has been configured.",
-                          etc_hostname);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/hostname",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/hostname: %m");
 
-        r = prompt_hostname();
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/hostname, assuming hostname has been configured.");
+        if (r <= 0)
+                return r;
+
+        r = prompt_hostname(rfd);
         if (r < 0)
                 return r;
 
         if (isempty(arg_hostname))
                 return 0;
 
-        r = write_string_file(etc_hostname, arg_hostname,
-                              WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
-                              (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+        r = write_string_file_at(pfd, f, arg_hostname,
+                                 WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
+                return log_error_errno(r, "Failed to write /etc/hostname: %m");
 
-        log_info("%s written.", etc_hostname);
+        log_info("/etc/hostname written.");
         return 0;
 }
 
-static int process_machine_id(void) {
-        const char *etc_machine_id;
+static int process_machine_id(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         int r;
 
-        etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
-        if (laccess(etc_machine_id, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming machine-id has been configured.",
-                          etc_machine_id);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/machine-id",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/machine-id: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/machine-id, assuming machine-id has been configured.");
+        if (r <= 0)
+                return r;
 
         if (sd_id128_is_null(arg_machine_id)) {
                 log_debug("Initialization of machine-id was not requested, skipping.");
                 return 0;
         }
 
-        r = write_string_file(etc_machine_id, SD_ID128_TO_STRING(arg_machine_id),
-                              WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
-                              (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+        r = write_string_file_at(pfd, "machine-id", SD_ID128_TO_STRING(arg_machine_id),
+                                 WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
         if (r < 0)
-                return log_error_errno(r, "Failed to write machine id: %m");
+                return log_error_errno(r, "Failed to write /etc/machine id: %m");
 
-        log_info("%s written.", etc_machine_id);
+        log_info("/etc/machine-id written.");
         return 0;
 }
 
-static int prompt_root_password(void) {
+static int prompt_root_password(int rfd) {
         const char *msg1, *msg2;
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_root_password)
                 return 0;
 
@@ -632,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):");
@@ -681,7 +755,7 @@ static int prompt_root_password(void) {
         return 0;
 }
 
-static int find_shell(const char *path, const char *root) {
+static int find_shell(int rfd, const char *path) {
         int r;
 
         assert(path);
@@ -689,19 +763,18 @@ static int find_shell(const char *path, const char *root) {
         if (!valid_shell(path))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path);
 
-        r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, NULL, NULL);
-        if (r < 0) {
-                const char *p;
-                p = prefix_roota(root, path);
-                return log_error_errno(r, "Failed to resolve shell %s: %m", p);
-        }
+        r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to resolve shell %s: %m", path);
 
         return 0;
 }
 
-static int prompt_root_shell(void) {
+static int prompt_root_shell(int rfd) {
         int r;
 
+        assert(rfd >= 0);
+
         if (arg_root_shell)
                 return 0;
 
@@ -718,7 +791,7 @@ static int prompt_root_shell(void) {
                 return 0;
         }
 
-        print_welcome();
+        print_welcome(rfd);
         putchar('\n');
 
         for (;;) {
@@ -733,7 +806,7 @@ static int prompt_root_shell(void) {
                         break;
                 }
 
-                r = find_shell(s, arg_root);
+                r = find_shell(rfd, s);
                 if (r < 0)
                         continue;
 
@@ -744,18 +817,21 @@ static int prompt_root_shell(void) {
         return 0;
 }
 
-static int write_root_passwd(const char *passwd_path, const char *password, const char *shell) {
+static int write_root_passwd(int rfd, int etc_fd, const char *password, const char *shell) {
         _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
         _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
         int r;
 
         assert(password);
 
-        r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
+        r = fopen_temporary_at_label(etc_fd, "passwd", "passwd", &passwd, &passwd_tmp);
         if (r < 0)
                 return r;
 
-        original = fopen(passwd_path, "re");
+        r = xfopenat(etc_fd, "passwd", "re", O_NOFOLLOW, &original);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
         if (original) {
                 struct passwd *i;
 
@@ -786,7 +862,7 @@ static int write_root_passwd(const char *passwd_path, const char *password, cons
                         .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)
@@ -805,25 +881,28 @@ static int write_root_passwd(const char *passwd_path, const char *password, cons
         if (r < 0)
                 return r;
 
-        r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
+        r = renameat_and_apply_smack_floor_label(etc_fd, passwd_tmp, etc_fd, "passwd");
         if (r < 0)
                 return r;
 
         return 0;
 }
 
-static int write_root_shadow(const char *shadow_path, const char *hashed_password) {
+static int write_root_shadow(int etc_fd, const char *hashed_password) {
         _cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
         _cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
         int r;
 
         assert(hashed_password);
 
-        r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
+        r = fopen_temporary_at_label(etc_fd, "shadow", "shadow", &shadow, &shadow_tmp);
         if (r < 0)
                 return r;
 
-        original = fopen(shadow_path, "re");
+        r = xfopenat(etc_fd, "shadow", "re", O_NOFOLLOW, &original);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
         if (original) {
                 struct spwd *i;
 
@@ -874,26 +953,46 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor
         if (r < 0)
                 return r;
 
-        r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
+        r = renameat_and_apply_smack_floor_label(etc_fd, shadow_tmp, etc_fd, "shadow");
         if (r < 0)
                 return r;
 
         return 0;
 }
 
-static int process_root_account(void) {
-        _cleanup_close_ int lock = -EBADF;
+static int process_root_account(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_(release_lock_file) LockFile lock = LOCK_FILE_INIT;
         _cleanup_(erase_and_freep) char *_hashed_password = NULL;
         const char *password, *hashed_password;
-        const char *etc_passwd, *etc_shadow;
-        int r;
+        int k = 0, r;
+
+        assert(rfd >= 0);
 
-        etc_passwd = prefix_roota(arg_root, "/etc/passwd");
-        etc_shadow = prefix_roota(arg_root, "/etc/shadow");
+        pfd = chase_and_open_parent_at(rfd, "/etc/passwd",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       NULL);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/passwd: %m");
 
-        if (laccess(etc_passwd, F_OK) >= 0 && laccess(etc_shadow, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s and %s, assuming root account has been initialized.",
-                          etc_passwd, etc_shadow);
+        /* Ensure that passwd and shadow are in the same directory and are not symlinks. */
+
+        FOREACH_STRING(s, "passwd", "shadow") {
+                r = verify_regular_at(pfd, s, /* follow = */ false);
+                if (IN_SET(r, -EISDIR, -ELOOP, -EBADFD))
+                        return log_error_errno(r, "/etc/%s is not a regular file", s);
+                if (r < 0 && r != -ENOENT)
+                        return log_error_errno(r, "Failed to check whether /etc/%s is a regular file: %m", s);
+
+                r = should_configure(pfd, s);
+                if (r < 0)
+                        return r;
+
+                k += r;
+        }
+
+        if (k == 0) {
+                log_debug("Found /etc/passwd and /etc/shadow, assuming root account has been initialized.");
                 return 0;
         }
 
@@ -904,11 +1003,15 @@ static int process_root_account(void) {
                 return 0;
         }
 
-        lock = take_etc_passwd_lock(arg_root);
-        if (lock < 0)
-                return log_error_errno(lock, "Failed to take a lock on %s: %m", etc_passwd);
+        r = make_lock_file_at(pfd, ETC_PASSWD_LOCK_FILENAME, LOCK_EX, &lock);
+        if (r < 0)
+                return log_error_errno(r, "Failed to take a lock on /etc/passwd: %m");
+
+        k = dir_fd_is_root(rfd);
+        if (k < 0)
+                return log_error_errno(k, "Failed to check if directory file descriptor is root: %m");
 
-        if (arg_copy_root_shell && arg_root) {
+        if (arg_copy_root_shell && k == 0) {
                 struct passwd *p;
 
                 errno = 0;
@@ -921,11 +1024,11 @@ static int process_root_account(void) {
                         return log_oom();
         }
 
-        r = prompt_root_shell();
+        r = prompt_root_shell(rfd);
         if (r < 0)
                 return r;
 
-        if (arg_copy_root_password && arg_root) {
+        if (arg_copy_root_password && k == 0) {
                 struct spwd *p;
 
                 errno = 0;
@@ -940,7 +1043,7 @@ static int process_root_account(void) {
                 arg_root_password_is_hashed = true;
         }
 
-        r = prompt_root_password();
+        r = prompt_root_password(rfd);
         if (r < 0)
                 return r;
 
@@ -960,43 +1063,92 @@ static int process_root_account(void) {
         else
                 password = hashed_password = PASSWORD_LOCKED_AND_INVALID;
 
-        r = write_root_passwd(etc_passwd, password, arg_root_shell);
+        r = write_root_passwd(rfd, pfd, password, arg_root_shell);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_passwd);
+                return log_error_errno(r, "Failed to write /etc/passwd: %m");
 
-        log_info("%s written", etc_passwd);
+        log_info("/etc/passwd written.");
 
-        r = write_root_shadow(etc_shadow, hashed_password);
+        r = write_root_shadow(pfd, hashed_password);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
+                return log_error_errno(r, "Failed to write /etc/shadow: %m");
 
-        log_info("%s written.", etc_shadow);
+        log_info("/etc/shadow written.");
         return 0;
 }
 
-static int process_kernel_cmdline(void) {
-        const char *etc_kernel_cmdline;
+static int process_kernel_cmdline(int rfd) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         int r;
 
-        etc_kernel_cmdline = prefix_roota(arg_root, "/etc/kernel/cmdline");
-        if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force) {
-                log_debug("Found %s, assuming kernel has been configured.",
-                          etc_kernel_cmdline);
-                return 0;
-        }
+        assert(rfd >= 0);
+
+        pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline",
+                                       CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
+                                       &f);
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m");
+
+        r = should_configure(pfd, f);
+        if (r == 0)
+                log_debug("Found /etc/kernel/cmdline, assuming kernel command line has been configured.");
+        if (r <= 0)
+                return r;
 
         if (!arg_kernel_cmdline) {
                 log_debug("Creation of /etc/kernel/cmdline was not requested, skipping.");
                 return 0;
         }
 
-        r = write_string_file(etc_kernel_cmdline, arg_kernel_cmdline,
-                              WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
-                              (arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
+        r = write_string_file_at(pfd, "cmdline", arg_kernel_cmdline,
+                                 WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC);
         if (r < 0)
-                return log_error_errno(r, "Failed to write %s: %m", etc_kernel_cmdline);
+                return log_error_errno(r, "Failed to write /etc/kernel/cmdline: %m");
+
+        log_info("/etc/kernel/cmdline written.");
+        return 0;
+}
+
+static int reset_one(int rfd, const char *path) {
+        _cleanup_close_ int pfd = -EBADF;
+        _cleanup_free_ char *f = NULL;
+
+        assert(rfd >= 0);
+        assert(path);
+
+        pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f);
+        if (pfd == -ENOENT)
+                return 0;
+        if (pfd < 0)
+                return log_error_errno(pfd, "Failed to resolve %s: %m", path);
+
+        if (unlinkat(pfd, f, 0) < 0)
+                return errno == ENOENT ? 0 : log_error_errno(errno, "Failed to remove %s: %m", path);
+
+        log_info("Removed %s", path);
+        return 0;
+}
+
+static int process_reset(int rfd) {
+        int r;
+
+        assert(rfd >= 0);
+
+        if (!arg_reset)
+                return 0;
+
+        FOREACH_STRING(p,
+                       "/etc/locale.conf",
+                       "/etc/vconsole.conf",
+                       "/etc/hostname",
+                       "/etc/machine-id",
+                       "/etc/kernel/cmdline") {
+                r = reset_one(rfd, p);
+                if (r < 0)
+                        return r;
+        }
 
-        log_info("%s written.", etc_kernel_cmdline);
         return 0;
 }
 
@@ -1013,7 +1165,8 @@ static int help(void) {
                "  -h --help                       Show this help\n"
                "     --version                    Show package version\n"
                "     --root=PATH                  Operate on an alternate filesystem root\n"
-               "     --image=PATH                 Operate on an alternate filesystem image\n"
+               "     --image=PATH                 Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY        Specify disk image dissection policy\n"
                "     --locale=LOCALE              Set primary locale (LANG=)\n"
                "     --locale-messages=LOCALE     Set message locale (LC_MESSAGES=)\n"
                "     --keymap=KEYMAP              Set keymap\n"
@@ -1041,6 +1194,7 @@ static int help(void) {
                "     --force                      Overwrite existing files\n"
                "     --delete-root-password       Delete root password\n"
                "     --welcome=no                 Disable the welcome text\n"
+               "     --reset                      Remove existing files\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                link);
@@ -1054,6 +1208,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_LOCALE,
                 ARG_LOCALE_MESSAGES,
                 ARG_KEYMAP,
@@ -1082,6 +1237,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_FORCE,
                 ARG_DELETE_ROOT_PASSWORD,
                 ARG_WELCOME,
+                ARG_RESET,
         };
 
         static const struct option options[] = {
@@ -1089,6 +1245,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "version",                 no_argument,       NULL, ARG_VERSION                 },
                 { "root",                    required_argument, NULL, ARG_ROOT                    },
                 { "image",                   required_argument, NULL, ARG_IMAGE                   },
+                { "image-policy",            required_argument, NULL, ARG_IMAGE_POLICY            },
                 { "locale",                  required_argument, NULL, ARG_LOCALE                  },
                 { "locale-messages",         required_argument, NULL, ARG_LOCALE_MESSAGES         },
                 { "keymap",                  required_argument, NULL, ARG_KEYMAP                  },
@@ -1117,6 +1274,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "force",                   no_argument,       NULL, ARG_FORCE                   },
                 { "delete-root-password",    no_argument,       NULL, ARG_DELETE_ROOT_PASSWORD    },
                 { "welcome",                 required_argument, NULL, ARG_WELCOME                 },
+                { "reset",                   no_argument,       NULL, ARG_RESET                   },
                 {}
         };
 
@@ -1147,6 +1305,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_LOCALE:
                         r = free_and_strdup(&arg_locale, optarg);
                         if (r < 0)
@@ -1210,10 +1374,6 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_ROOT_SHELL:
-                        r = find_shell(optarg, arg_root);
-                        if (r < 0)
-                                return r;
-
                         r = free_and_strdup(&arg_root_shell, optarg);
                         if (r < 0)
                                 return log_oom();
@@ -1323,6 +1483,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_welcome = r;
                         break;
 
+                case ARG_RESET:
+                        arg_reset = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1330,14 +1494,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");
@@ -1351,6 +1507,7 @@ static int parse_argv(int argc, char *argv[]) {
 static int run(int argc, char *argv[]) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
+        _cleanup_close_ int rfd = -EBADF;
         int r;
 
         r = parse_argv(argc, argv);
@@ -1382,6 +1539,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_VALIDATE_OS |
@@ -1389,7 +1547,7 @@ static int run(int argc, char *argv[]) {
                                 DISSECT_IMAGE_FSCK |
                                 DISSECT_IMAGE_GROWFS,
                                 &unlink_dir,
-                                /* ret_dir_fd= */ NULL,
+                                &rfd,
                                 &loop_device);
                 if (r < 0)
                         return r;
@@ -1397,33 +1555,57 @@ static int run(int argc, char *argv[]) {
                 arg_root = strdup(unlink_dir);
                 if (!arg_root)
                         return log_oom();
+        } else {
+                rfd = open(empty_to_root(arg_root), O_DIRECTORY|O_CLOEXEC);
+                if (rfd < 0)
+                        return log_error_errno(errno, "Failed to open %s: %m", empty_to_root(arg_root));
         }
 
-        r = process_locale();
+        LOG_SET_PREFIX(arg_image ?: arg_root);
+
+        /* We check these conditions here instead of in parse_argv() so that we can take the root directory
+         * into account. */
+
+        if (arg_locale && !locale_is_ok(rfd, arg_locale))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
+        if (arg_locale_messages && !locale_is_ok(rfd, arg_locale_messages))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
+
+        if (arg_root_shell) {
+                r = find_shell(rfd, arg_root_shell);
+                if (r < 0)
+                        return r;
+        }
+
+        r = process_reset(rfd);
+        if (r < 0)
+                return r;
+
+        r = process_locale(rfd);
         if (r < 0)
                 return r;
 
-        r = process_keymap();
+        r = process_keymap(rfd);
         if (r < 0)
                 return r;
 
-        r = process_timezone();
+        r = process_timezone(rfd);
         if (r < 0)
                 return r;
 
-        r = process_hostname();
+        r = process_hostname(rfd);
         if (r < 0)
                 return r;
 
-        r = process_machine_id();
+        r = process_machine_id(rfd);
         if (r < 0)
                 return r;
 
-        r = process_root_account();
+        r = process_root_account(rfd);
         if (r < 0)
                 return r;
 
-        r = process_kernel_cmdline();
+        r = process_kernel_cmdline(rfd);
         if (r < 0)
                 return r;
 
index cfdc6b24bf908aa8198e7acc06a2499f26a41056..c0d0994e1b2d868c62d8623204a8bab63bb1eea8 100644 (file)
@@ -365,7 +365,7 @@ static int run(int argc, char *argv[]) {
                 } else
                         dash_c[0] = 0;
 
-                cmdline[i++] = "/sbin/fsck";
+                cmdline[i++] = "fsck";
                 cmdline[i++] =  arg_repair;
                 cmdline[i++] = "-T";
 
@@ -388,7 +388,7 @@ static int run(int argc, char *argv[]) {
                 cmdline[i++] = device;
                 cmdline[i++] = NULL;
 
-                execv(cmdline[0], (char**) cmdline);
+                execvp(cmdline[0], (char**) cmdline);
                 _exit(FSCK_OPERATIONAL_ERROR);
         }
 
index 28cd5f05f1fc8dfd8615d76cd28dbfca92b7fc1a..dae290c88929b222f9d5b80bc7312a9a7a0bbefa 100644 (file)
@@ -7,7 +7,7 @@
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-locator.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "efi-loader.h"
 #include "env-util.h"
 #include "fd-util.h"
@@ -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)) {
@@ -723,12 +723,11 @@ static int parse_fstab(bool initrd) {
                          * where a symlink refers to another mount target; this works assuming the sub-mountpoint
                          * target is the final directory.
                          *
-                         * FIXME: when chase_symlinks() learns to chase non-existent paths, use this here and
+                         * FIXME: when chase() learns to chase non-existent paths, use this here and
                          *        drop the prefixing with /sysroot on error below.
                          */
-                        k = chase_symlinks(where, initrd ? "/sysroot" : NULL,
-                                           CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
-                                           &canonical_where, NULL);
+                        k = chase(where, initrd ? "/sysroot" : NULL, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
+                                  &canonical_where, NULL);
                         if (k < 0) {
                                 /* If we can't canonicalize, continue as if it wasn't a symlink */
                                 log_debug_errno(k, "Failed to read symlink target for %s, using as-is: %m", where);
@@ -1081,7 +1080,7 @@ static int add_sysroot_usr_mount_or_fallback(void) {
 
         /* OK, so we didn't write anything out for /sysusr/usr/ nor /sysroot/usr/. In this case, let's make
          * sure that initrd-usr-fs.target is at least ordered after sysroot.mount so that services that order
-         * themselves get the guarantee that /usr/ is definitely mounted somewhere. */
+         * themselves after it get the guarantee that /usr/ is definitely mounted somewhere. */
 
         return generator_add_symlink(
                         arg_dest,
similarity index 76%
rename from src/basic/logarithm.h
rename to src/fundamental/logarithm.h
index 646f2d3613a39727360ab57391bcc8692e3dea90..0b03bbded12a257ec4d3d0b9ba3f337a60ffcf07 100644 (file)
@@ -3,8 +3,6 @@
 
 #include <stdint.h>
 
-#include "macro.h"
-
 /* Note: log2(0) == log2(1) == 0 here and below. */
 
 #define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
@@ -30,6 +28,14 @@ static inline unsigned u32ctz(uint32_t n) {
 #endif
 }
 
+#define popcount(n)                                             \
+        _Generic((n),                                           \
+                 unsigned char: __builtin_popcount(n),          \
+                 unsigned short: __builtin_popcount(n),         \
+                 unsigned: __builtin_popcount(n),               \
+                 unsigned long: __builtin_popcountl(n),         \
+                 unsigned long long: __builtin_popcountll(n))
+
 #define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0)
 #define NONCONST_LOG2U(x) ({                                             \
                 unsigned _x = (x);                                       \
index fa5b5d221a4d5575b966a09920d88a2ddd7cbfdf..e901e8fb596a2e0f49803002fe72b153cde8ae64 100644 (file)
@@ -6,12 +6,13 @@
 #endif
 
 #include <limits.h>
+#include <stdalign.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
 #define _align_(x) __attribute__((__aligned__(x)))
-#define _alignas_(x) __attribute__((__aligned__(__alignof__(x))))
+#define _alignas_(x) __attribute__((__aligned__(alignof(x))))
 #define _alignptr_ __attribute__((__aligned__(sizeof(void *))))
 #define _cleanup_(x) __attribute__((__cleanup__(x)))
 #define _const_ __attribute__((__const__))
@@ -343,9 +344,9 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
 #define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p)))
 
 /* Checks if the specified pointer is aligned as appropriate for the specific type */
-#define IS_ALIGNED16(p) (((uintptr_t) p) % __alignof__(uint16_t) == 0)
-#define IS_ALIGNED32(p) (((uintptr_t) p) % __alignof__(uint32_t) == 0)
-#define IS_ALIGNED64(p) (((uintptr_t) p) % __alignof__(uint64_t) == 0)
+#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0)
+#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0)
+#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0)
 
 /* Same as ALIGN_TO but callable in constant contexts. */
 #define CONST_ALIGN_TO(l, ali)                                         \
@@ -363,7 +364,7 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
 #define CAST_ALIGN_PTR(t, p)                                    \
         ({                                                      \
                 const void *_p = (p);                           \
-                assert(((uintptr_t) _p) % __alignof__(t) == 0); \
+                assert(((uintptr_t) _p) % alignof(t) == 0); \
                 (t *) _p;                                       \
         })
 
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 9ccd78af658e11a636a0514dc777f4c6cedeff1d..030ada5d6e89a237db0228363f7f2301cfdba3d8 100644 (file)
@@ -23,6 +23,7 @@
 #include "fstab-util.h"
 #include "generator.h"
 #include "gpt.h"
+#include "image-policy.h"
 #include "initrd-util.h"
 #include "mkdir.h"
 #include "mountpoint-util.h"
@@ -43,6 +44,9 @@ static bool arg_root_enabled = true;
 static char *arg_root_fstype = NULL;
 static char *arg_root_options = NULL;
 static int arg_root_rw = -1;
+static ImagePolicy *arg_image_policy = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 STATIC_DESTRUCTOR_REGISTER(arg_root_fstype, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
@@ -744,22 +748,24 @@ static int enumerate_partitions(dev_t devnum) {
 
         r = dissect_loop_device(
                         loop,
-                        NULL, NULL,
+                        /* verity= */ NULL,
+                        /* mount_options= */ NULL,
+                        arg_image_policy ?: &image_policy_host,
                         DISSECT_IMAGE_GPT_ONLY|
                         DISSECT_IMAGE_USR_NO_ROOT|
-                        DISSECT_IMAGE_DISKSEQ_DEVNODE,
+                        DISSECT_IMAGE_DISKSEQ_DEVNODE|
+                        DISSECT_IMAGE_ALLOW_EMPTY,
                         /* NB! Unlike most other places where we dissect block devices we do not use
                          * DISSECT_IMAGE_ADD_PARTITION_DEVICES here: we want that the kernel finds the
                          * devices, and udev probes them before we mount them via .mount units much later
                          * on. And thus we also don't set DISSECT_IMAGE_PIN_PARTITION_DEVICES here, because
                          * we don't actually mount anything immediately. */
                         &m);
-        if (r == -ENOPKG) {
-                log_debug_errno(r, "No suitable partition table found on block device %s, ignoring.", devname);
-                return 0;
+        if (r < 0) {
+                bool ok = r == -ENOPKG;
+                dissect_log_error(ok ? LOG_DEBUG : LOG_ERR, r, devname, NULL);
+                return ok ? 0 : r;
         }
-        if (r < 0)
-                return log_error_errno(r, "Failed to dissect partition table of block device %s: %m", devname);
 
         if (m->partitions[PARTITION_SWAP].found) {
                 k = add_partition_swap(m->partitions + PARTITION_SWAP);
@@ -882,6 +888,8 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 arg_root_rw = true;
         else if (proc_cmdline_key_streq(key, "ro") && !value)
                 arg_root_rw = false;
+        else if (proc_cmdline_key_streq(key, "systemd.image_policy"))
+                return parse_image_policy_argument(optarg, &arg_image_policy);
 
         return 0;
 }
index 158164ae81c116ab5b625cd24d06879c162cb89b..b265f8741e2aaa8fac93005bf42af07b4b2295a5 100644 (file)
@@ -3576,38 +3576,29 @@ static int parse_argv(int argc, char *argv[]) {
                         strv_uniq(arg_fido2_device);
                         break;
 
-                case ARG_FIDO2_WITH_PIN: {
-                        bool lock_with_pin;
-
-                        r = parse_boolean_argument("--fido2-with-client-pin=", optarg, &lock_with_pin);
+                case ARG_FIDO2_WITH_PIN:
+                        r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, lock_with_pin);
+                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r);
                         break;
-                }
 
-                case ARG_FIDO2_WITH_UP: {
-                        bool lock_with_up;
-
-                        r = parse_boolean_argument("--fido2-with-user-presence=", optarg, &lock_with_up);
+                case ARG_FIDO2_WITH_UP:
+                        r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, lock_with_up);
+                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r);
                         break;
-                }
 
-                case ARG_FIDO2_WITH_UV: {
-                        bool lock_with_uv;
-
-                        r = parse_boolean_argument("--fido2-with-user-verification=", optarg, &lock_with_uv);
+                case ARG_FIDO2_WITH_UV:
+                        r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
-                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, lock_with_uv);
+                        SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r);
                         break;
-                }
 
                 case ARG_RECOVERY_KEY:
                         r = parse_boolean(optarg);
@@ -3727,8 +3718,6 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_DROP_CACHES: {
-                        bool drop_caches;
-
                         if (isempty(optarg)) {
                                 r = drop_from_identity("dropCaches");
                                 if (r < 0)
@@ -3736,7 +3725,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 break;
                         }
 
-                        r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
+                        r = parse_boolean_argument("--drop-caches=", optarg, NULL);
                         if (r < 0)
                                 return r;
 
index 258d2b16ed0dd46dcf62dbe153f427ccdc2a9e2d..92fe5a88b8211fa79ff9d741b09130d1a510ea7c 100644 (file)
@@ -1096,7 +1096,7 @@ static ssize_t read_datagram(
                     cmsg->cmsg_type == SCM_CREDENTIALS &&
                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
                         assert(!sender);
-                        sender = (struct ucred*) CMSG_DATA(cmsg);
+                        sender = CMSG_TYPED_DATA(cmsg, struct ucred);
                 }
 
                 if (cmsg->cmsg_level == SOL_SOCKET &&
@@ -1108,7 +1108,7 @@ static ssize_t read_datagram(
                         }
 
                         assert(passed_fd < 0);
-                        passed_fd = *(int*) CMSG_DATA(cmsg);
+                        passed_fd = *CMSG_TYPED_DATA(cmsg, int);
                 }
         }
 
index ac3d2b417c631080a029dfd6d5268a955d77be55..a24e0a70b2a0011fdb97e481893298c7c0914c76 100644 (file)
@@ -234,7 +234,7 @@ static int run_fsck(const char *node, const char *fstype) {
                 return r;
         if (r == 0) {
                 /* Child */
-                execl("/sbin/fsck", "/sbin/fsck", "-aTl", node, NULL);
+                execlp("fsck", "fsck", "-aTl", node, NULL);
                 log_open();
                 log_error_errno(errno, "Failed to execute fsck: %m");
                 _exit(FSCK_OPERATIONAL_ERROR);
index cf42a0b94dfae6e7aa3254db66eadfa80ac6b464..130617a157fd894a34fff99353baed43e4f03269 100644 (file)
@@ -184,7 +184,7 @@ static int append_identity_range(char **text, uid_t start, uid_t next_start, uid
                           exclude + 1, exclude + 1, next_start - exclude - 1);
 }
 
-static int make_userns(uid_t stored_uid, uid_t exposed_uid) {
+static int make_home_userns(uid_t stored_uid, uid_t exposed_uid) {
         _cleanup_free_ char *text = NULL;
         _cleanup_close_ int userns_fd = -EBADF;
         int r;
@@ -269,7 +269,7 @@ int home_shift_uid(int dir_fd, const char *target, uid_t stored_uid, uid_t expos
                 return log_error_errno(errno, "Failed to open tree of home directory: %m");
         }
 
-        userns_fd = make_userns(stored_uid, exposed_uid);
+        userns_fd = make_home_userns(stored_uid, exposed_uid);
         if (userns_fd < 0)
                 return userns_fd;
 
index 8a7bfc5837f26fe9b9e3bccea4c3b425994b1229..6a3e656035fea4c2355f09334dfba5b63c24da2b 100644 (file)
@@ -140,7 +140,7 @@ static int acquire_user_record(
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
                 _cleanup_free_ char *generic_field = NULL, *json_copy = NULL;
 
-                r = pam_acquire_bus_connection(handle, &bus);
+                r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
                 if (r != PAM_SUCCESS)
                         return r;
 
@@ -513,7 +513,7 @@ static int acquire_home(
         if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0)
                 return PAM_SUCCESS;
 
-        r = pam_acquire_bus_connection(handle, &bus);
+        r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
         if (r != PAM_SUCCESS)
                 return r;
 
@@ -727,7 +727,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
 
         r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug);
         if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
-                return PAM_SUCCESS;
+                goto success; /* Need to free the bus resource, as acquire_home() takes a reference. */
         if (r != PAM_SUCCESS)
                 return r;
 
@@ -741,10 +741,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                 return pam_syslog_pam_error(handle, LOG_ERR, r,
                                             "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
 
+success:
         /* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
          * not going to process the bus connection in that time, so let's better close before the daemon
          * kicks us off because we are not processing anything. */
-        (void) pam_release_bus_connection(handle);
+        (void) pam_release_bus_connection(handle, "pam-systemd-home");
         return PAM_SUCCESS;
 }
 
@@ -784,7 +785,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
         if (r != PAM_SUCCESS)
                 return r;
 
-        r = pam_acquire_bus_connection(handle, &bus);
+        r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
         if (r != PAM_SUCCESS)
                 return r;
 
@@ -943,7 +944,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
         if (debug)
                 pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
 
-        r = pam_acquire_bus_connection(handle, &bus);
+        r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
         if (r != PAM_SUCCESS)
                 return r;
 
index ed757019cf989c478af933995b2b09b73d28ee4e..a58587beb26fd4fdf04fd115232b154fb24b926d 100644 (file)
@@ -554,9 +554,9 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
         };
         struct ucred *ucred;
         Manager *m = userdata;
-        char *p, *e;
         Transfer *t;
         ssize_t n;
+        char *p;
         int r;
 
         n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
@@ -590,17 +590,11 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void
 
         buf[n] = 0;
 
-        p = startswith(buf, "X_IMPORT_PROGRESS=");
-        if (!p) {
-                p = strstr(buf, "\nX_IMPORT_PROGRESS=");
-                if (!p)
-                        return 0;
-
-                p += 19;
-        }
+        p = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
+        if (!p)
+                return 0;
 
-        e = strchrnul(p, '\n');
-        *e = 0;
+        truncate_nl(p);
 
         r = parse_percent(p);
         if (r < 0) {
index 8a152d0f6f41f8e9390b75f635fafe6bccbafeb6..3befa96a042cb45d6e0718f1025d1e400fe54a5e 100644 (file)
@@ -327,7 +327,6 @@ static int raw_pull_copy_auxiliary_file(
                         *path,
                         local,
                         0644,
-                        0, 0,
                         COPY_REFLINK |
                         (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) |
                         (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0));
index d3a1179ebc7616eb803cdd05b3f72ad785d4246f..f22eb0e3a33da5ebe2d8aa4cf0b50359dda09d64 100644 (file)
@@ -275,7 +275,6 @@ static int tar_pull_make_local_copy(TarPull *i) {
                                 i->settings_path,
                                 local_settings,
                                 0664,
-                                0, 0,
                                 COPY_REFLINK |
                                 (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) |
                                 (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0));
index 956c96c5e34e73e6199d254b68cde0a7cc8781d0..c5ecc2b844b6d174926707be0c71789d8710c482 100644 (file)
@@ -42,7 +42,7 @@ static int http_socket = -1, https_socket = -1;
 static char** arg_gnutls_log = NULL;
 
 static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID;
-static const char* arg_output = NULL;
+static char *arg_output = NULL;
 
 static char *arg_key = NULL;
 static char *arg_cert = NULL;
@@ -62,6 +62,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_gnutls_log, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_cert, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_trust, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_output, freep);
 
 static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
         [JOURNAL_WRITE_SPLIT_NONE] = "none",
@@ -734,7 +735,7 @@ static int create_remoteserver(
                    create output as expected. */
                 r = journal_remote_get_writer(s, NULL, &s->_single_writer);
                 if (r < 0)
-                        return r;
+                        return log_warning_errno(r, "Failed to get writer: %m");
         }
 
         return 0;
@@ -957,7 +958,9 @@ static int parse_argv(int argc, char *argv[]) {
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                        "cannot use --output/-o more than once");
 
-                        arg_output = optarg;
+                        r = parse_path_argument(optarg, /* suppress_root = */ false, &arg_output);
+                        if (r < 0)
+                                return r;
                         break;
 
                 case ARG_SPLIT_MODE:
index ace4a1cfadd91538d69ead2a6684b794c25a01ea..54ed1e23a0e008ff4b40bef0f62bbdfa480ebe07 100644 (file)
@@ -21,19 +21,22 @@ static int do_rotate(ManagedJournalFile **f, MMapCache *m, JournalFileFlags file
         return r;
 }
 
-Writer* writer_new(RemoteServer *server) {
+int writer_new(RemoteServer *server, Writer **ret) {
         _cleanup_(writer_unrefp) Writer *w = NULL;
         int r;
 
+        assert(server);
+        assert(ret);
+
         w = new0(Writer, 1);
         if (!w)
-                return NULL;
+                return -ENOMEM;
 
         w->metrics = server->metrics;
 
         w->mmap = mmap_cache_new();
         if (!w->mmap)
-                return NULL;
+                return -ENOMEM;
 
         w->n_ref = 1;
         w->server = server;
@@ -41,16 +44,15 @@ Writer* writer_new(RemoteServer *server) {
         if (is_dir(server->output, /* follow = */ true) > 0) {
                 w->output = strdup(server->output);
                 if (!w->output)
-                        return NULL;
+                        return -ENOMEM;
         } else {
                 r = path_extract_directory(server->output, &w->output);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to find directory of file \"%s\": %m", server->output);
-                        return NULL;
-                }
+                if (r < 0)
+                        return r;
         }
 
-        return TAKE_PTR(w);
+        *ret = TAKE_PTR(w);
+        return 0;
 }
 
 static Writer* writer_free(Writer *w) {
index c140f6cba3d4f07bef66be37e35754176d0ba21a..55513d9bf7ce404179854acc66de210f12f611ec 100644 (file)
@@ -20,7 +20,7 @@ typedef struct Writer {
         unsigned n_ref;
 } Writer;
 
-Writer* writer_new(RemoteServer* server);
+int writer_new(RemoteServer *server, Writer **ret);
 Writer* writer_ref(Writer *w);
 Writer* writer_unref(Writer *w);
 
index a670468884133d3d2db09c119c51d512954c9f11..6e993863bfbfdc5fc1aa49cf93d20be71b3783f3 100644 (file)
@@ -122,14 +122,14 @@ int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer
         if (w)
                 writer_ref(w);
         else {
-                w = writer_new(s);
-                if (!w)
-                        return log_oom();
+                r = writer_new(s, &w);
+                if (r < 0)
+                        return r;
 
                 if (s->split_mode == JOURNAL_WRITE_SPLIT_HOST) {
                         w->hashmap_key = strdup(key);
                         if (!w->hashmap_key)
-                                return log_oom();
+                                return -ENOMEM;
                 }
 
                 r = open_output(s, w, host);
@@ -142,7 +142,6 @@ int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer
         }
 
         *writer = TAKE_PTR(w);
-
         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 59736143bc0d8cfc450f231af5af9d6627109406..12119b302c649dbe3c4c314b2731d664322b6cc4 100644 (file)
 #include "bus-error.h"
 #include "bus-util.h"
 #include "catalog.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "chattr-util.h"
 #include "constants.h"
+#include "devnum-util.h"
 #include "dissect-image.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -134,6 +135,7 @@ static Set *arg_output_fields = NULL;
 static const char *arg_pattern = NULL;
 static pcre2_code *arg_compiled_pattern = NULL;
 static PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
+ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep);
@@ -145,6 +147,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static enum {
         ACTION_SHOW,
@@ -192,7 +195,7 @@ static int add_matches_for_device(sd_journal *j, const char *devpath) {
 
         r = sd_device_new_from_stat_rdev(&device, &st);
         if (r < 0)
-                return log_error_errno(r, "Failed to get device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev));
+                return log_error_errno(r, "Failed to get device from devnum " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(st.st_rdev));
 
         for (d = device; d; ) {
                 _cleanup_free_ char *match = NULL;
@@ -222,7 +225,7 @@ static int add_matches_for_device(sd_journal *j, const char *devpath) {
                         if (r < 0)
                                 return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode);
 
-                        r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev));
+                        r = asprintf(&match1, "_KERNEL_DEVICE=%c" DEVNUM_FORMAT_STR, S_ISBLK(st.st_mode) ? 'b' : 'c', DEVNUM_FORMAT_VAL(st.st_rdev));
                         if (r < 0)
                                 return log_oom();
 
@@ -326,8 +329,9 @@ static int help(void) {
                "  -m --merge                 Show entries from all available journals\n"
                "  -D --directory=PATH        Show journal files from directory\n"
                "     --file=PATH             Show journal file\n"
-               "     --root=ROOT             Operate on files below a root directory\n"
-               "     --image=IMAGE           Operate on files in filesystem image\n"
+               "     --root=PATH             Operate on an alternate filesystem root\n"
+               "     --image=PATH            Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY   Specify disk image dissection policy\n"
                "     --namespace=NAMESPACE   Show journal data from specified journal namespace\n"
                "\n%3$sFiltering Options:%4$s\n"
                "  -S --since=DATE            Show entries not older than the specified date\n"
@@ -415,6 +419,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SYSTEM,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_HEADER,
                 ARG_FACILITY,
                 ARG_SETUP_KEYS,
@@ -472,6 +477,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "file",                 required_argument, NULL, ARG_FILE                 },
                 { "root",                 required_argument, NULL, ARG_ROOT                 },
                 { "image",                required_argument, NULL, ARG_IMAGE                },
+                { "image-policy",         required_argument, NULL, ARG_IMAGE_POLICY         },
                 { "header",               no_argument,       NULL, ARG_HEADER               },
                 { "identifier",           required_argument, NULL, 't'                      },
                 { "priority",             required_argument, NULL, 'p'                      },
@@ -730,6 +736,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case 'c':
                         arg_cursor = optarg;
                         break;
@@ -1033,7 +1045,6 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
                 }
-
                 case '?':
                         return -EINVAL;
 
@@ -1086,8 +1097,10 @@ static int parse_argv(int argc, char *argv[]) {
                         return r;
 
                 /* When --grep is used along with --lines, we don't know how many lines we can print.
-                 * So we search backwards and count until enough lines have been printed or we hit the head. */
-                if (arg_lines >= 0)
+                 * So we search backwards and count until enough lines have been printed or we hit the head.
+                 * An exception is that --follow might set arg_lines, so let's not imply --reverse
+                 * if that is specified. */
+                if (arg_lines >= 0 && !arg_follow)
                         arg_reverse = true;
         }
 
@@ -1112,7 +1125,7 @@ static int add_matches(sd_journal *j, char **args) {
                         _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
                         struct stat st;
 
-                        r = chase_symlinks(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL);
+                        r = chase(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL);
                         if (r < 0)
                                 return log_error_errno(r, "Couldn't canonicalize path: %m");
 
@@ -2124,6 +2137,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_VALIDATE_OS |
index a8958fcb0cfd0d332afa8d1d9972388fad6bc253..47f1d8b5043c427ae60a42a16f038a4779cdb2a4 100644 (file)
@@ -935,10 +935,9 @@ static void server_write_to_journal(
                 if (!f)
                         return;
 
-                if (journal_file_rotate_suggested(f->file, s->max_file_usec, LOG_INFO)) {
-                        log_ratelimit_info(JOURNAL_LOG_RATELIMIT,
-                                           "%s: Journal header limits reached or header out-of-date, rotating.",
-                                           f->file->path);
+                if (journal_file_rotate_suggested(f->file, s->max_file_usec, LOG_DEBUG)) {
+                        log_debug("%s: Journal header limits reached or header out-of-date, rotating.",
+                                  f->file->path);
                         rotate = true;
                 }
         }
@@ -1417,7 +1416,7 @@ int server_process_datagram(
         size_t label_len = 0, m;
         Server *s = ASSERT_PTR(userdata);
         struct ucred *ucred = NULL;
-        struct timeval *tv = NULL;
+        struct timeval tv_buf, *tv = NULL;
         struct cmsghdr *cmsg;
         char *label = NULL;
         struct iovec iovec;
@@ -1486,21 +1485,21 @@ int server_process_datagram(
                     cmsg->cmsg_type == SCM_CREDENTIALS &&
                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
                         assert(!ucred);
-                        ucred = (struct ucred*) CMSG_DATA(cmsg);
+                        ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
                 } else if (cmsg->cmsg_level == SOL_SOCKET &&
                          cmsg->cmsg_type == SCM_SECURITY) {
                         assert(!label);
-                        label = (char*) CMSG_DATA(cmsg);
+                        label = CMSG_TYPED_DATA(cmsg, char);
                         label_len = cmsg->cmsg_len - CMSG_LEN(0);
                 } else if (cmsg->cmsg_level == SOL_SOCKET &&
-                           cmsg->cmsg_type == SO_TIMESTAMP &&
+                           cmsg->cmsg_type == SCM_TIMESTAMP &&
                            cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) {
                         assert(!tv);
-                        tv = (struct timeval*) CMSG_DATA(cmsg);
+                        tv = memcpy(&tv_buf, CMSG_DATA(cmsg), sizeof(struct timeval));
                 } else if (cmsg->cmsg_level == SOL_SOCKET &&
                          cmsg->cmsg_type == SCM_RIGHTS) {
                         assert(!fds);
-                        fds = (int*) CMSG_DATA(cmsg);
+                        fds = CMSG_TYPED_DATA(cmsg, int);
                         n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
                 }
 
@@ -2124,7 +2123,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 538d999de0a611c232173d34c48151ed8a5d74ae..37d41f0678e2491cfba10a53ddab78c78c51366a 100644 (file)
@@ -222,10 +222,11 @@ static void managed_journal_file_set_offline_internal(ManagedJournalFile *f) {
 
                                 log_debug_errno(r, "Failed to re-enable copy-on-write for %s: %m, rewriting file", f->file->path);
 
-                                r = copy_file_atomic(FORMAT_PROC_FD_PATH(f->file->fd), f->file->path, f->file->mode,
-                                                     0,
-                                                     FS_NOCOW_FL,
-                                                     COPY_REPLACE | COPY_FSYNC | COPY_HOLES | COPY_ALL_XATTRS);
+                                r = copy_file_atomic_full(FORMAT_PROC_FD_PATH(f->file->fd), f->file->path, f->file->mode,
+                                                          0,
+                                                          FS_NOCOW_FL,
+                                                          COPY_REPLACE | COPY_FSYNC | COPY_HOLES | COPY_ALL_XATTRS,
+                                                          NULL, NULL);
                                 if (r < 0) {
                                         log_debug_errno(r, "Failed to rewrite %s: %m", f->file->path);
                                         continue;
index 1490113f1cca49b5d686c4622ea9e29f15f3eb8f..c540a1ce45371559ea37385e9274f90e407aef78 100644 (file)
@@ -105,6 +105,7 @@ tests += [
         {
                 'sources' : files('test-journal-verify.c'),
                 'base' : test_journal_base,
+                'timeout' : 90,
         },
         {
                 'sources' : files('test-journal.c'),
index 7a90079fc8e6b1e65bff7edd4c3d187ee1a9cabd..f15071d81d31750b5b18d07596d502f9baf70c5e 100644 (file)
@@ -12,6 +12,7 @@
 #include "managed-journal-file.h"
 #include "mmap-cache.h"
 #include "rm-rf.h"
+#include "strv.h"
 #include "terminal-util.h"
 #include "tests.h"
 
@@ -45,7 +46,17 @@ static int raw_verify(const char *fn, const char *verification_key) {
         m = mmap_cache_new();
         assert_se(m != NULL);
 
-        r = journal_file_open(-1, fn, O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), 0666, UINT64_MAX, NULL, m, NULL, &f);
+        r = journal_file_open(
+                        /* fd= */ -1,
+                        fn,
+                        O_RDONLY,
+                        JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+                        0666,
+                        /* compress_threshold_bytes= */ UINT64_MAX,
+                        /* metrics= */ NULL,
+                        m,
+                        /* template= */ NULL,
+                        &f);
         if (r < 0)
                 return r;
 
@@ -55,16 +66,15 @@ static int raw_verify(const char *fn, const char *verification_key) {
         return r;
 }
 
-static int run_test(int argc, char *argv[]) {
+static int run_test(const char *verification_key, ssize_t max_iterations) {
         _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
         char t[] = "/var/tmp/journal-XXXXXX";
-        unsigned n;
+        struct stat st;
         JournalFile *f;
         ManagedJournalFile *df;
-        const char *verification_key = argv[1];
         usec_t from = 0, to = 0, total = 0;
-        struct stat st;
-        uint64_t p;
+        uint64_t start, end;
+        int r;
 
         m = mmap_cache_new();
         assert_se(m != NULL);
@@ -79,32 +89,57 @@ static int run_test(int argc, char *argv[]) {
         assert_se(chdir(t) >= 0);
         (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
 
-        log_info("Generating...");
-
-        assert_se(managed_journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), 0666, UINT64_MAX, NULL, m, NULL, NULL, &df) == 0);
-
-        for (n = 0; n < N_ENTRIES; n++) {
+        log_info("Generating a test journal");
+
+        assert_se(managed_journal_file_open(
+                                /* fd= */ -1,
+                                "test.journal",
+                                O_RDWR|O_CREAT,
+                                JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+                                0666,
+                                /* compress_threshold_bytes= */ UINT64_MAX,
+                                /* metrics= */ NULL,
+                                m,
+                                /* deferred_closes= */ NULL,
+                                /* template= */ NULL,
+                                &df) == 0);
+
+        for (size_t n = 0; n < N_ENTRIES; n++) {
+                _cleanup_free_ char *test = NULL;
                 struct iovec iovec;
                 struct dual_timestamp ts;
-                char *test;
 
                 dual_timestamp_get(&ts);
-
                 assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE));
-
                 iovec = IOVEC_MAKE_STRING(test);
-
-                assert_se(journal_file_append_entry(df->file, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0);
-
-                free(test);
+                assert_se(journal_file_append_entry(
+                                        df->file,
+                                        &ts,
+                                        /* boot_id= */ NULL,
+                                        &iovec,
+                                        /* n_iovec= */ 1,
+                                        /* seqnum= */ NULL,
+                                        /* seqnum_id= */ NULL,
+                                        /* ret_object= */ NULL,
+                                        /* ret_offset= */ NULL) == 0);
         }
 
         (void) managed_journal_file_close(df);
 
-        log_info("Verifying...");
-
-        assert_se(journal_file_open(-1, "test.journal", O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL: 0), 0666, UINT64_MAX, NULL, m, NULL, &f) == 0);
-        /* journal_file_print_header(f); */
+        log_info("Verifying with key: %s", strna(verification_key));
+
+        assert_se(journal_file_open(
+                                /* fd= */ -1,
+                                "test.journal",
+                                O_RDONLY,
+                                JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+                                0666,
+                                /* compress_threshold_bytes= */ UINT64_MAX,
+                                /* metrics= */ NULL,
+                                m,
+                                /* template= */ NULL,
+                                &f) == 0);
+        journal_file_print_header(f);
         journal_file_dump(f);
 
         assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0);
@@ -116,37 +151,61 @@ static int run_test(int argc, char *argv[]) {
                          FORMAT_TIMESPAN(total > to ? total - to : 0, 0));
 
         (void) journal_file_close(f);
+        assert_se(stat("test.journal", &st) >= 0);
 
-        if (verification_key) {
-                log_info("Toggling bits...");
-
-                assert_se(stat("test.journal", &st) >= 0);
+        start = 38448 * 8 + 0;
+        end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations;
+        log_info("Toggling bits %"PRIu64 " to %"PRIu64, start, end);
 
-                for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) {
-                        bit_toggle("test.journal", p);
+        for (uint64_t p = start; p < end; p++) {
+                bit_toggle("test.journal", p);
 
+                if (max_iterations < 0)
                         log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8);
 
-                        if (raw_verify("test.journal", verification_key) >= 0)
-                                log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8);
+                r = raw_verify("test.journal", verification_key);
+                /* Suppress the notice when running in the limited (CI) mode */
+                if (verification_key && max_iterations < 0 && r >= 0)
+                        log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8);
 
-                        bit_toggle("test.journal", p);
-                }
+                bit_toggle("test.journal", p);
         }
 
-        log_info("Exiting...");
-
         assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
 
         return 0;
 }
 
 int main(int argc, char *argv[]) {
+        const char *verification_key = NULL;
+        int max_iterations = 512;
+
+        if (argc > 1) {
+                /* Don't limit the number of iterations when the verification key
+                 * is provided on the command line, we want to do that only in CIs */
+                verification_key = argv[1];
+                max_iterations = -1;
+        }
+
         assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
-        run_test(argc, argv);
+        run_test(verification_key, max_iterations);
 
         assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
-        run_test(argc, argv);
+        run_test(verification_key, max_iterations);
+
+#if HAVE_GCRYPT
+        /* If we're running without any arguments and we're compiled with gcrypt
+         * check the journal verification stuff with a valid key as well */
+        if (argc <= 1) {
+                verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900";
+
+                assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
+                run_test(verification_key, max_iterations);
+
+                assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
+                run_test(verification_key, max_iterations);
+        }
+#endif
 
         return 0;
 }
index 43b6e7d7926358e3aa60d57403403cbbb96e292b..4520c01d72f19252b5b653206130163956924593 100644 (file)
@@ -9,3 +9,4 @@
 
 #layout=bls|other|...
 #initrd_generator=dracut|...
+#uki_generator=ukify|...
index a16c29d2aa57259b5ac112d6a39ea7b42a308ca4..611f67243ceb01ebfd8cbfa36711d6a907d27ba1 100755 (executable)
@@ -106,9 +106,10 @@ else
     shift
 fi
 
-# These two settings are only settable via install.conf
+# These three settings are only settable via install.conf
 layout=
 initrd_generator=
+uki_generator=
 # These two settings can be inherited from the environment
 _MACHINE_ID_SAVED="$MACHINE_ID"
 _BOOT_ROOT_SAVED="$BOOT_ROOT"
@@ -132,6 +133,8 @@ fi
 [ -n "$layout" ] && log_verbose "$install_conf configures layout=$layout"
 [ -n "$initrd_generator" ] && \
     log_verbose "$install_conf configures initrd_generator=$initrd_generator"
+[ -n "$uki_generator" ] && \
+    log_verbose "$install_conf configures uki_generator=$uki_generator"
 
 if [ -n "$_MACHINE_ID_SAVED" ]; then
     MACHINE_ID="$_MACHINE_ID_SAVED"
@@ -307,6 +310,7 @@ export KERNEL_INSTALL_ENTRY_TOKEN="$ENTRY_TOKEN"
 export KERNEL_INSTALL_BOOT_ROOT="$BOOT_ROOT"
 export KERNEL_INSTALL_LAYOUT="$layout"
 export KERNEL_INSTALL_INITRD_GENERATOR="$initrd_generator"
+export KERNEL_INSTALL_UKI_GENERATOR="$uki_generator"
 export KERNEL_INSTALL_STAGING_AREA
 
 MAKE_ENTRY_DIR_ABS=0
@@ -383,6 +387,7 @@ case "$COMMAND" in
         echo "KERNEL_INSTALL_BOOT_ROOT: $KERNEL_INSTALL_BOOT_ROOT"
         echo "KERNEL_INSTALL_LAYOUT: $KERNEL_INSTALL_LAYOUT"
         echo "KERNEL_INSTALL_INITRD_GENERATOR: $KERNEL_INSTALL_INITRD_GENERATOR"
+        echo "KERNEL_INSTALL_UKI_GENERATOR: $KERNEL_INSTALL_UKI_GENERATOR"
         echo "ENTRY_DIR_ABS: $KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN/\$KERNEL_VERSION"
 
         # Assert that ENTRY_DIR_ABS actually matches what we are printing here
index bc833e4cb263c66a3dca2f78be85ffc6138060b0..4cbf16f0df2754e42ece790191d9fbfc4ee45d78 100755 (executable)
@@ -1,16 +1,21 @@
 #!/usr/bin/env bash
 # SPDX-License-Identifier: LGPL-2.1-or-later
 # shellcheck disable=SC2235
-set -eu
+set -eux
 set -o pipefail
 
+export SYSTEMD_LOG_LEVEL=debug
+
 kernel_install="${1:?}"
 plugin="${2:?}"
+if [[ -d "${PROJECT_BUILD_ROOT:-}" ]]; then
+    bootctl="${PROJECT_BUILD_ROOT}/bootctl"
+else
+    bootctl=
+fi
 
 D="$(mktemp --tmpdir --directory "test-kernel-install.XXXXXXXXXX")"
 
-export _KERNEL_INSTALL_BOOTCTL="$PROJECT_BUILD_ROOT/bootctl"
-
 # shellcheck disable=SC2064
 trap "rm -rf '$D'" EXIT INT QUIT PIPE
 mkdir -p "$D/boot"
@@ -52,9 +57,9 @@ grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
 "$kernel_install" inspect
 
 "$kernel_install" -v remove 1.1.1
-test ! -f "$entry"
-test ! -f "$BOOT_ROOT/the-token/1.1.1/linux"
-test ! -f "$BOOT_ROOT/the-token/1.1.1/initrd"
+test ! -e "$entry"
+test ! -e "$BOOT_ROOT/the-token/1.1.1/linux"
+test ! -e "$BOOT_ROOT/the-token/1.1.1/initrd"
 
 # Invoke kernel-install as installkernel
 ln -s --relative -v "$kernel_install" "$D/sources/installkernel"
@@ -86,7 +91,7 @@ grep -qE '^initrd .*/the-token/1.1.1/initrd' "$entry"
 grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
 grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
 
-if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
+if test -x "$bootctl"; then
     echo "Testing bootctl"
     e2="${entry%+*}_2.conf"
     cp "$entry" "$e2"
@@ -97,14 +102,14 @@ if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
     # create file that is not referenced. Check if cleanup removes
     # it but leaves the rest alone
     :> "$BOOT_ROOT/the-token/1.1.2/initrd"
-    "$_KERNEL_INSTALL_BOOTCTL" --root="$D" cleanup
+    "$bootctl" --root="$D" cleanup
     test ! -e "$BOOT_ROOT/the-token/1.1.2/initrd"
     test -e "$BOOT_ROOT/the-token/1.1.2/linux"
     test -e "$BOOT_ROOT/the-token/1.1.1/linux"
     test -e "$BOOT_ROOT/the-token/1.1.1/initrd"
 
     # now remove duplicated entry and make sure files are left over
-    "$_KERNEL_INSTALL_BOOTCTL" --root="$D" unlink "${e2##*/}"
+    "$bootctl" --root="$D" unlink "${e2##*/}"
     test -e "$BOOT_ROOT/the-token/1.1.1/linux"
     test -e "$BOOT_ROOT/the-token/1.1.1/initrd"
     test -e "$entry"
@@ -112,7 +117,7 @@ if test -x "$_KERNEL_INSTALL_BOOTCTL"; then
     # remove last entry referencing those files
     entry_id="${entry##*/}"
     entry_id="${entry_id%+*}.conf"
-    "$_KERNEL_INSTALL_BOOTCTL" --root="$D" unlink "$entry_id"
+    "$bootctl" --root="$D" unlink "$entry_id"
     test ! -e "$entry"
     test ! -e "$BOOT_ROOT/the-token/1.1.1/linux"
     test ! -e "$BOOT_ROOT/the-token/1.1.1/initrd"
index 00a59291a1c43112835045e35d5ce61164787dbd..ecddab61e42381ca2aa6f0aa71436c313ce61f00 100644 (file)
@@ -192,16 +192,18 @@ int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *ret_dst,
                 if (cmsg->cmsg_level == SOL_IPV6 &&
                     cmsg->cmsg_type == IPV6_HOPLIMIT &&
                     cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
-                        int hops = *(int*) CMSG_DATA(cmsg);
+                        int hops = *CMSG_TYPED_DATA(cmsg, int);
 
                         if (hops != 255)
                                 return -EMULTIHOP;
                 }
 
                 if (cmsg->cmsg_level == SOL_SOCKET &&
-                    cmsg->cmsg_type == SO_TIMESTAMP &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
-                        triple_timestamp_from_realtime(&t, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
+                    cmsg->cmsg_type == SCM_TIMESTAMP &&
+                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) {
+                        struct timeval *tv = memcpy(&(struct timeval) {}, CMSG_DATA(cmsg), sizeof(struct timeval));
+                        triple_timestamp_from_realtime(&t, timeval_load(tv));
+                }
         }
 
         if (!triple_timestamp_is_set(&t))
index cffe7ba917dfdcdb0231d601c62d2a1368c5bb75..f207cf0c1e9175e91df0d358b3c6b5e070f10032 100644 (file)
@@ -1981,7 +1981,7 @@ static int client_receive_message_raw(
 
         cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata)));
         if (cmsg) {
-                struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg);
+                struct tpacket_auxdata *aux = CMSG_TYPED_DATA(cmsg, struct tpacket_auxdata);
                 checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
         }
 
index 5de230aaed783d2808fc43fab59c2318a353f251..55290ee0f114d83b7268b6b8c6a5e4d54edb8141 100644 (file)
@@ -407,7 +407,7 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
                    rather than binding the socket. This will be mostly useful
                    when we gain support for arbitrary number of server addresses
                  */
-                pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
+                pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
                 assert(pktinfo);
 
                 pktinfo->ipi_ifindex = server->ifindex;
@@ -1061,6 +1061,40 @@ static bool address_available(sd_dhcp_server *server, be32_t address) {
         return true;
 }
 
+static int server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req, DHCPLease **ret) {
+        DHCPLease *static_lease;
+        _cleanup_free_ uint8_t *data = NULL;
+
+        assert(server);
+        assert(req);
+        assert(ret);
+
+        static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+        if (static_lease) {
+                *ret = static_lease;
+                return 0;
+        }
+
+        /* when no lease is found based on the client id fall back to chaddr */
+        data = new(uint8_t, req->message->hlen + 1);
+        if (!data)
+                return -ENOMEM;
+
+        /* set client id type to 1: Ethernet Link-Layer (RFC 2132) */
+        data[0] = 0x01;
+        memcpy(data + 1, req->message->chaddr, req->message->hlen);
+
+        static_lease = hashmap_get(server->static_leases_by_client_id,
+                                   &(DHCPClientId) {
+                                           .length = req->message->hlen + 1,
+                                           .data = data,
+                                   });
+
+        *ret = static_lease;
+
+        return 0;
+}
+
 #define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
 
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length) {
@@ -1092,7 +1126,9 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
                 return r;
 
         existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
-        static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
+        r = server_get_static_lease(server, req, &static_lease);
+        if (r < 0)
+                return r;
 
         switch (type) {
 
@@ -1270,7 +1306,6 @@ static int server_receive_message(sd_event_source *s, int fd,
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
         };
-        struct cmsghdr *cmsg;
         ssize_t datagram_size, len;
         int r;
 
@@ -1306,19 +1341,10 @@ static int server_receive_message(sd_event_source *s, int fd,
         if ((size_t) len < sizeof(DHCPMessage))
                 return 0;
 
-        CMSG_FOREACH(cmsg, &msg)
-                if (cmsg->cmsg_level == IPPROTO_IP &&
-                    cmsg->cmsg_type == IP_PKTINFO &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
-                        struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
-
-                        /* TODO figure out if this can be done as a filter on
-                         * the socket, like for IPv6 */
-                        if (server->ifindex != info->ipi_ifindex)
-                                return 0;
-
-                        break;
-                }
+        /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
+        struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo);
+        if (info && info->ipi_ifindex != server->ifindex)
+                return 0;
 
         if (sd_dhcp_server_is_in_relay_mode(server)) {
                 r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen);
index 57dd91f81f3c45798c8053aef5fe893580cc1d98..6d62ba380b808638427ffbc0b92218692ebf1b49 100644 (file)
@@ -1276,7 +1276,6 @@ static int client_receive_message(
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
         };
-        struct cmsghdr *cmsg;
         triple_timestamp t = {};
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr *server_address = NULL;
@@ -1320,12 +1319,9 @@ static int client_receive_message(
                 server_address = &sa.in6.sin6_addr;
         }
 
-        CMSG_FOREACH(cmsg, &msg) {
-                if (cmsg->cmsg_level == SOL_SOCKET &&
-                    cmsg->cmsg_type == SO_TIMESTAMP &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
-                        triple_timestamp_from_realtime(&t, timeval_load(CMSG_TYPED_DATA(cmsg, struct timeval)));
-        }
+        struct timeval *tv = CMSG_FIND_AND_COPY_DATA(&msg, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
+        if (tv)
+                triple_timestamp_from_realtime(&t, timeval_load(tv));
 
         if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
                 return 0;
index 53db234a0c32ff4a547600b9e5bf8d63bf079b38..76fa1e0e32b03e07484bb182d74ea1b67cacfd12 100644 (file)
@@ -818,4 +818,5 @@ global:
         sd_session_get_username;
         sd_session_get_start_time;
         sd_uid_get_login_time;
+        sd_pid_notifyf_with_fds;
 } LIBSYSTEMD_253;
index 0eaedec87c77fe41afcc7f58de95c71d39dd4e4b..df26fd75cd19a453d09a49113a61ad5d06e27664 100644 (file)
@@ -32,6 +32,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER,         ESRCH),
         SD_BUS_ERROR_MAP(BUS_ERROR_NOT_REFERENCED,               EUNATCH),
         SD_BUS_ERROR_MAP(BUS_ERROR_DISK_FULL,                    ENOSPC),
+        SD_BUS_ERROR_MAP(BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED,
+                                                                 EHOSTDOWN),
 
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE,              ENXIO),
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE,                ENOENT),
index b6c2e93ea5b0580b3419fc7d4690a5b4f275b6ff..3a0eef49ef57b93d6c99a4037bd0b2d90c30b7db 100644 (file)
@@ -32,6 +32,8 @@
 #define BUS_ERROR_UNIT_BUSY                    "org.freedesktop.systemd1.UnitBusy"
 #define BUS_ERROR_UNIT_INACTIVE                "org.freedesktop.systemd1.UnitInactive"
 #define BUS_ERROR_FREEZE_CANCELLED             "org.freedesktop.systemd1.FreezeCancelled"
+#define BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED        \
+                                               "org.freedesktop.systemd1.FileDescriptorStoreDisabled"
 
 #define BUS_ERROR_NO_SUCH_MACHINE              "org.freedesktop.machine1.NoSuchMachine"
 #define BUS_ERROR_NO_SUCH_IMAGE                "org.freedesktop.machine1.NoSuchImage"
index d96b7256a1b162d173a60f223f43a50803be1c11..864acd73ac174b036f942539c228be7de9b40aab 100644 (file)
@@ -26,7 +26,7 @@ _public_ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(unique, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!bus->bus_client)
                 return -EINVAL;
@@ -89,7 +89,7 @@ _public_ int sd_bus_request_name(
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(name, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         r = validate_request_name_parameters(bus, name, flags, &param);
         if (r < 0)
@@ -193,7 +193,7 @@ _public_ int sd_bus_request_name_async(
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(name, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         r = validate_request_name_parameters(bus, name, flags, &param);
         if (r < 0)
@@ -247,7 +247,7 @@ _public_ int sd_bus_release_name(
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(name, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         r = validate_release_name_parameters(bus, name);
         if (r < 0)
@@ -340,7 +340,7 @@ _public_ int sd_bus_release_name_async(
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(name, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         r = validate_release_name_parameters(bus, name);
         if (r < 0)
@@ -367,7 +367,7 @@ _public_ int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatabl
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(acquired || activatable, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!bus->bus_client)
                 return -EINVAL;
@@ -438,7 +438,7 @@ _public_ int sd_bus_get_name_creds(
         assert_return(name, -EINVAL);
         assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
         assert_return(mask == 0 || creds, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(service_name_is_valid(name), -EINVAL);
 
         if (!bus->bus_client)
@@ -693,7 +693,7 @@ _public_ int sd_bus_get_name_creds(
                                                 "s",
                                                 unique ?: name);
                                 if (r < 0) {
-                                        if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"))
+                                        if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN))
                                                 return r;
 
                                         /* no data is fine */
@@ -732,7 +732,7 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP);
         assert_return(ret, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -903,7 +903,7 @@ _public_ int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(name, -EINVAL);
         assert_return(machine, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(service_name_is_valid(name), -EINVAL);
 
         if (!bus->bus_client)
index 6974e210a24a1d54c645b65364b26b47493e70db..81892c3254c1fc4799d397285c2f5614bbd388ea 100644 (file)
@@ -12,7 +12,7 @@
 _public_ int sd_bus_message_send(sd_bus_message *reply) {
         assert_return(reply, -EINVAL);
         assert_return(reply->bus, -EINVAL);
-        assert_return(!bus_pid_changed(reply->bus), -ECHILD);
+        assert_return(!bus_origin_changed(reply->bus), -ECHILD);
 
         return sd_bus_send(reply->bus, reply, NULL);
 }
@@ -30,7 +30,7 @@ _public_ int sd_bus_emit_signal_tov(
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -109,7 +109,7 @@ _public_ int sd_bus_call_method_asyncv(
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -163,7 +163,7 @@ _public_ int sd_bus_call_methodv(
 
         bus_assert_return(bus, -EINVAL, error);
         bus_assert_return(bus = bus_resolve(bus), -ENOPKG, error);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -217,7 +217,7 @@ _public_ int sd_bus_reply_method_returnv(
         assert_return(call->sealed, -EPERM);
         assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
 
         if (!BUS_IS_OPEN(call->bus->state))
                 return -ENOTCONN;
@@ -264,7 +264,7 @@ _public_ int sd_bus_reply_method_error(
         assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
         assert_return(sd_bus_error_is_set(e), -EINVAL);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
 
         if (!BUS_IS_OPEN(call->bus->state))
                 return -ENOTCONN;
@@ -291,7 +291,7 @@ _public_ int sd_bus_reply_method_errorfv(
         assert_return(call->sealed, -EPERM);
         assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
 
         if (!BUS_IS_OPEN(call->bus->state))
                 return -ENOTCONN;
@@ -331,7 +331,7 @@ _public_ int sd_bus_reply_method_errno(
         assert_return(call->sealed, -EPERM);
         assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
 
         if (!BUS_IS_OPEN(call->bus->state))
                 return -ENOTCONN;
@@ -359,7 +359,7 @@ _public_ int sd_bus_reply_method_errnofv(
         assert_return(call->sealed, -EPERM);
         assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
 
         if (!BUS_IS_OPEN(call->bus->state))
                 return -ENOTCONN;
@@ -407,7 +407,7 @@ _public_ int sd_bus_get_property(
         bus_assert_return(member_name_is_valid(member), -EINVAL, error);
         bus_assert_return(reply, -EINVAL, error);
         bus_assert_return(signature_is_single(type, false), -EINVAL, error);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -452,7 +452,7 @@ _public_ int sd_bus_get_property_trivial(
         bus_assert_return(member_name_is_valid(member), -EINVAL, error);
         bus_assert_return(bus_type_is_trivial(type), -EINVAL, error);
         bus_assert_return(ptr, -EINVAL, error);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -496,7 +496,7 @@ _public_ int sd_bus_get_property_string(
         bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
         bus_assert_return(member_name_is_valid(member), -EINVAL, error);
         bus_assert_return(ret, -EINVAL, error);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -545,7 +545,7 @@ _public_ int sd_bus_get_property_strv(
         bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
         bus_assert_return(member_name_is_valid(member), -EINVAL, error);
         bus_assert_return(ret, -EINVAL, error);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -587,7 +587,7 @@ _public_ int sd_bus_set_propertyv(
         bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error);
         bus_assert_return(member_name_is_valid(member), -EINVAL, error);
         bus_assert_return(signature_is_single(type, false), -EINVAL, error);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -646,7 +646,7 @@ _public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_b
         assert_return(call, -EINVAL);
         assert_return(call->sealed, -EPERM);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
         assert_return(ret, -EINVAL);
 
         if (!BUS_IS_OPEN(call->bus->state))
@@ -694,7 +694,7 @@ _public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability)
         assert_return(call, -EINVAL);
         assert_return(call->sealed, -EPERM);
         assert_return(call->bus, -EINVAL);
-        assert_return(!bus_pid_changed(call->bus), -ECHILD);
+        assert_return(!bus_origin_changed(call->bus), -ECHILD);
 
         if (!BUS_IS_OPEN(call->bus->state))
                 return -ENOTCONN;
@@ -786,7 +786,7 @@ _public_ int sd_bus_match_signal(
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(!sender || service_name_is_valid(sender), -EINVAL);
         assert_return(!path || object_path_is_valid(path), -EINVAL);
         assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
@@ -812,7 +812,7 @@ _public_ int sd_bus_match_signal_async(
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(!sender || service_name_is_valid(sender), -EINVAL);
         assert_return(!path || object_path_is_valid(path), -EINVAL);
         assert_return(!interface || interface_name_is_valid(interface), -EINVAL);
index 413e2dd43f34dc0c523687567d8d86a273a40515..7a2303350c2ea14640485df9c0f71323c4988c5e 100644 (file)
 #include "strv.h"
 
 BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = {
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed",                           EACCES),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory",                         ENOMEM),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown",                   EHOSTUNREACH),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner",                   ENXIO),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply",                          ETIMEDOUT),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError",                          EIO),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress",                       EADDRNOTAVAIL),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported",                     EOPNOTSUPP),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded",                   ENOBUFS),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied",                     EACCES),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed",                       EACCES),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer",                         EHOSTDOWN),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout",                          ETIMEDOUT),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork",                        ENONET),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse",                     EADDRINUSE),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected",                     ECONNRESET),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs",                      EINVAL),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound",                     ENOENT),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists",                       EEXIST),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod",                    EBADR),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject",                    EBADR),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface",                 EBADR),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty",                  EBADR),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly",                 EROFS),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown",             ESRCH),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature",                 EINVAL),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage",              EBADMSG),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut",                         ETIMEDOUT),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid",                 EINVAL),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent",               EINVAL),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound",                ENOENT),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown",    ESRCH),
-        SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse",                  EBUSY),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_FAILED,                             EACCES),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_MEMORY,                          ENOMEM),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_SERVICE_UNKNOWN,                    EHOSTUNREACH),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_NAME_HAS_NO_OWNER,                  ENXIO),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_REPLY,                           ETIMEDOUT),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_IO_ERROR,                           EIO),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_BAD_ADDRESS,                        EADDRNOTAVAIL),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_NOT_SUPPORTED,                      EOPNOTSUPP),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_LIMITS_EXCEEDED,                    ENOBUFS),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_ACCESS_DENIED,                      EACCES),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_AUTH_FAILED,                        EACCES),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_SERVER,                          EHOSTDOWN),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_TIMEOUT,                            ETIMEDOUT),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_NO_NETWORK,                         ENONET),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_ADDRESS_IN_USE,                     EADDRINUSE),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_DISCONNECTED,                       ECONNRESET),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_INVALID_ARGS,                       EINVAL),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_FILE_NOT_FOUND,                     ENOENT),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_FILE_EXISTS,                        EEXIST),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_METHOD,                     EBADR),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_OBJECT,                     EBADR),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_INTERFACE,                  EBADR),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNKNOWN_PROPERTY,                   EBADR),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_PROPERTY_READ_ONLY,                 EROFS),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,            ESRCH),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_INVALID_SIGNATURE,                  EINVAL),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_INCONSISTENT_MESSAGE,               EBADMSG),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_TIMED_OUT,                          ETIMEDOUT),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_MATCH_RULE_NOT_FOUND,               ENOENT),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_MATCH_RULE_INVALID,                 EINVAL),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, EACCES),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_INVALID_FILE_CONTENT,               EINVAL),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN,   ESRCH),
+        SD_BUS_ERROR_MAP(SD_BUS_ERROR_OBJECT_PATH_IN_USE,                 EBUSY),
         SD_BUS_ERROR_MAP_END
 };
 
index a9acdc7d0a014dd99cbd1034f5aa7957c275a634..aaef6ac3780080d57284cdd5493e8df78e618335 100644 (file)
@@ -286,7 +286,7 @@ struct sd_bus {
         struct memfd_cache memfd_cache[MEMFD_CACHE_MAX];
         unsigned n_memfd_cache;
 
-        pid_t original_pid;
+        uint64_t origin_id;
         pid_t busexec_pid;
 
         unsigned iteration_counter;
@@ -377,7 +377,7 @@ int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m);
 
 int bus_rqueue_make_room(sd_bus *bus);
 
-bool bus_pid_changed(sd_bus *bus);
+bool bus_origin_changed(sd_bus *bus);
 
 char *bus_address_escape(const char *v);
 
index 2ad7a9993d949ee09ca9bbc48482e7b2db3eacaf..80dc86d01a4c408c58c5b9433112570fd90d449e 100644 (file)
@@ -1610,7 +1610,7 @@ static int bus_add_object(
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
         assert_return(callback, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         n = bus_node_allocate(bus, path);
         if (!n)
@@ -1806,7 +1806,7 @@ static int add_object_vtable_internal(
         assert_return(vtable[0].x.start.element_size == VTABLE_ELEMENT_SIZE_221 ||
                       vtable[0].x.start.element_size >= VTABLE_ELEMENT_SIZE_242,
                       -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(!streq(interface, "org.freedesktop.DBus.Properties") &&
                       !streq(interface, "org.freedesktop.DBus.Introspectable") &&
                       !streq(interface, "org.freedesktop.DBus.Peer") &&
@@ -2028,7 +2028,7 @@ _public_ int sd_bus_add_node_enumerator(
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
         assert_return(callback, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         n = bus_node_allocate(bus, path);
         if (!n)
@@ -2280,7 +2280,7 @@ _public_ int sd_bus_emit_properties_changed_strv(
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
         assert_return(interface_name_is_valid(interface), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2334,7 +2334,7 @@ _public_ int sd_bus_emit_properties_changed(
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
         assert_return(interface_name_is_valid(interface), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2523,7 +2523,7 @@ _public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2703,7 +2703,7 @@ _public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2862,7 +2862,7 @@ _public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, ch
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2932,7 +2932,7 @@ _public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const c
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2950,7 +2950,7 @@ _public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path,
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2986,7 +2986,7 @@ _public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -3004,7 +3004,7 @@ _public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const ch
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(object_path_is_valid(path), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         n = bus_node_allocate(bus, path);
         if (!n)
index 33cfeebda7be3456c2355a1f3828cc5aaea3f189..ceda20f289e891b2508f6fa9e999a16f48562945 100644 (file)
@@ -604,7 +604,7 @@ static int bus_socket_read_auth(sd_bus *b) {
                                  * protocol? Somebody is playing games with
                                  * us. Close them all, and fail */
                                 j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
-                                close_many((int*) CMSG_DATA(cmsg), j);
+                                close_many(CMSG_TYPED_DATA(cmsg, int), j);
                                 return -EIO;
                         } else
                                 log_debug("Got unexpected auxiliary data with level=%d and type=%d",
@@ -1268,18 +1268,18 @@ int bus_socket_read_message(sd_bus *bus) {
                                          * isn't actually enabled? Close them,
                                          * and fail */
 
-                                        close_many((int*) CMSG_DATA(cmsg), n);
+                                        close_many(CMSG_TYPED_DATA(cmsg, int), n);
                                         return -EIO;
                                 }
 
                                 f = reallocarray(bus->fds, bus->n_fds + n, sizeof(int));
                                 if (!f) {
-                                        close_many((int*) CMSG_DATA(cmsg), n);
+                                        close_many(CMSG_TYPED_DATA(cmsg, int), n);
                                         return -ENOMEM;
                                 }
 
                                 for (i = 0; i < n; i++)
-                                        f[bus->n_fds++] = fd_move_above_stdio(((int*) CMSG_DATA(cmsg))[i]);
+                                        f[bus->n_fds++] = fd_move_above_stdio(CMSG_TYPED_DATA(cmsg, int)[i]);
                                 bus->fds = f;
                         } else
                                 log_debug("Got unexpected auxiliary data with level=%d and type=%d",
index e3f71ca302c5958754b38a9c26b5a7416bb67253..6e0b88e9d731c8813861a067bfec34baa224bc72 100644 (file)
@@ -38,6 +38,7 @@
 #include "memory-util.h"
 #include "missing_syscall.h"
 #include "missing_threads.h"
+#include "origin-id.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -232,6 +233,8 @@ static sd_bus* bus_free(sd_bus *b) {
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_bus*, bus_free);
 
+DEFINE_ORIGIN_ID_HELPERS(sd_bus, bus);
+
 _public_ int sd_bus_new(sd_bus **ret) {
         _cleanup_free_ sd_bus *b = NULL;
 
@@ -249,7 +252,7 @@ _public_ int sd_bus_new(sd_bus **ret) {
                 .message_version = 1,
                 .creds_mask = SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME,
                 .accept_fd = true,
-                .original_pid = getpid_cached(),
+                .origin_id = origin_id_query(),
                 .n_groups = SIZE_MAX,
                 .close_on_exit = true,
                 .ucred = UCRED_INVALID,
@@ -271,7 +274,7 @@ _public_ int sd_bus_set_address(sd_bus *bus, const char *address) {
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
         assert_return(address, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return free_and_strdup(&bus->address, address);
 }
@@ -282,7 +285,7 @@ _public_ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) {
         assert_return(bus->state == BUS_UNSET, -EPERM);
         assert_return(input_fd >= 0, -EBADF);
         assert_return(output_fd >= 0, -EBADF);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->input_fd = input_fd;
         bus->output_fd = output_fd;
@@ -298,7 +301,7 @@ _public_ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const *argv) {
         assert_return(bus->state == BUS_UNSET, -EPERM);
         assert_return(path, -EINVAL);
         assert_return(!strv_isempty(argv), -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         a = strv_copy(argv);
         if (!a)
@@ -316,7 +319,7 @@ _public_ int sd_bus_set_bus_client(sd_bus *bus, int b) {
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
         assert_return(!bus->patch_sender, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->bus_client = !!b;
         return 0;
@@ -326,7 +329,7 @@ _public_ int sd_bus_set_monitor(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->is_monitor = !!b;
         return 0;
@@ -336,7 +339,7 @@ _public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->accept_fd = !!b;
         return 0;
@@ -346,7 +349,7 @@ _public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         /* This is not actually supported by any of our transports these days, but we do honour it for synthetic
          * replies, and maybe one day classic D-Bus learns this too */
@@ -360,7 +363,7 @@ _public_ int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t mask) {
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL);
         assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         SET_FLAG(bus->creds_mask, mask, b);
 
@@ -375,7 +378,7 @@ _public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) {
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->is_server = !!b;
         bus->server_id = server_id;
@@ -386,7 +389,7 @@ _public_ int sd_bus_set_anonymous(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->anonymous_auth = !!b;
         return 0;
@@ -396,7 +399,7 @@ _public_ int sd_bus_set_trusted(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->trusted = !!b;
         return 0;
@@ -406,7 +409,7 @@ _public_ int sd_bus_set_description(sd_bus *bus, const char *description) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return free_and_strdup(&bus->description, description);
 }
@@ -414,7 +417,7 @@ _public_ int sd_bus_set_description(sd_bus *bus, const char *description) {
 _public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->allow_interactive_authorization = !!b;
         return 0;
@@ -423,7 +426,7 @@ _public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) {
 _public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->allow_interactive_authorization;
 }
@@ -432,7 +435,7 @@ _public_ int sd_bus_set_watch_bind(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->watch_bind = !!b;
         return 0;
@@ -441,7 +444,7 @@ _public_ int sd_bus_set_watch_bind(sd_bus *bus, int b) {
 _public_ int sd_bus_get_watch_bind(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->watch_bind;
 }
@@ -450,7 +453,7 @@ _public_ int sd_bus_set_connected_signal(sd_bus *bus, int b) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus->connected_signal = !!b;
         return 0;
@@ -459,7 +462,7 @@ _public_ int sd_bus_set_connected_signal(sd_bus *bus, int b) {
 _public_ int sd_bus_get_connected_signal(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->connected_signal;
 }
@@ -1197,7 +1200,7 @@ _public_ int sd_bus_start(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state == BUS_UNSET, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         bus_set_state(bus, BUS_OPENING);
 
@@ -1758,7 +1761,7 @@ _public_ void sd_bus_close(sd_bus *bus) {
                 return;
         if (bus->state == BUS_CLOSED)
                 return;
-        if (bus_pid_changed(bus))
+        if (bus_origin_changed(bus))
                 return;
 
         /* Don't leave ssh hanging around */
@@ -1779,6 +1782,8 @@ _public_ void sd_bus_close(sd_bus *bus) {
 _public_ sd_bus *sd_bus_close_unref(sd_bus *bus) {
         if (!bus)
                 return NULL;
+        if (bus_origin_changed(bus))
+                return NULL;
 
         sd_bus_close(bus);
 
@@ -1788,6 +1793,8 @@ _public_ sd_bus *sd_bus_close_unref(sd_bus *bus) {
 _public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) {
         if (!bus)
                 return NULL;
+        if (bus_origin_changed(bus))
+                return NULL;
 
         /* Have to do this before flush() to prevent hang */
         bus_kill_exec(bus);
@@ -1805,14 +1812,37 @@ void bus_enter_closing(sd_bus *bus) {
         bus_set_state(bus, BUS_CLOSING);
 }
 
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_bus, sd_bus, bus_free);
+/* Define manually so we can add the PID check */
+_public_ sd_bus *sd_bus_ref(sd_bus *bus) {
+        if (!bus)
+                return NULL;
+        if (bus_origin_changed(bus))
+                return NULL;
+
+        bus->n_ref++;
+
+        return bus;
+}
+
+_public_ sd_bus* sd_bus_unref(sd_bus *bus) {
+        if (!bus)
+                return NULL;
+        if (bus_origin_changed(bus))
+                return NULL;
+
+        assert(bus->n_ref > 0);
+        if (--bus->n_ref > 0)
+                return NULL;
+
+        return bus_free(bus);
+}
 
 _public_ int sd_bus_is_open(sd_bus *bus) {
         if (!bus)
                 return 0;
 
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return BUS_IS_OPEN(bus->state);
 }
@@ -1822,7 +1852,7 @@ _public_ int sd_bus_is_ready(sd_bus *bus) {
                 return 0;
 
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->state == BUS_RUNNING;
 }
@@ -1833,7 +1863,7 @@ _public_ int sd_bus_can_send(sd_bus *bus, char type) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->state != BUS_UNSET, -ENOTCONN);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->is_monitor)
                 return 0;
@@ -1858,7 +1888,7 @@ _public_ int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(id, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         r = bus_ensure_running(bus);
         if (r < 0)
@@ -2111,7 +2141,7 @@ _public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) {
                 assert_return(bus = bus_resolve(bus), -ENOPKG);
         else
                 assert_return(bus = m->bus, -ENOTCONN);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2196,7 +2226,7 @@ _public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destinat
                 assert_return(bus = bus_resolve(bus), -ENOPKG);
         else
                 assert_return(bus = m->bus, -ENOTCONN);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2264,7 +2294,7 @@ _public_ int sd_bus_call_async(
                 assert_return(bus = bus_resolve(bus), -ENOPKG);
         else
                 assert_return(bus = m->bus, -ENOTCONN);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
@@ -2372,7 +2402,7 @@ _public_ int sd_bus_call(
                 assert_return(bus = bus_resolve(bus), -ENOPKG);
         else
                 assert_return(bus = m->bus, -ENOTCONN);
-        bus_assert_return(!bus_pid_changed(bus), -ECHILD, error);
+        bus_assert_return(!bus_origin_changed(bus), -ECHILD, error);
 
         if (!BUS_IS_OPEN(bus->state)) {
                 r = -ENOTCONN;
@@ -2505,7 +2535,7 @@ _public_ int sd_bus_get_fd(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(bus->input_fd == bus->output_fd, -EPERM);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->state == BUS_CLOSED)
                 return -ENOTCONN;
@@ -2524,7 +2554,7 @@ _public_ int sd_bus_get_events(sd_bus *bus) {
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         switch (bus->state) {
 
@@ -2571,7 +2601,7 @@ _public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(timeout_usec, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING)
                 return -ENOTCONN;
@@ -2940,7 +2970,7 @@ static int process_fd_check(sd_bus *bus, sd_bus_message *m) {
 }
 
 static int process_message(sd_bus *bus, sd_bus_message *m) {
-        _unused_ _cleanup_(log_context_freep) LogContext *c = NULL;
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = NULL;
         int r;
 
         assert(bus);
@@ -2950,7 +2980,7 @@ static int process_message(sd_bus *bus, sd_bus_message *m) {
         bus->iteration_counter++;
 
         if (log_context_enabled())
-                c = log_context_new_consume(bus_message_make_log_fields(m));
+                c = log_context_new_strv_consume(bus_message_make_log_fields(m));
 
         log_debug_bus_message(m);
 
@@ -3210,7 +3240,7 @@ static int bus_process_internal(sd_bus *bus, sd_bus_message **ret) {
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         /* We don't allow recursively invoking sd_bus_process(). */
         assert_return(!bus->current_message, -EBUSY);
@@ -3344,7 +3374,7 @@ _public_ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) {
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->state == BUS_CLOSING)
                 return 0;
@@ -3367,7 +3397,7 @@ _public_ int sd_bus_flush(sd_bus *bus) {
 
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->state == BUS_CLOSING)
                 return 0;
@@ -3421,7 +3451,7 @@ _public_ int sd_bus_add_filter(
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(callback, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata);
         if (!s)
@@ -3512,23 +3542,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);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
+
+        CLEANUP_ARRAY(components, n_components, bus_match_parse_free);
 
         r = bus_match_parse(match, &components, &n_components);
         if (r < 0)
-                goto finish;
+                return r;
 
         s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata);
-        if (!s) {
-                r = -ENOMEM;
-                goto finish;
-        }
+        if (!s)
+                return -ENOMEM;
 
         s->match_callback.callback = callback;
         s->match_callback.install_callback = install_callback;
@@ -3544,10 +3574,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 +3585,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 +3594,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 +3603,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(
@@ -3609,15 +3633,6 @@ _public_ int sd_bus_add_match_async(
         return bus_add_match_full(bus, slot, true, match, callback, install_callback, userdata);
 }
 
-bool bus_pid_changed(sd_bus *bus) {
-        assert(bus);
-
-        /* We don't support people creating a bus connection and
-         * keeping it around over a fork(). Let's complain. */
-
-        return bus->original_pid != getpid_cached();
-}
-
 static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
         sd_bus *bus = ASSERT_PTR(userdata);
         int r;
@@ -3948,7 +3963,7 @@ _public_ int sd_bus_default(sd_bus **ret) {
 _public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) {
         assert_return(b, -EINVAL);
         assert_return(tid, -EINVAL);
-        assert_return(!bus_pid_changed(b), -ECHILD);
+        assert_return(!bus_origin_changed(b), -ECHILD);
 
         if (b->tid != 0) {
                 *tid = b->tid;
@@ -4174,7 +4189,7 @@ _public_ int sd_bus_path_decode_many(const char *path, const char *path_template
 _public_ int sd_bus_try_close(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return -EOPNOTSUPP;
 }
@@ -4184,7 +4199,7 @@ _public_ int sd_bus_get_description(sd_bus *bus, const char **description) {
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(description, -EINVAL);
         assert_return(bus->description, -ENXIO);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->description)
                 *description = bus->description;
@@ -4198,7 +4213,7 @@ _public_ int sd_bus_get_scope(sd_bus *bus, const char **scope) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(scope, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->runtime_scope < 0)
                 return -ENODATA;
@@ -4211,7 +4226,7 @@ _public_ int sd_bus_get_address(sd_bus *bus, const char **address) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(address, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (bus->address) {
                 *address = bus->address;
@@ -4225,7 +4240,7 @@ _public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(mask, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         *mask = bus->creds_mask;
         return 0;
@@ -4234,7 +4249,7 @@ _public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) {
 _public_ int sd_bus_is_bus_client(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->bus_client;
 }
@@ -4242,7 +4257,7 @@ _public_ int sd_bus_is_bus_client(sd_bus *bus) {
 _public_ int sd_bus_is_server(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->is_server;
 }
@@ -4250,7 +4265,7 @@ _public_ int sd_bus_is_server(sd_bus *bus) {
 _public_ int sd_bus_is_anonymous(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->anonymous_auth;
 }
@@ -4258,7 +4273,7 @@ _public_ int sd_bus_is_anonymous(sd_bus *bus) {
 _public_ int sd_bus_is_trusted(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->trusted;
 }
@@ -4266,7 +4281,7 @@ _public_ int sd_bus_is_trusted(sd_bus *bus) {
 _public_ int sd_bus_is_monitor(sd_bus *bus) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         return bus->is_monitor;
 }
@@ -4332,7 +4347,7 @@ _public_ int sd_bus_get_sender(sd_bus *bus, const char **ret) {
 _public_ int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(ret, -EINVAL);
 
         *ret = bus->rqueue_size;
@@ -4342,7 +4357,7 @@ _public_ int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) {
 _public_ int sd_bus_get_n_queued_write(sd_bus *bus, uint64_t *ret) {
         assert_return(bus, -EINVAL);
         assert_return(bus = bus_resolve(bus), -ENOPKG);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
         assert_return(ret, -EINVAL);
 
         *ret = bus->wqueue_size;
@@ -4404,7 +4419,7 @@ _public_ int sd_bus_enqueue_for_read(sd_bus *bus, sd_bus_message *m) {
         assert_return(bus = bus_resolve(bus), -ENOPKG);
         assert_return(m, -EINVAL);
         assert_return(m->sealed, -EINVAL);
-        assert_return(!bus_pid_changed(bus), -ECHILD);
+        assert_return(!bus_origin_changed(bus), -ECHILD);
 
         if (!BUS_IS_OPEN(bus->state))
                 return -ENOTCONN;
index f563db74e6d00ebe492fd2af951f205f5297f0cd..3e14627c750dbc92a259a9ff0b981c295dab0e91 100644 (file)
@@ -1,11 +1,13 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <stdio.h>
+#include <unistd.h>
 
 #include "sd-bus.h"
 
 #include "bus-internal.h"
 #include "bus-message.h"
+#include "process-util.h"
 #include "tests.h"
 
 static bool use_system_bus = false;
@@ -17,6 +19,30 @@ static void test_bus_new(void) {
         assert_se(bus->n_ref == 1);
 }
 
+static void test_bus_fork(void) {
+        _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        assert_se(sd_bus_new(&bus) == 0);
+        assert_se(bus->n_ref == 1);
+
+        /* Check that after a fork the cleanup functions return NULL */
+        r = safe_fork("(bus-fork-test)", FORK_WAIT|FORK_LOG, NULL);
+        if (r == 0) {
+                assert_se(bus);
+                assert_se(sd_bus_is_ready(bus) == -ECHILD);
+                assert_se(sd_bus_flush_close_unref(bus) == NULL);
+                assert_se(sd_bus_close_unref(bus) == NULL);
+                assert_se(sd_bus_unref(bus) == NULL);
+                sd_bus_close(bus);
+                assert_se(bus->n_ref == 1);
+                _exit(EXIT_SUCCESS);
+        }
+
+        assert_se(r >= 0);
+        assert_se(bus->n_ref == 1);
+}
+
 static int test_bus_open(void) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
@@ -67,6 +93,7 @@ int main(int argc, char **argv) {
         test_setup_logging(LOG_INFO);
 
         test_bus_new();
+        test_bus_fork();
 
         if (test_bus_open() < 0)
                 return log_tests_skipped("Failed to connect to bus");
index 8dc11aeb306afb1126070a10d44afca83ee29b53..eec6fb64efe1773e64a544c846d089c351d4ea0c 100644 (file)
@@ -567,7 +567,7 @@ _public_ int sd_pid_notify_with_fds(
                         cmsg->cmsg_type = SCM_CREDENTIALS;
                         cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
 
-                        ucred = (struct ucred*) CMSG_DATA(cmsg);
+                        ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
                         ucred->pid = pid != 0 ? pid : getpid_cached();
                         ucred->uid = getuid();
                         ucred->gid = getgid();
@@ -667,6 +667,36 @@ _public_ int sd_notifyf(int unset_environment, const char *format, ...) {
         return sd_pid_notify(0, unset_environment, p);
 }
 
+int sd_pid_notifyf_with_fds(
+                pid_t pid,
+                int unset_environment,
+                const int *fds, size_t n_fds,
+                const char *format, ...) {
+
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        /* Paranoia check: we traditionally used 'unsigned' as array size, but we nowadays more correctly use
+         * 'size_t'. sd_pid_notifyf_with_fds() and sd_pid_notify_with_fds() are from different eras, hence
+         * differ in this. Let's catch resulting incompatibilites early, even though they are pretty much
+         * theoretic only */
+        if (n_fds > UINT_MAX)
+                return -E2BIG;
+
+        if (format) {
+                va_list ap;
+
+                va_start(ap, format);
+                r = vasprintf(&p, format, ap);
+                va_end(ap);
+
+                if (r < 0 || !p)
+                        return -ENOMEM;
+        }
+
+        return sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds);
+}
+
 _public_ int sd_booted(void) {
         /* We test whether the runtime unit file directory has been
          * created. This takes place in mount-setup.c, so is
index 0bd2c89eb6e4e247d4e518427509a0f20f299ec2..f3f59e2ac3f3a8c7b927f49eca2e20b3f26ed041 100644 (file)
@@ -281,11 +281,10 @@ static int sound_device_compare(const char *devpath_a, const char *devpath_b) {
          * kernel makes this guarantee when creating those devices, and hence we should too when
          * enumerating them. */
 
-        sound_a = strstr(devpath_a, "/sound/card");
+        sound_a = strstrafter(devpath_a, "/sound/card");
         if (!sound_a)
                 return 0;
 
-        sound_a += STRLEN("/sound/card");
         sound_a = strchr(devpath_a, '/');
         if (!sound_a)
                 return 0;
index 0d2eea5f59104d31b34b9b92f1f33bdf89bdc0f7..a23b5e7847024c3298cb75f0586f2e778a722f8b 100644 (file)
@@ -242,14 +242,14 @@ _public_ int sd_device_monitor_stop(sd_device_monitor *m) {
 
 static int device_monitor_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
         _cleanup_(sd_device_unrefp) sd_device *device = NULL;
-        _unused_ _cleanup_(log_context_freep) LogContext *c = NULL;
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = NULL;
         sd_device_monitor *m = ASSERT_PTR(userdata);
 
         if (device_monitor_receive_device(m, &device) <= 0)
                 return 0;
 
         if (log_context_enabled())
-                c = log_context_new_consume(device_make_log_fields(device));
+                c = log_context_new_strv_consume(device_make_log_fields(device));
 
         if (m->callback)
                 return m->callback(m, device, m->userdata);
@@ -503,7 +503,6 @@ int device_monitor_receive_device(sd_device_monitor *m, sd_device **ret) {
                 .msg_name = &snl,
                 .msg_namelen = sizeof(snl),
         };
-        struct cmsghdr *cmsg;
         struct ucred *cred;
         size_t offset;
         ssize_t n;
@@ -559,12 +558,11 @@ int device_monitor_receive_device(sd_device_monitor *m, sd_device **ret) {
                                                  snl.nl.nl_pid);
         }
 
-        cmsg = CMSG_FIRSTHDR(&smsg);
-        if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS)
+        cred = CMSG_FIND_DATA(&smsg, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
+        if (!cred)
                 return log_monitor_errno(m, SYNTHETIC_ERRNO(EAGAIN),
                                          "No sender credentials received, ignoring message.");
 
-        cred = (struct ucred*) CMSG_DATA(cmsg);
         if (!check_sender_uid(m, cred->uid))
                 return log_monitor_errno(m, SYNTHETIC_ERRNO(EAGAIN),
                                          "Sender uid="UID_FMT", message ignored.", cred->uid);
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 47c5d98896313d1b986d27b37be41e2bda0c7e7e..529eff2fd1991e6bc7aa4ede7bb8afeda6141dd1 100644 (file)
@@ -15,7 +15,7 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) {
 
         assert(ret);
 
-        if (major(devnum) == 0 && minor(devnum) == 0)
+        if (devnum_is_zero(devnum))
                 return device_path_make_inaccessible(mode, ret);
 
         r = device_new_from_mode_and_devnum(&dev, mode, devnum);
index 8c65ee346984aa50189b40e313fb970d760df2cc..2c7b97ab4daa08fbd2c65ae160cac35397974029 100644 (file)
@@ -8,7 +8,7 @@
 #include "sd-device.h"
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "device-internal.h"
 #include "device-private.h"
 #include "device-util.h"
@@ -150,7 +150,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
                 /* The input path maybe a symlink located outside of /sys. Let's try to chase the symlink at first.
                  * The primary usecase is that e.g. /proc/device-tree is a symlink to /sys/firmware/devicetree/base.
                  * By chasing symlinks in the path at first, we can call sd_device_new_from_path() with such path. */
-                r = chase_symlinks(_syspath, NULL, 0, &syspath, &fd);
+                r = chase(_syspath, NULL, 0, &syspath, &fd);
                 if (r == -ENOENT)
                          /* the device does not exist (any more?) */
                         return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
@@ -163,7 +163,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
                         char *p;
 
                         /* /sys is a symlink to somewhere sysfs is mounted on? In that case, we convert the path to real sysfs to "/sys". */
-                        r = chase_symlinks("/sys", NULL, 0, &real_sys, NULL);
+                        r = chase("/sys", NULL, 0, &real_sys, NULL);
                         if (r < 0)
                                 return log_debug_errno(r, "sd-device: Failed to chase symlink /sys: %m");
 
@@ -301,7 +301,7 @@ int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum)
         if (major(devnum) == 0)
                 return -ENODEV;
 
-        if (asprintf(&syspath, "/sys/dev/%s/%u:%u", t, major(devnum), minor(devnum)) < 0)
+        if (asprintf(&syspath, "/sys/dev/%s/" DEVNUM_FORMAT_STR, t, DEVNUM_FORMAT_VAL(devnum)) < 0)
                 return -ENOMEM;
 
         r = sd_device_new_from_syspath(&dev, syspath);
@@ -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) {
@@ -1620,9 +1654,9 @@ int device_get_device_id(sd_device *device, const char **ret) {
 
                 if (sd_device_get_devnum(device, &devnum) >= 0) {
                         /* use dev_t — b259:131072, c254:0 */
-                        if (asprintf(&id, "%c%u:%u",
+                        if (asprintf(&id, "%c" DEVNUM_FORMAT_STR,
                                      streq(subsystem, "block") ? 'b' : 'c',
-                                     major(devnum), minor(devnum)) < 0)
+                                     DEVNUM_FORMAT_VAL(devnum)) < 0)
                                 return -ENOMEM;
                 } else if (sd_device_get_ifindex(device, &ifindex) >= 0) {
                         /* use netdev ifindex — n3 */
@@ -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 f11f35ef6e858677447a4382586b0b0da9a164de..a12d5e1a5a5ec0fe0d819d9cdb6de7a3cb38efdf 100644 (file)
@@ -25,6 +25,7 @@
 #include "missing_magic.h"
 #include "missing_syscall.h"
 #include "missing_threads.h"
+#include "origin-id.h"
 #include "path-util.h"
 #include "prioq.h"
 #include "process-util.h"
@@ -144,7 +145,7 @@ struct sd_event {
         /* A list of memory pressure event sources that still need their subscription string written */
         LIST_HEAD(sd_event_source, memory_pressure_write_list);
 
-        pid_t original_pid;
+        uint64_t origin_id;
 
         uint64_t iteration;
         triple_timestamp timestamp;
@@ -174,6 +175,8 @@ struct sd_event {
         unsigned delays[sizeof(usec_t) * 8];
 };
 
+DEFINE_PRIVATE_ORIGIN_ID_HELPERS(sd_event, event);
+
 static thread_local sd_event *default_event = NULL;
 
 static void source_disconnect(sd_event_source *s);
@@ -410,7 +413,7 @@ _public_ int sd_event_new(sd_event** ret) {
                 .boottime_alarm.fd = -EBADF,
                 .boottime_alarm.next = USEC_INFINITY,
                 .perturb = USEC_INFINITY,
-                .original_pid = getpid_cached(),
+                .origin_id = origin_id_query(),
         };
 
         r = prioq_ensure_allocated(&e->pending, pending_prioq_compare);
@@ -439,7 +442,31 @@ fail:
         return r;
 }
 
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
+/* Define manually so we can add the origin check */
+_public_ sd_event *sd_event_ref(sd_event *e) {
+        if (!e)
+                return NULL;
+        if (event_origin_changed(e))
+                return NULL;
+
+        e->n_ref++;
+
+        return e;
+}
+
+_public_ sd_event* sd_event_unref(sd_event *e) {
+        if (!e)
+                return NULL;
+        if (event_origin_changed(e))
+                return NULL;
+
+        assert(e->n_ref > 0);
+        if (--e->n_ref > 0)
+                return NULL;
+
+        return event_free(e);
+}
+
 #define PROTECT_EVENT(e)                                                \
         _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e);
 
@@ -449,20 +476,11 @@ _public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) {
         return sd_event_source_unref(s);
 }
 
-static bool event_pid_changed(sd_event *e) {
-        assert(e);
-
-        /* We don't support people creating an event loop and keeping
-         * it around over a fork(). Let's complain. */
-
-        return e->original_pid != getpid_cached();
-}
-
 static void source_io_unregister(sd_event_source *s) {
         assert(s);
         assert(s->type == SOURCE_IO);
 
-        if (event_pid_changed(s->event))
+        if (event_origin_changed(s->event))
                 return;
 
         if (!s->io.registered)
@@ -503,7 +521,7 @@ static void source_child_pidfd_unregister(sd_event_source *s) {
         assert(s);
         assert(s->type == SOURCE_CHILD);
 
-        if (event_pid_changed(s->event))
+        if (event_origin_changed(s->event))
                 return;
 
         if (!s->child.registered)
@@ -542,7 +560,7 @@ static void source_memory_pressure_unregister(sd_event_source *s) {
         assert(s);
         assert(s->type == SOURCE_MEMORY_PRESSURE);
 
-        if (event_pid_changed(s->event))
+        if (event_origin_changed(s->event))
                 return;
 
         if (!s->memory_pressure.registered)
@@ -694,7 +712,7 @@ static int event_make_signal_data(
 
         assert(e);
 
-        if (event_pid_changed(e))
+        if (event_origin_changed(e))
                 return -ECHILD;
 
         if (e->signal_sources && e->signal_sources[sig])
@@ -791,7 +809,7 @@ static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig
                 return;
         }
 
-        if (event_pid_changed(e))
+        if (event_origin_changed(e))
                 return;
 
         assert(d->fd >= 0);
@@ -954,7 +972,7 @@ static void source_disconnect(sd_event_source *s) {
                 break;
 
         case SOURCE_CHILD:
-                if (event_pid_changed(s->event))
+                if (event_origin_changed(s->event))
                         s->child.process_owned = false;
 
                 if (s->child.pid > 0) {
@@ -1231,7 +1249,7 @@ _public_ int sd_event_add_io(
         assert_return(fd >= 0, -EBADF);
         assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = io_exit_callback;
@@ -1378,7 +1396,7 @@ _public_ int sd_event_add_time(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(accuracy != UINT64_MAX, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */
                 return -EOPNOTSUPP;
@@ -1465,7 +1483,7 @@ _public_ int sd_event_add_signal(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         /* Let's make sure our special flag stays outside of the valid signal range */
         assert_cc(_NSIG < SD_EVENT_SIGNAL_PROCMASK);
@@ -1575,7 +1593,7 @@ _public_ int sd_event_add_child(
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = child_exit_callback;
@@ -1673,7 +1691,7 @@ _public_ int sd_event_add_child_pidfd(
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = child_exit_callback;
@@ -1754,7 +1772,7 @@ _public_ int sd_event_add_defer(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = generic_exit_callback;
@@ -1790,7 +1808,7 @@ _public_ int sd_event_add_post(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = generic_exit_callback;
@@ -1828,7 +1846,7 @@ _public_ int sd_event_add_exit(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         r = prioq_ensure_allocated(&e->exit, exit_prioq_compare);
         if (r < 0)
@@ -1928,7 +1946,7 @@ _public_ int sd_event_add_memory_pressure(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = memory_pressure_callback;
@@ -2126,7 +2144,7 @@ static void event_free_inotify_data(sd_event *e, struct inotify_data *d) {
         assert_se(hashmap_remove(e->inotify_data, &d->priority) == d);
 
         if (d->fd >= 0) {
-                if (!event_pid_changed(e) &&
+                if (!event_origin_changed(e) &&
                     epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0)
                         log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m");
 
@@ -2237,7 +2255,7 @@ static void event_free_inode_data(
         if (d->inotify_data) {
 
                 if (d->wd >= 0) {
-                        if (d->inotify_data->fd >= 0 && !event_pid_changed(e)) {
+                        if (d->inotify_data->fd >= 0 && !event_origin_changed(e)) {
                                 /* So here's a problem. At the time this runs the watch descriptor might already be
                                  * invalidated, because an IN_IGNORED event might be queued right the moment we enter
                                  * the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very
@@ -2444,7 +2462,7 @@ static int event_add_inotify_fd_internal(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(fd >= 0, -EBADF);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = inotify_exit_callback;
@@ -2577,7 +2595,7 @@ DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event_source, sd_event_source, event_sou
 
 _public_ int sd_event_source_set_description(sd_event_source *s, const char *description) {
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return free_and_strdup(&s->description, description);
 }
@@ -2585,7 +2603,7 @@ _public_ int sd_event_source_set_description(sd_event_source *s, const char *des
 _public_ int sd_event_source_get_description(sd_event_source *s, const char **description) {
         assert_return(s, -EINVAL);
         assert_return(description, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (!s->description)
                 return -ENXIO;
@@ -2596,6 +2614,7 @@ _public_ int sd_event_source_get_description(sd_event_source *s, const char **de
 
 _public_ sd_event *sd_event_source_get_event(sd_event_source *s) {
         assert_return(s, NULL);
+        assert_return(!event_origin_changed(s->event), NULL);
 
         return s->event;
 }
@@ -2604,7 +2623,7 @@ _public_ int sd_event_source_get_pending(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->pending;
 }
@@ -2612,7 +2631,7 @@ _public_ int sd_event_source_get_pending(sd_event_source *s) {
 _public_ int sd_event_source_get_io_fd(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->io.fd;
 }
@@ -2623,7 +2642,7 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
         assert_return(s, -EINVAL);
         assert_return(fd >= 0, -EBADF);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->io.fd == fd)
                 return 0;
@@ -2656,6 +2675,7 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
 _public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->io.owned;
 }
@@ -2663,6 +2683,7 @@ _public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
 _public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->io.owned = own;
         return 0;
@@ -2672,7 +2693,7 @@ _public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events)
         assert_return(s, -EINVAL);
         assert_return(events, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *events = s->io.events;
         return 0;
@@ -2685,7 +2706,7 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
         assert_return(s->type == SOURCE_IO, -EDOM);
         assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* edge-triggered updates are never skipped, so we can reset edges */
         if (s->io.events == events && !(events & EPOLLET))
@@ -2711,7 +2732,7 @@ _public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revent
         assert_return(revents, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
         assert_return(s->pending, -ENODATA);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *revents = s->io.revents;
         return 0;
@@ -2720,14 +2741,14 @@ _public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revent
 _public_ int sd_event_source_get_signal(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_SIGNAL, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->signal.sig;
 }
 
 _public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) {
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *priority = s->priority;
         return 0;
@@ -2741,7 +2762,7 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority)
 
         assert_return(s, -EINVAL);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->priority == priority)
                 return 0;
@@ -2841,7 +2862,7 @@ _public_ int sd_event_source_get_enabled(sd_event_source *s, int *ret) {
                 return false;
 
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (ret)
                 *ret = s->enabled;
@@ -3035,7 +3056,7 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
                 return 0;
 
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */
         if (s->event->state == SD_EVENT_FINISHED)
@@ -3067,7 +3088,7 @@ _public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) {
         assert_return(s, -EINVAL);
         assert_return(usec, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *usec = s->time.next;
         return 0;
@@ -3079,7 +3100,7 @@ _public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) {
         assert_return(s, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         r = source_set_pending(s, false);
         if (r < 0)
@@ -3097,6 +3118,7 @@ _public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec
 
         assert_return(s, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (usec == USEC_INFINITY)
                 return sd_event_source_set_time(s, USEC_INFINITY);
@@ -3116,7 +3138,7 @@ _public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *use
         assert_return(s, -EINVAL);
         assert_return(usec, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *usec = s->time.accuracy;
         return 0;
@@ -3129,7 +3151,7 @@ _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec
         assert_return(usec != UINT64_MAX, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         r = source_set_pending(s, false);
         if (r < 0)
@@ -3148,7 +3170,7 @@ _public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock
         assert_return(s, -EINVAL);
         assert_return(clock, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *clock = event_source_type_to_clock(s->type);
         return 0;
@@ -3158,7 +3180,7 @@ _public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
         assert_return(s, -EINVAL);
         assert_return(pid, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *pid = s->child.pid;
         return 0;
@@ -3167,7 +3189,7 @@ _public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
 _public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->child.pidfd < 0)
                 return -EOPNOTSUPP;
@@ -3178,7 +3200,7 @@ _public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
 _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
         assert_return(SIGNAL_VALID(sig), -EINVAL);
 
         /* If we already have seen indication the process exited refuse sending a signal early. This way we
@@ -3223,6 +3245,7 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons
 _public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->child.pidfd < 0)
                 return -EOPNOTSUPP;
@@ -3233,6 +3256,7 @@ _public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
 _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->child.pidfd < 0)
                 return -EOPNOTSUPP;
@@ -3244,6 +3268,7 @@ _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
 _public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->child.process_owned;
 }
@@ -3251,6 +3276,7 @@ _public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
 _public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->child.process_owned = own;
         return 0;
@@ -3260,7 +3286,7 @@ _public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask
         assert_return(s, -EINVAL);
         assert_return(mask, -EINVAL);
         assert_return(s->type == SOURCE_INOTIFY, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *mask = s->inotify.mask;
         return 0;
@@ -3272,7 +3298,7 @@ _public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->prepare == callback)
                 return 0;
@@ -3300,6 +3326,7 @@ _public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t
 
 _public_ void* sd_event_source_get_userdata(sd_event_source *s) {
         assert_return(s, NULL);
+        assert_return(!event_origin_changed(s->event), NULL);
 
         return s->userdata;
 }
@@ -3308,6 +3335,7 @@ _public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata)
         void *ret;
 
         assert_return(s, NULL);
+        assert_return(!event_origin_changed(s->event), NULL);
 
         ret = s->userdata;
         s->userdata = userdata;
@@ -4423,7 +4451,7 @@ _public_ int sd_event_prepare(sd_event *e) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
@@ -4662,7 +4690,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_ARMED, -EBUSY);
 
@@ -4765,7 +4793,7 @@ _public_ int sd_event_dispatch(sd_event *e) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_PENDING, -EBUSY);
 
@@ -4805,7 +4833,7 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
@@ -4853,9 +4881,10 @@ _public_ int sd_event_loop(sd_event *e) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
+
         PROTECT_EVENT(e);
 
         while (e->state != SD_EVENT_FINISHED) {
@@ -4870,7 +4899,7 @@ _public_ int sd_event_loop(sd_event *e) {
 _public_ int sd_event_get_fd(sd_event *e) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         return e->epoll_fd;
 }
@@ -4878,7 +4907,7 @@ _public_ int sd_event_get_fd(sd_event *e) {
 _public_ int sd_event_get_state(sd_event *e) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         return e->state;
 }
@@ -4887,7 +4916,7 @@ _public_ int sd_event_get_exit_code(sd_event *e, int *code) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(code, -EINVAL);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!e->exit_requested)
                 return -ENODATA;
@@ -4900,7 +4929,7 @@ _public_ int sd_event_exit(sd_event *e, int code) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         e->exit_requested = true;
         e->exit_code = code;
@@ -4912,7 +4941,7 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(usec, -EINVAL);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
                 return -EOPNOTSUPP;
@@ -4955,7 +4984,7 @@ _public_ int sd_event_get_tid(sd_event *e, pid_t *tid) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(tid, -EINVAL);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (e->tid != 0) {
                 *tid = e->tid;
@@ -4970,7 +4999,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (e->watchdog == !!b)
                 return e->watchdog;
@@ -5020,7 +5049,7 @@ fail:
 _public_ int sd_event_get_watchdog(sd_event *e) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         return e->watchdog;
 }
@@ -5028,7 +5057,7 @@ _public_ int sd_event_get_watchdog(sd_event *e) {
 _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         *ret = e->iteration;
         return 0;
@@ -5036,6 +5065,8 @@ _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
 
 _public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) {
         assert_return(s, -EINVAL);
+        assert_return(s->event, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->destroy_callback = callback;
         return 0;
@@ -5043,6 +5074,7 @@ _public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_d
 
 _public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (ret)
                 *ret = s->destroy_callback;
@@ -5052,12 +5084,14 @@ _public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_d
 
 _public_ int sd_event_source_get_floating(sd_event_source *s) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->floating;
 }
 
 _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->floating == !!b)
                 return 0;
@@ -5081,6 +5115,7 @@ _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
 _public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->exit_on_failure;
 }
@@ -5088,6 +5123,7 @@ _public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
 _public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->exit_on_failure == !!b)
                 return 0;
@@ -5100,6 +5136,7 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval
         int r;
 
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing
          * so is a programming error. */
@@ -5117,6 +5154,7 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval
 
 _public_ int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->ratelimit_expire_callback = callback;
         return 0;
@@ -5124,6 +5162,7 @@ _public_ int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, s
 
 _public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* Querying whether an event source has ratelimiting configured is not a loggable offense, hence
          * don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error. */
@@ -5143,6 +5182,7 @@ _public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_int
 
 _public_ int sd_event_source_is_ratelimited(sd_event_source *s) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
                 return false;
@@ -5212,6 +5252,7 @@ _public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM);
         assert_return(ty, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (!STR_IN_SET(ty, "some", "full"))
                 return -EINVAL;
@@ -5253,6 +5294,7 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint
 
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (threshold_usec <= 0 || threshold_usec >= UINT64_MAX)
                 return -ERANGE;
index 591eab8896e97a542e579c46f32a52636c3f7bb4..4f61af50f339ef046ad4eac9ee926f3bc97e047b 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <sys/wait.h>
+#include <unistd.h>
 
 #include "sd-event.h"
 
@@ -808,4 +809,23 @@ TEST(inotify_process_buffered_data) {
         assert_se(sd_event_wait(e, 0) == 0);
 }
 
+TEST(fork) {
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+        int r;
+
+        assert_se(sd_event_default(&e) >= 0);
+        assert_se(sd_event_prepare(e) == 0);
+
+        /* Check that after a fork the cleanup functions return NULL */
+        r = safe_fork("(bus-fork-test)", FORK_WAIT|FORK_LOG, NULL);
+        if (r == 0) {
+                assert_se(e);
+                assert_se(sd_event_ref(e) == NULL);
+                assert_se(sd_event_unref(e) == NULL);
+                _exit(EXIT_SUCCESS);
+        }
+
+        assert_se(r >= 0);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index a009a110a96fdfb58ba67443eb508a75fb59276b..76c9d1c051a3b4c273273b7b0dfcc18c874f4fdf 100644 (file)
@@ -5,6 +5,7 @@
 #include <unistd.h>
 
 #include "fd-util.h"
+#include "fs-util.h"
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
@@ -40,8 +41,9 @@ bool id128_is_valid(const char *s) {
         return false;
 }
 
-int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret) {
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) {
         char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
+        sd_id128_t id;
         ssize_t l;
         int r;
 
@@ -97,21 +99,34 @@ int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret) {
                 return -EUCLEAN;
         }
 
-        r = sd_id128_from_string(buffer, ret);
-        return r == -EINVAL ? -EUCLEAN : r;
+        r = sd_id128_from_string(buffer, &id);
+        if (r == -EINVAL)
+                return -EUCLEAN;
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+                return -ENOMEDIUM;
+
+        if (ret)
+                *ret = id;
+        return 0;
 }
 
-int id128_read(const char *p, Id128FormatFlag f, sd_id128_t *ret) {
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) {
         _cleanup_close_ int fd = -EBADF;
 
-        fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, 0);
         if (fd < 0)
-                return -errno;
+                return fd;
 
         return id128_read_fd(fd, f, ret);
 }
 
-int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id) {
         char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
         size_t sz;
         int r;
@@ -119,6 +134,9 @@ int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
         assert(fd >= 0);
         assert(IN_SET((f & ID128_FORMAT_ANY), ID128_FORMAT_PLAIN, ID128_FORMAT_UUID));
 
+        if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+                return -ENOMEDIUM;
+
         if (FLAGS_SET(f, ID128_FORMAT_PLAIN)) {
                 assert_se(sd_id128_to_string(id, buffer));
                 sz = SD_ID128_STRING_MAX;
@@ -141,12 +159,15 @@ int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id) {
         return 0;
 }
 
-int id128_write(const char *p, Id128FormatFlag f, sd_id128_t id) {
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) {
         _cleanup_close_ int fd = -EBADF;
 
-        fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444);
         if (fd < 0)
-                return -errno;
+                return fd;
 
         return id128_write_fd(fd, f, id);
 }
index e094de64419406c11a3b6226f3a8ee8ca0378741..7bcbd8e55840cec02bfed6f2c8f6ef3dfd11cccb 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
 #include <stdbool.h>
 
 #include "sd-id128.h"
 
 bool id128_is_valid(const char *s) _pure_;
 
-typedef enum Id128FormatFlag {
-        ID128_FORMAT_PLAIN = 1 << 0,  /* formatted as 32 hex chars as-is */
-        ID128_FORMAT_UUID  = 1 << 1,  /* formatted as 36 character uuid string */
-        ID128_FORMAT_ANY   = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
+typedef enum Id128Flag {
+        ID128_FORMAT_PLAIN  = 1 << 0,  /* formatted as 32 hex chars as-is */
+        ID128_FORMAT_UUID   = 1 << 1,  /* formatted as 36 character uuid string */
+        ID128_FORMAT_ANY    = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
 
         ID128_SYNC_ON_WRITE = 1 << 2, /* Sync the file after write. Used only when writing an ID. */
-} Id128FormatFlag;
-
-int id128_read_fd(int fd, Id128FormatFlag f, sd_id128_t *ret);
-int id128_read(const char *p, Id128FormatFlag f, sd_id128_t *ret);
-
-int id128_write_fd(int fd, Id128FormatFlag f, sd_id128_t id);
-int id128_write(const char *p, Id128FormatFlag f, sd_id128_t id);
+        ID128_REFUSE_NULL   = 1 << 3, /* Refuse all zero ID with -ENOMEDIUM. */
+} Id128Flag;
+
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret);
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret);
+static inline int id128_read(const char *path, Id128Flag f, sd_id128_t *ret) {
+        return id128_read_at(AT_FDCWD, path, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id);
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id);
+static inline int id128_write(const char *path, Id128Flag f, sd_id128_t id) {
+        return id128_write_at(AT_FDCWD, path, f, id);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret);
+int id128_get_machine_at(int rfd, sd_id128_t *ret);
 
 void id128_hash_func(const sd_id128_t *p, struct siphash *state);
 int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
index 5ce36cf2fc9c51d3586aa506bde5773d6921d212..6a82a7f7b890b8b1db308927a806ca0a17907e07 100644 (file)
@@ -7,6 +7,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "hexdecoct.h"
 #include "hmac.h"
@@ -15,6 +16,7 @@
 #include "macro.h"
 #include "missing_syscall.h"
 #include "missing_threads.h"
+#include "path-util.h"
 #include "random-util.h"
 #include "stat-util.h"
 #include "user-util.h"
@@ -126,12 +128,9 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) {
         int r;
 
         if (sd_id128_is_null(saved_machine_id)) {
-                r = id128_read("/etc/machine-id", ID128_FORMAT_PLAIN, &saved_machine_id);
+                r = id128_read("/etc/machine-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &saved_machine_id);
                 if (r < 0)
                         return r;
-
-                if (sd_id128_is_null(saved_machine_id))
-                        return -ENOMEDIUM;
         }
 
         if (ret)
@@ -139,19 +138,48 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) {
         return 0;
 }
 
+int id128_get_machine_at(int rfd, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        r = dir_fd_is_root_or_cwd(rfd);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return sd_id128_get_machine(ret);
+
+        fd = chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd < 0)
+                return fd;
+
+        return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        if (empty_or_root(root))
+                return sd_id128_get_machine(ret);
+
+        fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd < 0)
+                return fd;
+
+        return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
 _public_ int sd_id128_get_boot(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_boot_id = {};
         int r;
 
         if (sd_id128_is_null(saved_boot_id)) {
-                r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID, &saved_boot_id);
+                r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id);
                 if (r == -ENOENT && proc_mounted() == 0)
                         return -ENOSYS;
                 if (r < 0)
                         return r;
-
-                if (sd_id128_is_null(saved_boot_id))
-                        return -ENOMEDIUM;
         }
 
         if (ret)
index 8da4ca9cadcd3e0d08e600dc149d4c723732f355..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 60e448dac0c2c8021aa098ab7baff03542cf6a72..259aac847d6645488960b41901657d7c1ae26636 100644 (file)
@@ -87,7 +87,7 @@ struct sd_journal {
 
         Match *level0, *level1, *level2;
 
-        pid_t original_pid;
+        uint64_t origin_id;
 
         int inotify_fd;
         unsigned current_invalidate_counter, last_invalidate_counter;
index d62cf3eb3af020ac1b8ee6cadb90b87a66dd96ed..829edb31a70f51ea78a75f178ca0dd946c0e6d52 100644 (file)
@@ -20,7 +20,7 @@
 #include "time-util.h"
 #include "xattr-util.h"
 
-struct vacuum_info {
+typedef struct vacuum_info {
         uint64_t usage;
         char *filename;
 
@@ -29,9 +29,9 @@ struct vacuum_info {
         sd_id128_t seqnum_id;
         uint64_t seqnum;
         bool have_seqnum;
-};
+} vacuum_info;
 
-static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info *b) {
+static int vacuum_info_compare(const vacuum_info *a, const vacuum_info *b) {
         int r;
 
         if (a->have_seqnum && b->have_seqnum &&
@@ -48,6 +48,16 @@ static int vacuum_compare(const struct vacuum_info *a, const struct vacuum_info
         return strcmp(a->filename, b->filename);
 }
 
+static void vacuum_info_array_free(vacuum_info *list, size_t n) {
+        if (!list)
+                return;
+
+        FOREACH_ARRAY(i, list, n)
+                free(i->filename);
+
+        free(list);
+}
+
 static void patch_realtime(
                 int fd,
                 const char *fn,
@@ -126,10 +136,12 @@ int journal_directory_vacuum(
         uint64_t sum = 0, freed = 0, n_active_files = 0;
         size_t n_list = 0, i;
         _cleanup_closedir_ DIR *d = NULL;
-        struct vacuum_info *list = NULL;
+        vacuum_info *list = NULL;
         usec_t retention_limit = 0;
         int r;
 
+        CLEANUP_ARRAY(list, n_list, vacuum_info_array_free);
+
         assert(directory);
 
         if (max_use <= 0 && max_retention_usec <= 0 && n_max_files <= 0)
@@ -142,7 +154,7 @@ int journal_directory_vacuum(
         if (!d)
                 return -errno;
 
-        FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
+        FOREACH_DIRENT_ALL(de, d, return -errno) {
                 unsigned long long seqnum = 0, realtime;
                 _cleanup_free_ char *p = NULL;
                 sd_id128_t seqnum_id;
@@ -159,6 +171,8 @@ int journal_directory_vacuum(
                 if (!S_ISREG(st.st_mode))
                         continue;
 
+                size = 512UL * (uint64_t) st.st_blocks;
+
                 q = strlen(de->d_name);
 
                 if (endswith(de->d_name, ".journal")) {
@@ -168,6 +182,7 @@ int journal_directory_vacuum(
 
                         if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
@@ -175,23 +190,24 @@ int journal_directory_vacuum(
                             de->d_name[q-8-16-1-16-1] != '-' ||
                             de->d_name[q-8-16-1-16-1-32-1] != '@') {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
                         p = strdup(de->d_name);
-                        if (!p) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
+                        if (!p)
+                                return -ENOMEM;
 
                         de->d_name[q-8-16-1-16-1] = 0;
                         if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
                         if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
@@ -207,23 +223,24 @@ int journal_directory_vacuum(
 
                         if (q < 1 + 16 + 1 + 16 + 8 + 1) {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
                         if (de->d_name[q-1-8-16-1] != '-' ||
                             de->d_name[q-1-8-16-1-16-1] != '@') {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
                         p = strdup(de->d_name);
-                        if (!p) {
-                                r = -ENOMEM;
-                                goto finish;
-                        }
+                        if (!p)
+                                return -ENOMEM;
 
                         if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
                                 n_active_files++;
+                                sum += size;
                                 continue;
                         }
 
@@ -234,8 +251,6 @@ int journal_directory_vacuum(
                         continue;
                 }
 
-                size = 512UL * (uint64_t) st.st_blocks;
-
                 r = journal_file_empty(dirfd(d), p);
                 if (r < 0) {
                         log_debug_errno(r, "Failed check if %s is empty, ignoring: %m", p);
@@ -261,12 +276,10 @@ int journal_directory_vacuum(
 
                 patch_realtime(dirfd(d), p, &st, &realtime);
 
-                if (!GREEDY_REALLOC(list, n_list + 1)) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                if (!GREEDY_REALLOC(list, n_list + 1))
+                        return -ENOMEM;
 
-                list[n_list++] = (struct vacuum_info) {
+                list[n_list++] = (vacuum_info) {
                         .filename = TAKE_PTR(p),
                         .usage = size,
                         .seqnum = seqnum,
@@ -278,7 +291,7 @@ int journal_directory_vacuum(
                 sum += size;
         }
 
-        typesafe_qsort(list, n_list, vacuum_compare);
+        typesafe_qsort(list, n_list, vacuum_info_compare);
 
         for (i = 0; i < n_list; i++) {
                 uint64_t left;
@@ -310,15 +323,8 @@ int journal_directory_vacuum(
         if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
                 *oldest_usec = list[i].realtime;
 
-        r = 0;
-
-finish:
-        for (i = 0; i < n_list; i++)
-                free(list[i].filename);
-        free(list);
-
         log_full(verbose ? LOG_INFO : LOG_DEBUG, "Vacuuming done, freed %s of archived journals from %s.",
                  FORMAT_BYTES(freed), directory);
 
-        return r;
+        return 0;
 }
index 8964140f7ae8f3d26852a4d95d0fa29afcfc8dc6..b0194e875c9880c2ba4b10e508468be0724536b8 100644 (file)
@@ -33,6 +33,7 @@
 #include "list.h"
 #include "lookup3.h"
 #include "nulstr-util.h"
+#include "origin-id.h"
 #include "path-util.h"
 #include "prioq.h"
 #include "process-util.h"
 
 #define DEFAULT_DATA_THRESHOLD (64*1024)
 
+DEFINE_PRIVATE_ORIGIN_ID_HELPERS(sd_journal, journal);
+
 static void remove_file_real(sd_journal *j, JournalFile *f);
 static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f);
 static void journal_file_unlink_newest_by_bood_id(sd_journal *j, JournalFile *f);
 
-static bool journal_pid_changed(sd_journal *j) {
-        assert(j);
-
-        /* We don't support people creating a journal object and
-         * keeping it around over a fork(). Let's complain. */
-
-        return j->original_pid != getpid_cached();
-}
-
 static int journal_put_error(sd_journal *j, int r, const char *path) {
         _cleanup_free_ char *copy = NULL;
         int k;
@@ -234,7 +228,7 @@ _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size)
         uint64_t hash;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(data, -EINVAL);
 
         if (size == 0)
@@ -330,7 +324,7 @@ fail:
 
 _public_ int sd_journal_add_conjunction(sd_journal *j) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         if (!j->level0)
                 return 0;
@@ -349,7 +343,7 @@ _public_ int sd_journal_add_conjunction(sd_journal *j) {
 
 _public_ int sd_journal_add_disjunction(sd_journal *j) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         if (!j->level0)
                 return 0;
@@ -406,7 +400,7 @@ char *journal_make_match_string(sd_journal *j) {
 }
 
 _public_ void sd_journal_flush_matches(sd_journal *j) {
-        if (!j)
+        if (!j || journal_origin_changed(j))
                 return;
 
         if (j->level0)
@@ -930,7 +924,7 @@ static int real_journal_next(sd_journal *j, direction_t direction) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         r = iterated_cache_get(j->files_cache, NULL, &files, &n_files);
         if (r < 0)
@@ -988,7 +982,7 @@ static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t
         int c = 0, r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(skip <= INT_MAX, -ERANGE);
 
         if (skip == 0) {
@@ -1031,7 +1025,7 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(cursor, -EINVAL);
 
         if (!j->current_file || j->current_file->current_offset <= 0)
@@ -1064,7 +1058,7 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(!isempty(cursor), -EINVAL);
 
         for (const char *p = cursor;;) {
@@ -1160,7 +1154,7 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) {
         Object *o;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(!isempty(cursor), -EINVAL);
 
         if (!j->current_file || j->current_file->current_offset <= 0)
@@ -1239,7 +1233,7 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) {
 
 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         detach_location(j);
 
@@ -1255,7 +1249,7 @@ _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, u
 
 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         detach_location(j);
 
@@ -1270,7 +1264,7 @@ _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
 
 _public_ int sd_journal_seek_head(sd_journal *j) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         detach_location(j);
 
@@ -1283,7 +1277,7 @@ _public_ int sd_journal_seek_head(sd_journal *j) {
 
 _public_ int sd_journal_seek_tail(sd_journal *j) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         detach_location(j);
 
@@ -2001,7 +1995,7 @@ static sd_journal *journal_new(int flags, const char *path, const char *namespac
                 return NULL;
 
         *j = (sd_journal) {
-                .original_pid = getpid_cached(),
+                .origin_id = origin_id_query(),
                 .toplevel_fd = -EBADF,
                 .inotify_fd = -EBADF,
                 .flags = flags,
@@ -2254,7 +2248,7 @@ _public_ void sd_journal_close(sd_journal *j) {
         Directory *d;
         Prioq *p;
 
-        if (!j)
+        if (!j || journal_origin_changed(j))
                 return;
 
         while ((p = hashmap_first(j->newest_by_boot_id)))
@@ -2470,7 +2464,7 @@ _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         f = j->current_file;
         if (!f)
@@ -2498,7 +2492,7 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         f = j->current_file;
         if (!f)
@@ -2543,7 +2537,7 @@ _public_ int sd_journal_get_seqnum(
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         f = j->current_file;
         if (!f)
@@ -2597,7 +2591,7 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(field, -EINVAL);
         assert_return(data, -EINVAL);
         assert_return(size, -EINVAL);
@@ -2654,7 +2648,7 @@ _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(data, -EINVAL);
         assert_return(size, -EINVAL);
 
@@ -2715,7 +2709,7 @@ _public_ int sd_journal_enumerate_available_data(sd_journal *j, const void **dat
 }
 
 _public_ void sd_journal_restart_data(sd_journal *j) {
-        if (!j)
+        if (!j || journal_origin_changed(j))
                 return;
 
         j->current_field = 0;
@@ -2743,7 +2737,7 @@ _public_ int sd_journal_get_fd(sd_journal *j) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         if (j->no_inotify)
                 return -EMEDIUMTYPE;
@@ -2769,7 +2763,7 @@ _public_ int sd_journal_get_events(sd_journal *j) {
         int fd;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         fd = sd_journal_get_fd(j);
         if (fd < 0)
@@ -2782,7 +2776,7 @@ _public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) {
         int fd;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(timeout_usec, -EINVAL);
 
         fd = sd_journal_get_fd(j);
@@ -2905,7 +2899,7 @@ _public_ int sd_journal_process(sd_journal *j) {
         bool got_something = false;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         if (j->inotify_fd < 0) /* We have no inotify fd yet? Then there's noting to process. */
                 return 0;
@@ -2937,7 +2931,7 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
         uint64_t t;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         if (j->inotify_fd < 0) {
                 JournalFile *f;
@@ -2990,7 +2984,7 @@ _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from,
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(from || to, -EINVAL);
         assert_return(from != to, -EINVAL);
 
@@ -3035,7 +3029,7 @@ _public_ int sd_journal_get_cutoff_monotonic_usec(
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(ret_from != ret_to, -EINVAL);
 
         ORDERED_HASHMAP_FOREACH(f, j->files) {
@@ -3088,7 +3082,7 @@ _public_ int sd_journal_get_usage(sd_journal *j, uint64_t *ret) {
         uint64_t sum = 0;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(ret, -EINVAL);
 
         ORDERED_HASHMAP_FOREACH(f, j->files) {
@@ -3116,7 +3110,7 @@ _public_ int sd_journal_query_unique(sd_journal *j, const char *field) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(!isempty(field), -EINVAL);
         assert_return(field_is_valid(field), -EINVAL);
 
@@ -3139,7 +3133,7 @@ _public_ int sd_journal_enumerate_unique(
         size_t k;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(j->unique_field, -EINVAL);
 
         k = strlen(j->unique_field);
@@ -3272,7 +3266,7 @@ _public_ int sd_journal_enumerate_available_unique(sd_journal *j, const void **d
 }
 
 _public_ void sd_journal_restart_unique(sd_journal *j) {
-        if (!j)
+        if (!j || journal_origin_changed(j))
                 return;
 
         j->unique_file = NULL;
@@ -3284,7 +3278,7 @@ _public_ int sd_journal_enumerate_fields(sd_journal *j, const char **field) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(field, -EINVAL);
 
         if (!j->fields_file) {
@@ -3423,7 +3417,7 @@ _public_ int sd_journal_enumerate_fields(sd_journal *j, const char **field) {
 }
 
 _public_ void sd_journal_restart_fields(sd_journal *j) {
-        if (!j)
+        if (!j || journal_origin_changed(j))
                 return;
 
         j->fields_file = NULL;
@@ -3434,7 +3428,7 @@ _public_ void sd_journal_restart_fields(sd_journal *j) {
 
 _public_ int sd_journal_reliable_fd(sd_journal *j) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         return !j->on_network;
 }
@@ -3466,7 +3460,7 @@ _public_ int sd_journal_get_catalog(sd_journal *j, char **ret) {
         int r;
 
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(ret, -EINVAL);
 
         r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size);
@@ -3501,7 +3495,7 @@ _public_ int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **ret) {
 
 _public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
 
         j->data_threshold = sz;
         return 0;
@@ -3509,7 +3503,7 @@ _public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) {
 
 _public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) {
         assert_return(j, -EINVAL);
-        assert_return(!journal_pid_changed(j), -ECHILD);
+        assert_return(!journal_origin_changed(j), -ECHILD);
         assert_return(sz, -EINVAL);
 
         *sz = j->data_threshold;
index d5702db33009e7993717a9b2e65f6da7cbe18812..c8a19779f5f8b494b3a4d142a180b4303859be1c 100644 (file)
@@ -1,10 +1,14 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <unistd.h>
+
 #include "sd-journal.h"
 
 #include "chattr-util.h"
+#include "journal-internal.h"
 #include "log.h"
 #include "parse-util.h"
+#include "process-util.h"
 #include "rm-rf.h"
 #include "tests.h"
 
@@ -35,6 +39,21 @@ int main(int argc, char *argv[]) {
                 r = sd_journal_open_directory(&j, t, 0);
                 assert_se(r == 0);
 
+                assert_se(sd_journal_seek_head(j) == 0);
+                assert_se(j->current_location.type == LOCATION_HEAD);
+
+                r = safe_fork("(journal-fork-test)", FORK_WAIT|FORK_LOG, NULL);
+                if (r == 0) {
+                        assert_se(j);
+                        assert_se(sd_journal_get_realtime_usec(j, NULL) == -ECHILD);
+                        assert_se(sd_journal_seek_tail(j) == -ECHILD);
+                        assert_se(j->current_location.type == LOCATION_HEAD);
+                        sd_journal_close(j);
+                        _exit(EXIT_SUCCESS);
+                }
+
+                assert_se(r >= 0);
+
                 sd_journal_close(j);
 
                 j = NULL;
index d6b6593bea607d9e4738d352033c8cd36f06955b..17d96aea1d15734698f6e695ab283e6e9d5a33c6 100644 (file)
@@ -24,6 +24,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "tmpfile-util.h"
+#include "xkbcommon-util.h"
 
 static bool startswith_comma(const char *s, const char *prefix) {
         assert(s);
@@ -70,8 +71,7 @@ void x11_context_replace(X11Context *dest, X11Context *src) {
         assert(src);
 
         x11_context_clear(dest);
-        *dest = *src;
-        *src = (X11Context) {};
+        *dest = TAKE_STRUCT(*src);
 }
 
 bool x11_context_isempty(const X11Context *xc) {
@@ -154,6 +154,35 @@ int x11_context_copy(X11Context *dest, const X11Context *src) {
         return modified;
 }
 
+int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error) {
+        int r;
+
+        assert(xc);
+
+        if (!x11_context_is_safe(xc)) {
+                if (error)
+                        sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid X11 keyboard layout.");
+                return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL), "Invalid X11 keyboard layout.");
+        }
+
+        r = verify_xkb_rmlvo(xc->model, xc->layout, xc->variant, xc->options);
+        if (r == -EOPNOTSUPP) {
+                log_full_errno(MAX(log_level, LOG_NOTICE), r,
+                               "Cannot verify if new keymap is correct, libxkbcommon.so unavailable.");
+                return 0;
+        }
+        if (r < 0) {
+                if (error)
+                        sd_bus_error_set_errnof(error, r, "Specified keymap cannot be compiled, refusing as invalid.");
+                return log_full_errno(log_level, r,
+                                      "Cannot compile XKB keymap for x11 keyboard layout "
+                                      "(model='%s' / layout='%s' / variant='%s' / options='%s'): %m",
+                                      strempty(xc->model), strempty(xc->layout), strempty(xc->variant), strempty(xc->options));
+        }
+
+        return 0;
+}
+
 void vc_context_clear(VCContext *vc) {
         assert(vc);
 
@@ -166,8 +195,7 @@ void vc_context_replace(VCContext *dest, VCContext *src) {
         assert(src);
 
         vc_context_clear(dest);
-        *dest = *src;
-        *src = (VCContext) {};
+        *dest = TAKE_STRUCT(*src);
 }
 
 bool vc_context_isempty(const VCContext *vc) {
@@ -224,6 +252,46 @@ int vc_context_copy(VCContext *dest, const VCContext *src) {
         return modified;
 }
 
+static int verify_keymap(const char *keymap, int log_level, sd_bus_error *error) {
+        int r;
+
+        assert(keymap);
+
+        r = keymap_exists(keymap); /* This also verifies that the keymap name is kosher. */
+        if (r < 0) {
+                if (error)
+                        sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", keymap);
+                return log_full_errno(log_level, r, "Failed to check keymap %s: %m", keymap);
+        }
+        if (r == 0) {
+                if (error)
+                        sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", keymap);
+                return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOENT), "Keymap %s is not installed.", keymap);
+        }
+
+        return 0;
+}
+
+int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error) {
+        int r;
+
+        assert(vc);
+
+        if (vc->keymap) {
+                r = verify_keymap(vc->keymap, log_level, error);
+                if (r < 0)
+                        return r;
+        }
+
+        if (vc->toggle) {
+                r = verify_keymap(vc->toggle, log_level, error);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 void context_clear(Context *c) {
         assert(c);
 
@@ -269,6 +337,7 @@ int locale_read_data(Context *c, sd_bus_message *m) {
 int vconsole_read_data(Context *c, sd_bus_message *m) {
         _cleanup_close_ int fd = -EBADF;
         struct stat st;
+        int r;
 
         assert(c);
 
@@ -302,17 +371,28 @@ int vconsole_read_data(Context *c, sd_bus_message *m) {
         vc_context_clear(&c->vc);
         x11_context_clear(&c->x11_from_vc);
 
-        return parse_env_file_fd(fd, "/etc/vconsole.conf",
-                                 "KEYMAP",        &c->vc.keymap,
-                                 "KEYMAP_TOGGLE", &c->vc.toggle,
-                                 "XKBLAYOUT",     &c->x11_from_vc.layout,
-                                 "XKBMODEL",      &c->x11_from_vc.model,
-                                 "XKBVARIANT",    &c->x11_from_vc.variant,
-                                 "XKBOPTIONS",    &c->x11_from_vc.options);
+        r = parse_env_file_fd(
+                        fd, "/etc/vconsole.conf",
+                        "KEYMAP",        &c->vc.keymap,
+                        "KEYMAP_TOGGLE", &c->vc.toggle,
+                        "XKBLAYOUT",     &c->x11_from_vc.layout,
+                        "XKBMODEL",      &c->x11_from_vc.model,
+                        "XKBVARIANT",    &c->x11_from_vc.variant,
+                        "XKBOPTIONS",    &c->x11_from_vc.options);
+        if (r < 0)
+                return r;
+
+        if (vc_context_verify(&c->vc) < 0)
+                vc_context_clear(&c->vc);
+
+        if (x11_context_verify(&c->x11_from_vc) < 0)
+                x11_context_clear(&c->x11_from_vc);
+
+        return 0;
 }
 
 int x11_read_data(Context *c, sd_bus_message *m) {
-        _cleanup_close_ int fd = -EBADF, fd_ro = -EBADF;
+        _cleanup_close_ int fd = -EBADF;
         _cleanup_fclose_ FILE *f = NULL;
         bool in_section = false;
         struct stat st;
@@ -348,15 +428,9 @@ int x11_read_data(Context *c, sd_bus_message *m) {
         c->x11_stat = st;
         x11_context_clear(&c->x11_from_xorg);
 
-        fd_ro = fd_reopen(fd, O_CLOEXEC | O_RDONLY);
-        if (fd_ro < 0)
-                return fd_ro;
-
-        f = fdopen(fd_ro, "re");
-        if (!f)
-                return -errno;
-
-        TAKE_FD(fd_ro);
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
 
         for (;;) {
                 _cleanup_free_ char *line = NULL;
@@ -409,6 +483,9 @@ int x11_read_data(Context *c, sd_bus_message *m) {
                         in_section = false;
         }
 
+        if (x11_context_verify(&c->x11_from_xorg) < 0)
+                x11_context_clear(&c->x11_from_xorg);
+
         return 0;
 }
 
@@ -595,6 +672,7 @@ int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
 
         for (unsigned n = 0;;) {
                 _cleanup_strv_free_ char **a = NULL;
+                X11Context xc;
 
                 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
                 if (r < 0)
@@ -607,13 +685,17 @@ int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
                 if (!streq(vc->keymap, a[0]))
                         continue;
 
-                return x11_context_copy(ret,
-                                     &(X11Context) {
-                                             .layout  = empty_or_dash_to_null(a[1]),
-                                             .model   = empty_or_dash_to_null(a[2]),
-                                             .variant = empty_or_dash_to_null(a[3]),
-                                             .options = empty_or_dash_to_null(a[4]),
-                                     });
+                xc = (X11Context) {
+                        .layout  = empty_or_dash_to_null(a[1]),
+                        .model   = empty_or_dash_to_null(a[2]),
+                        .variant = empty_or_dash_to_null(a[3]),
+                        .options = empty_or_dash_to_null(a[4]),
+                };
+
+                if (x11_context_verify(&xc) < 0)
+                        continue;
+
+                return x11_context_copy(ret, &xc);
         }
 }
 
@@ -761,6 +843,30 @@ int find_legacy_keymap(const X11Context *xc, char **ret) {
         return !!*ret;
 }
 
+int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
+        _cleanup_free_ char *keymap = NULL;
+        int r;
+
+        assert(xc);
+        assert(ret);
+
+        if (isempty(xc->layout)) {
+                *ret = (VCContext) {};
+                return 0;
+        }
+
+        r = find_converted_keymap(xc, &keymap);
+        if (r == 0)
+                r = find_legacy_keymap(xc, &keymap);
+        if (r < 0)
+                return r;
+
+        *ret = (VCContext) {
+                .keymap = TAKE_PTR(keymap),
+        };
+        return 0;
+}
+
 int find_language_fallback(const char *lang, char **ret) {
         const char *map;
         _cleanup_fclose_ FILE *f = NULL;
@@ -790,30 +896,6 @@ int find_language_fallback(const char *lang, char **ret) {
         }
 }
 
-int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
-        _cleanup_free_ char *keymap = NULL;
-        int r;
-
-        assert(xc);
-        assert(ret);
-
-        if (isempty(xc->layout)) {
-                *ret = (VCContext) {};
-                return 0;
-        }
-
-        r = find_converted_keymap(xc, &keymap);
-        if (r == 0)
-                r = find_legacy_keymap(xc, &keymap);
-        if (r < 0)
-                return r;
-
-        *ret = (VCContext) {
-                .keymap = TAKE_PTR(keymap),
-        };
-        return 0;
-}
-
 bool locale_gen_check_available(void) {
 #if HAVE_LOCALEGEN
         if (access(LOCALEGEN_PATH, X_OK) < 0) {
@@ -864,6 +946,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)
@@ -871,8 +954,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;
         }
 }
@@ -950,14 +1033,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 83d253c18d9e55ee46cf46800fa64a94bbf094c1..0c68f2942ee58cdbae9ccfdc80c7698c723b5524 100644 (file)
@@ -43,6 +43,10 @@ void x11_context_empty_to_null(X11Context *xc);
 bool x11_context_is_safe(const X11Context *xc);
 bool x11_context_equal(const X11Context *a, const X11Context *b);
 int x11_context_copy(X11Context *dest, const X11Context *src);
+int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error);
+static inline int x11_context_verify(const X11Context *xc) {
+        return x11_context_verify_and_warn(xc, LOG_DEBUG, NULL);
+}
 
 X11Context *context_get_x11_context(Context *c);
 
@@ -52,6 +56,10 @@ bool vc_context_isempty(const VCContext *vc);
 void vc_context_empty_to_null(VCContext *vc);
 bool vc_context_equal(const VCContext *a, const VCContext *b);
 int vc_context_copy(VCContext *dest, const VCContext *src);
+int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error);
+static inline int vc_context_verify(const VCContext *vc) {
+        return vc_context_verify_and_warn(vc, LOG_DEBUG, NULL);
+}
 
 int find_converted_keymap(const X11Context *xc, char **ret);
 int find_legacy_keymap(const X11Context *xc, char **ret);
index ec5b78a19060df2b3e9b9554383744b59b709458..e08f8ac6efbe09bc2a96b8fe0f8af76925521179 100644 (file)
@@ -5,11 +5,6 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#if HAVE_XKBCOMMON
-#include <xkbcommon/xkbcommon.h>
-#include <dlfcn.h>
-#endif
-
 #include "sd-bus.h"
 
 #include "alloc-util.h"
@@ -19,7 +14,6 @@
 #include "bus-message.h"
 #include "bus-polkit.h"
 #include "constants.h"
-#include "dlfcn-util.h"
 #include "kbd-util.h"
 #include "localed-util.h"
 #include "macro.h"
@@ -378,15 +372,9 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro
 
         vc_context_empty_to_null(&in);
 
-        FOREACH_STRING(name, in.keymap ?: in.toggle, in.keymap ? in.toggle : NULL) {
-                r = keymap_exists(name); /* This also verifies that the keymap name is kosher. */
-                if (r < 0) {
-                        log_error_errno(r, "Failed to check keymap %s: %m", name);
-                        return sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", name);
-                }
-                if (r == 0)
-                        return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", name);
-        }
+        r = vc_context_verify_and_warn(&in, LOG_ERR, error);
+        if (r < 0)
+                return r;
 
         r = vconsole_read_data(c, m);
         if (r < 0) {
@@ -488,107 +476,6 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro
         return sd_bus_reply_method_return(m, NULL);
 }
 
-#if HAVE_XKBCOMMON
-
-_printf_(3, 0)
-static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
-        const char *fmt;
-
-        fmt = strjoina("libxkbcommon: ", format);
-        DISABLE_WARNING_FORMAT_NONLITERAL;
-        log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args);
-        REENABLE_WARNING;
-}
-
-#define LOAD_SYMBOL(symbol, dl, name)                                   \
-        ({                                                              \
-                (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
-                (symbol) ? 0 : -EOPNOTSUPP;                             \
-        })
-
-static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
-
-        /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
-         * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
-         * pointers to the shared library are below: */
-
-        struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL;
-        void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL;
-        void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL;
-        struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL;
-        void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL;
-
-        const struct xkb_rule_names rmlvo = {
-                .model          = model,
-                .layout         = layout,
-                .variant        = variant,
-                .options        = options,
-        };
-        struct xkb_context *ctx = NULL;
-        struct xkb_keymap *km = NULL;
-        _cleanup_(dlclosep) void *dl = NULL;
-        int r;
-
-        /* Compile keymap from RMLVO information to check out its validity */
-
-        dl = dlopen("libxkbcommon.so.0", RTLD_LAZY);
-        if (!dl)
-                return -EOPNOTSUPP;
-
-        r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new");
-        if (r < 0)
-                goto finish;
-
-        r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref");
-        if (r < 0)
-                goto finish;
-
-        r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn");
-        if (r < 0)
-                goto finish;
-
-        r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names");
-        if (r < 0)
-                goto finish;
-
-        r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref");
-        if (r < 0)
-                goto finish;
-
-        ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
-        if (!ctx) {
-                r = -ENOMEM;
-                goto finish;
-        }
-
-        symbol_xkb_context_set_log_fn(ctx, log_xkb);
-
-        km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
-        if (!km) {
-                r = -EINVAL;
-                goto finish;
-        }
-
-        r = 0;
-
-finish:
-        if (symbol_xkb_keymap_unref && km)
-                symbol_xkb_keymap_unref(km);
-
-        if (symbol_xkb_context_unref && ctx)
-                symbol_xkb_context_unref(ctx);
-
-        return r;
-}
-
-#else
-
-static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
-        return 0;
-}
-
-#endif
-
 static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
         _cleanup_(vc_context_clear) VCContext converted = {};
         Context *c = ASSERT_PTR(userdata);
@@ -603,18 +490,9 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err
 
         x11_context_empty_to_null(&in);
 
-        if (!x11_context_is_safe(&in))
-                return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keyboard data");
-
-        r = verify_xkb_rmlvo(in.model, in.layout, in.variant, in.options);
-        if (r == -EOPNOTSUPP)
-                log_notice_errno(r, "Cannot verify if new keymap is correct, libxkbcommon.so unavailable.");
-        else if (r < 0) {
-                log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
-                               strempty(in.model), strempty(in.layout), strempty(in.variant), strempty(in.options));
-                return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS,
-                                        "Specified keymap cannot be compiled, refusing as invalid.");
-        }
+        r = x11_context_verify_and_warn(&in, LOG_ERR, error);
+        if (r < 0)
+                return r;
 
         r = vconsole_read_data(c, m);
         if (r < 0) {
index 60d2666ffd6921efb42012d27c9865dbe53d7848..61001cc259c8e16c50c2c9500f4e7aeda362243b 100644 (file)
@@ -3,6 +3,7 @@
 systemd_localed_sources = files(
         'localed-util.c',
         'localed.c',
+        'xkbcommon-util.c',
 )
 
 localectl_sources = files('localectl.c')
@@ -28,11 +29,22 @@ if conf.get('ENABLE_LOCALED') == 1
                      install_dir : pkgdatadir)
 endif
 
+# logind will load libxkbcommon.so dynamically on its own, but we still need to
+# specify where the headers are.
+if conf.get('HAVE_XKBCOMMON') == 1
+        libxkbcommon_deps = [libdl,
+                             libxkbcommon.partial_dependency(compile_args: true)]
+else
+        libxkbcommon_deps = []
+endif
+
 tests += [
         {
                 'sources' : files(
                         'test-localed-util.c',
                         'localed-util.c',
+                        'xkbcommon-util.c',
                 ),
+                'dependencies' : libxkbcommon_deps,
         },
 ]
diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c
new file mode 100644 (file)
index 0000000..295ac8a
--- /dev/null
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dlfcn-util.h"
+#include "log.h"
+#include "macro.h"
+#include "string-util.h"
+#include "xkbcommon-util.h"
+
+#if HAVE_XKBCOMMON
+static void *xkbcommon_dl = NULL;
+
+struct xkb_context* (*sym_xkb_context_new)(enum xkb_context_flags flags);
+void (*sym_xkb_context_unref)(struct xkb_context *context);
+void (*sym_xkb_context_set_log_fn)(
+                struct xkb_context *context,
+                void (*log_fn)(
+                        struct xkb_context *context,
+                        enum xkb_log_level level,
+                        const char *format,
+                        va_list args));
+struct xkb_keymap* (*sym_xkb_keymap_new_from_names)(
+                struct xkb_context *context,
+                const struct xkb_rule_names *names,
+                enum xkb_keymap_compile_flags flags);
+void (*sym_xkb_keymap_unref)(struct xkb_keymap *keymap);
+
+static int dlopen_xkbcommon(void) {
+        return dlopen_many_sym_or_warn(
+                        &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG,
+                        DLSYM_ARG(xkb_context_new),
+                        DLSYM_ARG(xkb_context_unref),
+                        DLSYM_ARG(xkb_context_set_log_fn),
+                        DLSYM_ARG(xkb_keymap_new_from_names),
+                        DLSYM_ARG(xkb_keymap_unref));
+}
+
+_printf_(3, 0)
+static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
+        const char *fmt;
+
+        fmt = strjoina("libxkbcommon: ", format);
+        DISABLE_WARNING_FORMAT_NONLITERAL;
+        log_internalv(LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, fmt, args);
+        REENABLE_WARNING;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xkb_context *, sym_xkb_context_unref, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xkb_keymap *, sym_xkb_keymap_unref, NULL);
+
+int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
+        _cleanup_(sym_xkb_context_unrefp) struct xkb_context *ctx = NULL;
+        _cleanup_(sym_xkb_keymap_unrefp) struct xkb_keymap *km = NULL;
+        const struct xkb_rule_names rmlvo = {
+                .model          = model,
+                .layout         = layout,
+                .variant        = variant,
+                .options        = options,
+        };
+        int r;
+
+        /* Compile keymap from RMLVO information to check out its validity */
+
+        r = dlopen_xkbcommon();
+        if (r < 0)
+                return r;
+
+        ctx = sym_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
+        if (!ctx)
+                return -ENOMEM;
+
+        sym_xkb_context_set_log_fn(ctx, log_xkb);
+
+        km = sym_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
+        if (!km)
+                return -EINVAL;
+
+        return 0;
+}
+
+#endif
diff --git a/src/locale/xkbcommon-util.h b/src/locale/xkbcommon-util.h
new file mode 100644 (file)
index 0000000..e99c2d7
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#if HAVE_XKBCOMMON
+#include <xkbcommon/xkbcommon.h>
+
+extern struct xkb_context* (*sym_xkb_context_new)(enum xkb_context_flags flags);
+extern void (*sym_xkb_context_unref)(struct xkb_context *context);
+extern void (*sym_xkb_context_set_log_fn)(
+                struct xkb_context *context,
+                void (*log_fn)(
+                        struct xkb_context *context,
+                        enum xkb_log_level level,
+                        const char *format,
+                        va_list args));
+extern struct xkb_keymap* (*sym_xkb_keymap_new_from_names)(
+                struct xkb_context *context,
+                const struct xkb_rule_names *names,
+                enum xkb_keymap_compile_flags flags);
+extern void (*sym_xkb_keymap_unref)(struct xkb_keymap *keymap);
+
+int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options);
+
+#else
+
+static inline int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
+        return 0;
+}
+
+#endif
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 1c151021c7904f8a0f71e87bdd26241e379d100f..c5e0fda5e5d74ee3785ff2a57a9b4a93a84d9ac7 100644 (file)
@@ -867,7 +867,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
                 do {
                         id = mfree(id);
 
-                        if (asprintf(&id, "c%lu", ++m->session_counter) < 0)
+                        if (asprintf(&id, "c%" PRIu64, ++m->session_counter) < 0)
                                 return -ENOMEM;
 
                 } while (hashmap_contains(m->sessions, id));
@@ -3274,7 +3274,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
         do {
                 id = mfree(id);
 
-                if (asprintf(&id, "%lu", ++m->inhibit_counter) < 0)
+                if (asprintf(&id, "%" PRIu64, ++m->inhibit_counter) < 0)
                         return -ENOMEM;
 
         } while (hashmap_get(m->inhibitors, id));
index e3bebc9188be9d5e818149f51b78a5d6f39583de..00a0e8fd5896d63d127a19651ba652eba96f2e56 100644 (file)
@@ -23,6 +23,7 @@
 #include "path-util.h"
 #include "signal-util.h"
 #include "strv.h"
+#include "terminal-util.h"
 #include "user-util.h"
 
 static int property_get_user(
@@ -421,6 +422,41 @@ static int method_set_display(sd_bus_message *message, void *userdata, sd_bus_er
         return sd_bus_reply_method_return(message, NULL);
 }
 
+static int method_set_tty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Session *s = ASSERT_PTR(userdata);
+        int fd, r, flags;
+        _cleanup_free_ char *q = NULL;
+
+        assert(message);
+
+        r = sd_bus_message_read(message, "h", &fd);
+        if (r < 0)
+                return r;
+
+        if (!session_is_controller(s, sd_bus_message_get_sender(message)))
+                return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You must be in control of this session to set tty");
+
+        assert(fd >= 0);
+
+        flags = fcntl(fd, F_GETFL, 0);
+        if (flags < 0)
+                return -errno;
+        if ((flags & O_ACCMODE) != O_RDWR)
+                return -EACCES;
+        if (FLAGS_SET(flags, O_PATH))
+                return -ENOTTY;
+
+        r = getttyname_malloc(fd, &q);
+        if (r < 0)
+                return r;
+
+        r = session_set_tty(s, q);
+        if (r < 0)
+                return r;
+
+        return sd_bus_reply_method_return(message, NULL);
+}
+
 static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Session *s = ASSERT_PTR(userdata);
         uint32_t major, minor;
@@ -909,6 +945,11 @@ static const sd_bus_vtable session_vtable[] = {
                                 SD_BUS_NO_RESULT,
                                 method_set_display,
                                 SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("SetTTY",
+                                SD_BUS_ARGS("h", tty_fd),
+                                SD_BUS_NO_RESULT,
+                                method_set_tty,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD_WITH_ARGS("TakeDevice",
                                 SD_BUS_ARGS("u", major, "u", minor),
                                 SD_BUS_RESULT("h", fd, "b", inactive),
index 1daa102071c458c8cf1970d7dae8e162eee962dc..92d588b2764730d8a0318be3acc9eb403dccd8bf 100644 (file)
@@ -197,7 +197,7 @@ static void session_save_devices(Session *s, FILE *f) {
         if (!hashmap_isempty(s->devices)) {
                 fprintf(f, "DEVICES=");
                 HASHMAP_FOREACH(sd, s->devices)
-                        fprintf(f, "%u:%u ", major(sd->dev), minor(sd->dev));
+                        fprintf(f, DEVNUM_FORMAT_STR " ", DEVNUM_FORMAT_VAL(sd->dev));
                 fprintf(f, "\n");
         }
 }
@@ -1132,6 +1132,23 @@ int session_set_display(Session *s, const char *display) {
         return 1;
 }
 
+int session_set_tty(Session *s, const char *tty) {
+        int r;
+
+        assert(s);
+        assert(tty);
+
+        r = free_and_strdup(&s->tty, tty);
+        if (r <= 0)  /* 0 means the strings were equal */
+                return r;
+
+        session_save(s);
+
+        session_send_changed(s, "TTY", NULL);
+
+        return 1;
+}
+
 static int session_dispatch_fifo(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
         Session *s = ASSERT_PTR(userdata);
 
@@ -1349,6 +1366,9 @@ error:
 static void session_restore_vt(Session *s) {
         int r;
 
+        if (s->vtfd < 0)
+                return;
+
         r = vt_restore(s->vtfd);
         if (r == -EIO) {
                 int vt, old_fd;
index 4c286079866a78ef7ed1ceca3f500a69db946ad8..bf45301705ac13c435cae09e4ae56f55f7ad365b 100644 (file)
@@ -140,6 +140,7 @@ int session_get_locked_hint(Session *s);
 void session_set_locked_hint(Session *s, bool b);
 void session_set_type(Session *s, SessionType t);
 int session_set_display(Session *s, const char *display);
+int session_set_tty(Session *s, const char *tty);
 int session_create_fifo(Session *s);
 int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
 int session_stop(Session *s, bool force);
index d0b1f9671e0538071780d041931fc1b7875af415..e6a04e0834150006f9dde5eb0c0aa5e539559928 100644 (file)
@@ -59,8 +59,8 @@ struct Manager {
         char **kill_only_users, **kill_exclude_users;
         bool kill_user_processes;
 
-        unsigned long session_counter;
-        unsigned long inhibit_counter;
+        uint64_t session_counter;
+        uint64_t inhibit_counter;
 
         Hashmap *session_units;
         Hashmap *user_units;
index bf905e3b0f9771ae9e25e77b397f24cb859a1a30..8ba094bcff18ce8e505b740582464c27ebeb9c32 100644 (file)
                        send_interface="org.freedesktop.login1.Session"
                        send_member="SetDisplay"/>
 
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Session"
+                       send_member="SetTTY"/>
+
                 <allow receive_sender="org.freedesktop.login1"/>
         </policy>
 
index 65d09886bee0e101ec5daebf17b694c50772c9fb..3a009d8e37adfe53864c61053b2b899183a0d0d5 100644 (file)
@@ -948,7 +948,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
 
         /* Talk to logind over the message bus */
 
-        r = pam_acquire_bus_connection(handle, &bus);
+        r = pam_acquire_bus_connection(handle, "pam-systemd", &bus);
         if (r != PAM_SUCCESS)
                 return r;
 
@@ -1115,7 +1115,7 @@ success:
         /* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
          * not going to use the bus connection in that time, so let's better close before the daemon kicks us
          * off because we are not processing anything. */
-        (void) pam_release_bus_connection(handle);
+        (void) pam_release_bus_connection(handle, "pam-systemd");
         return PAM_SUCCESS;
 }
 
@@ -1159,7 +1159,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
                 /* Before we go and close the FIFO we need to tell logind that this is a clean session
                  * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
 
-                r = pam_acquire_bus_connection(handle, &bus);
+                r = pam_acquire_bus_connection(handle, "pam-systemd", &bus);
                 if (r != PAM_SUCCESS)
                         return r;
 
index 44d54811c3e75e78cc8e784608370a2e9d3c948f..0bfde422138ce42b24684d66edbeebf9b43dd76e 100644 (file)
@@ -6,10 +6,16 @@
  * ./test-session-properties /org/freedesktop/login1/session/_32
  */
 
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
 #include "alloc-util.h"
 #include "bus-common-errors.h"
 #include "bus-locator.h"
+#include "path-util.h"
 #include "string-util.h"
+#include "terminal-util.h"
 #include "tests.h"
 
 static BusLocator session;
@@ -94,6 +100,32 @@ TEST(set_display) {
         assert_se(isempty(display));
 }
 
+/* Tests org.freedesktop.logind.Session SetTTY */
+TEST(set_tty) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus* bus = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_free_ char *tty = NULL;
+        const char *path = "/dev/tty2"; /* testsuite uses tty2 */
+        int fd;
+
+        fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
+        assert_se(fd >= 0);
+
+        assert_se(sd_bus_open_system(&bus) >= 0);
+
+        /* tty can only be set by the session controller (which we're not ATM) */
+        assert_se(bus_call_method(bus, &session, "SetTTY", &error, NULL, "h", fd) < 0);
+        assert_se(sd_bus_error_has_name(&error, BUS_ERROR_NOT_IN_CONTROL));
+
+        assert_se(bus_call_method(bus, &session, "TakeControl", NULL, NULL, "b", true) >= 0);
+
+        /* tty can be set */
+        assert_se(bus_call_method(bus, &session, "SetTTY", NULL, NULL, "h", fd) >= 0);
+        tty = mfree(tty);
+        assert_se(bus_get_property_string(bus, &session, "TTY", NULL, &tty) >= 0);
+        assert_se(streq(tty, "tty2"));
+}
+
 static int intro(void) {
         if (saved_argc <= 1)
                 return EXIT_FAILURE;
index 5620bf9bafc750da7c1b8bd155548a846a94fac0..17012e20ac1f52e0a8ff65a626ea86bbf4390f19 100644 (file)
@@ -22,9 +22,11 @@ static char *arg_root = NULL;
 static char *arg_image = NULL;
 static bool arg_commit = false;
 static bool arg_print = false;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
@@ -36,12 +38,13 @@ static int help(void) {
 
         printf("%s [OPTIONS...]\n"
                "\n%sInitialize /etc/machine-id from a random source.%s\n\n"
-               "  -h --help             Show this help\n"
-               "     --version          Show package version\n"
-               "     --root=PATH        Operate relative to root path\n"
-               "     --image=PATH       Operate relative to image file\n"
-               "     --commit           Commit transient ID\n"
-               "     --print            Print used machine ID\n"
+               "  -h --help                 Show this help\n"
+               "     --version              Show package version\n"
+               "     --root=PATH            Operate on an alternate filesystem root\n"
+               "     --image=PATH           Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY  Specify disk image dissection policy\n"
+               "     --commit               Commit transient ID\n"
+               "     --print                Print used machine ID\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                ansi_highlight(),
@@ -57,17 +60,19 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_COMMIT,
                 ARG_PRINT,
         };
 
         static const struct option options[] = {
-                { "help",      no_argument,       NULL, 'h'           },
-                { "version",   no_argument,       NULL, ARG_VERSION   },
-                { "root",      required_argument, NULL, ARG_ROOT      },
-                { "image",     required_argument, NULL, ARG_IMAGE     },
-                { "commit",    no_argument,       NULL, ARG_COMMIT    },
-                { "print",     no_argument,       NULL, ARG_PRINT     },
+                { "help",         no_argument,       NULL, 'h'              },
+                { "version",      no_argument,       NULL, ARG_VERSION      },
+                { "root",         required_argument, NULL, ARG_ROOT         },
+                { "image",        required_argument, NULL, ARG_IMAGE        },
+                { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+                { "commit",       no_argument,       NULL, ARG_COMMIT       },
+                { "print",        no_argument,       NULL, ARG_PRINT        },
                 {}
         };
 
@@ -98,6 +103,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_COMMIT:
                         arg_commit = true;
                         break;
@@ -141,6 +152,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_VALIDATE_OS |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK |
@@ -158,14 +170,11 @@ static int run(int argc, char *argv[]) {
         }
 
         if (arg_commit) {
-                const char *etc_machine_id;
-
                 r = machine_id_commit(arg_root);
                 if (r < 0)
                         return r;
 
-                etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
-                r = id128_read(etc_machine_id, ID128_FORMAT_PLAIN, &id);
+                r = id128_get_machine(arg_root, &id);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read machine ID back: %m");
         } else {
index bf65eecfdd4232f19132efc045ebb4326207df92..336b42b7e5156b1f86a015d5539747cccf6797b0 100644 (file)
@@ -313,7 +313,7 @@ int bus_image_method_get_hostname(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image);
+                r = image_read_metadata(image, &image_policy_container);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -331,7 +331,7 @@ int bus_image_method_get_machine_id(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image);
+                r = image_read_metadata(image, &image_policy_container);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -359,7 +359,7 @@ int bus_image_method_get_machine_info(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image);
+                r = image_read_metadata(image, &image_policy_container);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -376,7 +376,7 @@ int bus_image_method_get_os_release(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image);
+                r = image_read_metadata(image, &image_policy_container);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
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 5cb1cd55263e4fee4cfa4077ae0852d6f547de29..5f5d84bb9718c1545d19f194ddc760e710e85415 100644 (file)
@@ -10,7 +10,7 @@
 #include "bus-locator.h"
 #include "bus-unit-util.h"
 #include "bus-wait-for-jobs.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "device-util.h"
 #include "dirent-util.h"
 #include "escape.h"
@@ -384,7 +384,7 @@ static int parse_argv(int argc, char *argv[]) {
                         if (!u)
                                 return log_oom();
 
-                        r = chase_symlinks(u, NULL, 0, &arg_mount_what, NULL);
+                        r = chase(u, NULL, 0, &arg_mount_what, NULL);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to make path %s absolute: %m", u);
                 } else {
@@ -401,7 +401,7 @@ static int parse_argv(int argc, char *argv[]) {
 
                 if (argc > optind+1) {
                         if (arg_transport == BUS_TRANSPORT_LOCAL) {
-                                r = chase_symlinks(argv[optind+1], NULL, CHASE_NONEXISTENT, &arg_mount_where, NULL);
+                                r = chase(argv[optind+1], NULL, CHASE_NONEXISTENT, &arg_mount_where, NULL);
                                 if (r < 0)
                                         return log_error_errno(r, "Failed to make path %s absolute: %m", argv[optind+1]);
                         } else {
@@ -1010,7 +1010,7 @@ static int action_umount(
                 if (!u)
                         return log_oom();
 
-                r = chase_symlinks(u, NULL, 0, &p, NULL);
+                r = chase(u, NULL, 0, &p, NULL);
                 if (r < 0) {
                         r2 = log_error_errno(r, "Failed to make path %s absolute: %m", argv[i]);
                         continue;
index 65015b132f0322f61c7082af26ffcd4407a9588d..9b21afe405b70e7cfa87173e675618ca4fcc913f 100644 (file)
@@ -80,10 +80,61 @@ static bool arg_full = false;
 static unsigned arg_lines = 10;
 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 
+static int check_netns_match(sd_bus *bus) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        struct stat st;
+        uint64_t id;
+        int r;
+
+        r = bus_get_property_trivial(bus, bus_network_mgr, "NamespaceId", &error, 't', &id);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r));
+                return 0;
+        }
+        if (id == 0) {
+                log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check.");
+                return 0;
+        }
+
+        if (stat("/proc/self/ns/net", &st) < 0)
+                return log_error_errno(errno, "Failed to determine our own network namespace ID: %m");
+
+        if (id != st.st_ino)
+                return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
+                                       "networkctl must be invoked in same network namespace as systemd-networkd.service.");
+
+        return 0;
+}
+
+static bool networkd_is_running(void) {
+        return access("/run/systemd/netif/state", F_OK) >= 0;
+}
+
+static int acquire_bus(sd_bus **ret) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        assert(ret);
+
+        r = sd_bus_open_system(&bus);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect system bus: %m");
+
+        r = check_netns_match(bus);
+        if (r < 0)
+                return r;
+
+        if (!networkd_is_running())
+                fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n");
+
+        *ret = TAKE_PTR(bus);
+        return 0;
+}
+
 static int get_description(sd_bus *bus, JsonVariant **ret) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        const char *text = NULL;
+        const char *text;
         int r;
 
         r = bus_call_method(bus, bus_network_mgr, "Describe", &error, &reply, NULL);
@@ -614,8 +665,8 @@ static int link_get_property(
                 const char *iface,
                 const char *propname) {
 
-        char ifindex_str[DECIMAL_STR_MAX(int)];
         _cleanup_free_ char *path = NULL;
+        char ifindex_str[DECIMAL_STR_MAX(int)];
         int r;
 
         xsprintf(ifindex_str, "%i", link->ifindex);
@@ -624,17 +675,7 @@ static int link_get_property(
         if (r < 0)
                 return r;
 
-        return sd_bus_call_method(
-                        bus,
-                        "org.freedesktop.network1",
-                        path,
-                        "org.freedesktop.DBus.Properties",
-                        "Get",
-                        error,
-                        reply,
-                        "ss",
-                        iface,
-                        propname);
+        return sd_bus_get_property(bus, "org.freedesktop.network1", path, iface, propname, error, reply, "ss");
 }
 
 static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
@@ -711,6 +752,7 @@ static void acquire_wlan_link_info(LinkInfo *link) {
 static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, LinkInfo **ret) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
         _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
+        _cleanup_free_ bool *matched_patterns = NULL;
         _cleanup_close_ int fd = -EBADF;
         size_t c = 0;
         int r;
@@ -730,7 +772,6 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
         if (r < 0)
                 return log_error_errno(r, "Failed to enumerate links: %m");
 
-        _cleanup_free_ bool *matched_patterns = NULL;
         if (patterns) {
                 matched_patterns = new0(bool, strv_length(patterns));
                 if (!matched_patterns)
@@ -774,8 +815,8 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
         typesafe_qsort(links, c, link_info_compare);
 
         if (bus)
-                for (size_t j = 0; j < c; j++)
-                        (void) acquire_link_bitrates(bus, links + j);
+                FOREACH_ARRAY(link, links, c)
+                        (void) acquire_link_bitrates(bus, link);
 
         *ret = TAKE_PTR(links);
 
@@ -786,13 +827,17 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
 }
 
 static int list_links(int argc, char *argv[], void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
         _cleanup_(table_unrefp) Table *table = NULL;
         TableCell *cell;
         int c, r;
 
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
         if (arg_json_format_flags != JSON_FORMAT_OFF) {
                 if (arg_all || argc <= 1)
                         return dump_manager_description(bus);
@@ -829,24 +874,24 @@ static int list_links(int argc, char *argv[], void *userdata) {
         assert_se(cell = table_get_cell(table, 0, 1));
         (void) table_set_ellipsize_percent(table, cell, 100);
 
-        for (int i = 0; i < c; i++) {
+        FOREACH_ARRAY(link, links, c) {
                 _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
-                const char *on_color_operational, *on_color_setup;
                 _cleanup_free_ char *t = NULL;
+                const char *on_color_operational, *on_color_setup;
 
-                (void) sd_network_link_get_operational_state(links[i].ifindex, &operational_state);
-                operational_state_to_color(links[i].name, operational_state, &on_color_operational, NULL);
+                (void) sd_network_link_get_operational_state(link->ifindex, &operational_state);
+                operational_state_to_color(link->name, operational_state, &on_color_operational, NULL);
 
-                (void) sd_network_link_get_setup_state(links[i].ifindex, &setup_state);
+                (void) sd_network_link_get_setup_state(link->ifindex, &setup_state);
                 setup_state_to_color(setup_state, &on_color_setup, NULL);
 
-                r = net_get_type_string(links[i].sd_device, links[i].iftype, &t);
+                r = net_get_type_string(link->sd_device, link->iftype, &t);
                 if (r == -ENOMEM)
                         return log_oom();
 
                 r = table_add_many(table,
-                                   TABLE_INT, links[i].ifindex,
-                                   TABLE_STRING, links[i].name,
+                                   TABLE_INT, link->ifindex,
+                                   TABLE_STRING, link->name,
                                    TABLE_STRING, t,
                                    TABLE_STRING, operational_state,
                                    TABLE_SET_COLOR, on_color_operational,
@@ -868,24 +913,21 @@ static int list_links(int argc, char *argv[], void *userdata) {
 
 /* IEEE Organizationally Unique Identifier vendor string */
 static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
+        _cleanup_free_ char *desc = NULL;
         const char *description;
-        char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1], *desc;
+        char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1];
         int r;
 
         assert(ret);
 
-        if (!hwdb)
-                return -EINVAL;
-
-        if (!mac)
+        if (!hwdb || !mac)
                 return -EINVAL;
 
         /* skip commonly misused 00:00:00 (Xerox) prefix */
         if (memcmp(mac, "\0\0\0", 3) == 0)
                 return -EINVAL;
 
-        xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR,
-                 ETHER_ADDR_FORMAT_VAL(*mac));
+        xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
 
         r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description);
         if (r < 0)
@@ -895,7 +937,7 @@ static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
         if (!desc)
                 return -ENOMEM;
 
-        *ret = desc;
+        *ret = TAKE_PTR(desc);
 
         return 0;
 }
@@ -906,7 +948,8 @@ static int get_gateway_description(
                 int ifindex,
                 int family,
                 union in_addr_union *gateway,
-                char **gateway_description) {
+                char **ret) {
+
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
         int r;
 
@@ -914,7 +957,7 @@ static int get_gateway_description(
         assert(ifindex >= 0);
         assert(IN_SET(family, AF_INET, AF_INET6));
         assert(gateway);
-        assert(gateway_description);
+        assert(ret);
 
         r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
         if (r < 0)
@@ -936,35 +979,37 @@ static int get_gateway_description(
 
                 r = sd_netlink_message_get_errno(m);
                 if (r < 0) {
-                        log_error_errno(r, "got error: %m");
+                        log_error_errno(r, "Failed to get netlink message, ignoring: %m");
                         continue;
                 }
 
                 r = sd_netlink_message_get_type(m, &type);
                 if (r < 0) {
-                        log_error_errno(r, "could not get type: %m");
+                        log_error_errno(r, "Failed to get netlink message type, ignoring: %m");
                         continue;
                 }
 
                 if (type != RTM_NEWNEIGH) {
-                        log_error("type is not RTM_NEWNEIGH");
+                        log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                        "Got unexpected netlink message type %u, ignoring",
+                                        type);
                         continue;
                 }
 
                 r = sd_rtnl_message_neigh_get_family(m, &fam);
                 if (r < 0) {
-                        log_error_errno(r, "could not get family: %m");
+                        log_error_errno(r, "Failed to get rtnl family, ignoring: %m");
                         continue;
                 }
 
                 if (fam != family) {
-                        log_error("family is not correct");
+                        log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got invalid rtnl family %d, ignoring", fam);
                         continue;
                 }
 
                 r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
                 if (r < 0) {
-                        log_error_errno(r, "could not get ifindex: %m");
+                        log_error_errno(r, "Failed to get rtnl ifindex, ignoring: %m");
                         continue;
                 }
 
@@ -972,18 +1017,21 @@ static int get_gateway_description(
                         continue;
 
                 switch (fam) {
+
                 case AF_INET:
                         r = sd_netlink_message_read_in_addr(m, NDA_DST, &gw.in);
                         if (r < 0)
                                 continue;
 
                         break;
+
                 case AF_INET6:
                         r = sd_netlink_message_read_in6_addr(m, NDA_DST, &gw.in6);
                         if (r < 0)
                                 continue;
 
                         break;
+
                 default:
                         continue;
                 }
@@ -995,7 +1043,7 @@ static int get_gateway_description(
                 if (r < 0)
                         continue;
 
-                r = ieee_oui(hwdb, &mac, gateway_description);
+                r = ieee_oui(hwdb, &mac, ret);
                 if (r < 0)
                         continue;
 
@@ -1021,37 +1069,33 @@ static int dump_list(Table *table, const char *prefix, char * const *l) {
         return 0;
 }
 
-static int dump_gateways(
-                sd_netlink *rtnl,
-                sd_hwdb *hwdb,
-                Table *table,
-                int ifindex) {
-        _cleanup_free_ struct local_address *local = NULL;
+static int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) {
+        _cleanup_free_ struct local_address *local_addrs = NULL;
         _cleanup_strv_free_ char **buf = NULL;
         int r, n;
 
         assert(rtnl);
         assert(table);
 
-        n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local);
+        n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local_addrs);
         if (n <= 0)
                 return n;
 
-        for (int i = 0; i < n; i++) {
+        FOREACH_ARRAY(local, local_addrs, n) {
                 _cleanup_free_ char *description = NULL;
 
-                r = get_gateway_description(rtnl, hwdb, local[i].ifindex, local[i].family, &local[i].address, &description);
+                r = get_gateway_description(rtnl, hwdb, local->ifindex, local->family, &local->address, &description);
                 if (r < 0)
                         log_debug_errno(r, "Could not get description of gateway, ignoring: %m");
 
                 /* Show interface name for the entry if we show entries for all interfaces */
                 r = strv_extendf(&buf, "%s%s%s%s%s%s",
-                                 IN_ADDR_TO_STRING(local[i].family, &local[i].address),
+                                 IN_ADDR_TO_STRING(local->family, &local->address),
                                  description ? " (" : "",
                                  strempty(description),
                                  description ? ")" : "",
                                  ifindex <= 0 ? " on " : "",
-                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local[i].ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
+                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
                 if (r < 0)
                         return log_oom();
         }
@@ -1065,7 +1109,7 @@ static int dump_addresses(
                 Table *table,
                 int ifindex) {
 
-        _cleanup_free_ struct local_address *local = NULL;
+        _cleanup_free_ struct local_address *local_addrs = NULL;
         _cleanup_strv_free_ char **buf = NULL;
         struct in_addr dhcp4_address = {};
         int r, n;
@@ -1073,27 +1117,27 @@ static int dump_addresses(
         assert(rtnl);
         assert(table);
 
-        n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local);
+        n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local_addrs);
         if (n <= 0)
                 return n;
 
         if (lease)
                 (void) sd_dhcp_lease_get_address(lease, &dhcp4_address);
 
-        for (int i = 0; i < n; i++) {
+        FOREACH_ARRAY(local, local_addrs, n) {
                 struct in_addr server_address;
                 bool dhcp4 = false;
 
-                if (local[i].family == AF_INET && in4_addr_equal(&local[i].address.in, &dhcp4_address))
+                if (local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address))
                         dhcp4 = sd_dhcp_lease_get_server_identifier(lease, &server_address) >= 0;
 
                 r = strv_extendf(&buf, "%s%s%s%s%s%s",
-                                 IN_ADDR_TO_STRING(local[i].family, &local[i].address),
+                                 IN_ADDR_TO_STRING(local->family, &local->address),
                                  dhcp4 ? " (DHCP4 via " : "",
                                  dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "",
                                  dhcp4 ? ")" : "",
                                  ifindex <= 0 ? " on " : "",
-                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local[i].ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
+                                 ifindex <= 0 ? FORMAT_IFNAME_FULL(local->ifindex, FORMAT_IFNAME_IFINDEX_WITH_PERCENT) : "");
                 if (r < 0)
                         return log_oom();
         }
@@ -1146,7 +1190,7 @@ static int dump_address_labels(sd_netlink *rtnl) {
 
                 r = sd_netlink_message_get_errno(m);
                 if (r < 0) {
-                        log_error_errno(r, "got error: %m");
+                        log_error_errno(r, "Failed to get netlink message, ignoring: %m");
                         continue;
                 }
 
@@ -1188,21 +1232,20 @@ static int list_address_labels(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        dump_address_labels(rtnl);
-
-        return 0;
+        return dump_address_labels(rtnl);
 }
 
 static int open_lldp_neighbors(int ifindex, FILE **ret) {
+        _cleanup_fclose_ FILE *f = NULL;
         char p[STRLEN("/run/systemd/netif/lldp/") + DECIMAL_STR_MAX(int)];
-        FILE *f;
 
         xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex);
+
         f = fopen(p, "re");
         if (!f)
                 return -errno;
 
-        *ret = f;
+        *ret = TAKE_PTR(f);
         return 0;
 }
 
@@ -2388,12 +2431,16 @@ static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) {
 }
 
 static int link_status(int argc, char *argv[], void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
         _cleanup_(link_info_array_freep) LinkInfo *links = NULL;
         int r, c;
 
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
         if (arg_json_format_flags != JSON_FORMAT_OFF) {
                 if (arg_all || argc <= 1)
                         return dump_manager_description(bus);
@@ -2516,14 +2563,14 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
         table_set_minimum_width(table, cell, 11);
         table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
 
-        for (int i = 0; i < c; i++) {
+        FOREACH_ARRAY(link, links, c) {
                 _cleanup_fclose_ FILE *f = NULL;
 
-                r = open_lldp_neighbors(links[i].ifindex, &f);
+                r = open_lldp_neighbors(link->ifindex, &f);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0) {
-                        log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", links[i].ifindex);
+                        log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", link->ifindex);
                         continue;
                 }
 
@@ -2552,7 +2599,7 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
                         }
 
                         r = table_add_many(table,
-                                           TABLE_STRING, links[i].name,
+                                           TABLE_STRING, link->name,
                                            TABLE_STRING, chassis_id,
                                            TABLE_STRING, system_name,
                                            TABLE_STRING, capabilities,
@@ -2701,10 +2748,14 @@ static int link_renew_one(sd_bus *bus, int index, const char *name) {
 }
 
 static int link_renew(int argc, char *argv[], void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int index, k = 0, r;
 
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
         for (int i = 1; i < argc; i++) {
                 index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
                 if (index < 0)
@@ -2731,10 +2782,14 @@ static int link_force_renew_one(sd_bus *bus, int index, const char *name) {
 }
 
 static int link_force_renew(int argc, char *argv[], void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int k = 0, r;
 
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
         for (int i = 1; i < argc; i++) {
                 int index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]);
                 if (index < 0)
@@ -2749,10 +2804,14 @@ static int link_force_renew(int argc, char *argv[], void *userdata) {
 }
 
 static int verb_reload(int argc, char *argv[], void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         int r;
 
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
         r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to reload network settings: %s", bus_error_message(&error, r));
@@ -2761,13 +2820,17 @@ static int verb_reload(int argc, char *argv[], void *userdata) {
 }
 
 static int verb_reconfigure(int argc, char *argv[], void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         _cleanup_set_free_ Set *indexes = NULL;
         int index, r;
         void *p;
 
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
         indexes = set_new(NULL);
         if (!indexes)
                 return log_oom();
@@ -2916,7 +2979,7 @@ static int parse_argv(int argc, char *argv[]) {
         return 1;
 }
 
-static int networkctl_main(sd_bus *bus, int argc, char *argv[]) {
+static int networkctl_main(int argc, char *argv[]) {
         static const Verb verbs[] = {
                 { "list",        VERB_ANY, VERB_ANY, VERB_DEFAULT, list_links          },
                 { "status",      VERB_ANY, VERB_ANY, 0,            link_status         },
@@ -2932,53 +2995,10 @@ static int networkctl_main(sd_bus *bus, int argc, char *argv[]) {
                 {}
         };
 
-        return dispatch_verb(argc, argv, verbs, bus);
-}
-
-static int check_netns_match(sd_bus *bus) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        struct stat st;
-        uint64_t id;
-        int r;
-
-        r = sd_bus_get_property_trivial(
-                        bus,
-                        "org.freedesktop.network1",
-                        "/org/freedesktop/network1",
-                        "org.freedesktop.network1.Manager",
-                        "NamespaceId",
-                        &error,
-                        't',
-                        &id);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r));
-                return 0;
-        }
-        if (id == 0) {
-                log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check.");
-                return 0;
-        }
-
-        if (stat("/proc/self/ns/net", &st) < 0)
-                return log_error_errno(r, "Failed to determine our own network namespace ID: %m");
-
-        if (id != st.st_ino)
-                return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
-                                       "networkctl must be invoked in same network namespace as systemd-networkd.service.");
-
-        return 0;
-}
-
-static void warn_networkd_missing(void) {
-
-        if (access("/run/systemd/netif/state", F_OK) >= 0)
-                return;
-
-        fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n");
+        return dispatch_verb(argc, argv, verbs, NULL);
 }
 
 static int run(int argc, char* argv[]) {
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
 
         log_setup();
@@ -2987,17 +3007,7 @@ static int run(int argc, char* argv[]) {
         if (r <= 0)
                 return r;
 
-        r = sd_bus_open_system(&bus);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect system bus: %m");
-
-        r = check_netns_match(bus);
-        if (r < 0)
-                return r;
-
-        warn_networkd_missing();
-
-        return networkctl_main(bus, argc, argv);
+        return networkctl_main(argc, argv);
 }
 
 DEFINE_MAIN_FUNCTION(run);
index e1e89e8357527752c6af8f63d6cc5cf9732a122b..0c4032633fb760c9381ab9294c9bcca0f87439a1 100644 (file)
@@ -4,6 +4,7 @@
 #include "ether-addr-util.h"
 #include "networkd-manager.h"
 #include "networkd-network-bus.h"
+#include "path-util.h"
 #include "string-util.h"
 #include "strv.h"
 
@@ -54,8 +55,8 @@ static const sd_bus_vtable network_vtable[] = {
 };
 
 static char *network_bus_path(Network *network) {
-        _cleanup_free_ char *name = NULL;
-        char *networkname, *d, *path;
+        _cleanup_free_ char *name = NULL, *networkname= NULL;
+        char *d, *path;
         int r;
 
         assert(network);
@@ -65,7 +66,9 @@ static char *network_bus_path(Network *network) {
         if (!name)
                 return NULL;
 
-        networkname = basename(name);
+        r = path_extract_filename(name, &networkname);
+        if (r < 0)
+                return NULL;
 
         d = strrchr(networkname, '.');
         if (!d)
index d82766702ac180e410a214861a1b796bddfa8ece..0820e0db2de3b00d660f3cd9d2b7bd9bc52fda89 100644 (file)
@@ -894,7 +894,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
                         return 0;
                 }
 
-                assert((uintptr_t) group % __alignof__(struct nexthop_grp) == 0);
+                assert((uintptr_t) group % alignof(struct nexthop_grp) == 0);
 
                 n_group = raw_group_size / sizeof(struct nexthop_grp);
                 for (size_t i = 0; i < n_group; i++) {
index 7320ffebdeffe7a39e6e596f913f585810c7bdbf..8489b8387309e36f9d043eaa1d448384f0095798 100644 (file)
@@ -11,6 +11,8 @@
 #include "alloc-util.h"
 #include "build.h"
 #include "env-util.h"
+#include "fd-util.h"
+#include "fdset.h"
 #include "format-util.h"
 #include "log.h"
 #include "main-func.h"
@@ -33,9 +35,13 @@ static gid_t arg_gid = GID_INVALID;
 static bool arg_no_block = false;
 static char **arg_env = NULL;
 static char **arg_exec = NULL;
+static FDSet *arg_fds = NULL;
+static char *arg_fdname = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep);
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
@@ -60,6 +66,8 @@ static int help(void) {
                "     --booted          Check if the system was booted up with systemd\n"
                "     --no-block        Do not wait until operation finished\n"
                "     --exec            Execute command line separated by ';' once done\n"
+               "     --fd=FD           Pass specified file descriptor with along with message\n"
+               "     --fdname=NAME     Name to assign to passed file descriptor(s)\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                program_invocation_short_name,
@@ -103,6 +111,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UID,
                 ARG_NO_BLOCK,
                 ARG_EXEC,
+                ARG_FD,
+                ARG_FDNAME,
         };
 
         static const struct option options[] = {
@@ -117,9 +127,12 @@ static int parse_argv(int argc, char *argv[]) {
                 { "uid",       required_argument, NULL, ARG_UID       },
                 { "no-block",  no_argument,       NULL, ARG_NO_BLOCK  },
                 { "exec",      no_argument,       NULL, ARG_EXEC      },
+                { "fd",        required_argument, NULL, ARG_FD        },
+                { "fdname",    required_argument, NULL, ARG_FDNAME    },
                 {}
         };
 
+        _cleanup_(fdset_freep) FDSet *passed = NULL;
         bool do_exec = false;
         int c, r, n_env;
 
@@ -198,6 +211,60 @@ static int parse_argv(int argc, char *argv[]) {
                         do_exec = true;
                         break;
 
+                case ARG_FD: {
+                        _cleanup_close_ int owned_fd = -EBADF;
+                        int fdnr;
+
+                        r = safe_atoi(optarg, &fdnr);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse file descriptor: %s", optarg);
+                        if (fdnr < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "File descriptor can't be negative: %i", fdnr);
+
+                        if (!passed) {
+                                /* Take possession of all passed fds */
+                                r = fdset_new_fill(&passed);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to take possession of passed file descriptors: %m");
+
+                                r = fdset_cloexec(passed, true);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to enable O_CLOEXEC for passed file descriptors: %m");
+                        }
+
+                        if (fdnr < 3) {
+                                /* For stdin/stdout/stderr we want to keep the fd, too, hence make a copy */
+                                owned_fd = fcntl(fdnr, F_DUPFD_CLOEXEC, 3);
+                                if (owned_fd < 0)
+                                        return log_error_errno(errno, "Failed to duplicate file descriptor: %m");
+                        } else {
+                                /* Otherwise, move the fd over */
+                                owned_fd = fdset_remove(passed, fdnr);
+                                if (owned_fd < 0)
+                                        return log_error_errno(owned_fd, "Specified file descriptor '%i' not passed or specified more than once: %m", fdnr);
+                        }
+
+                        if (!arg_fds) {
+                                arg_fds = fdset_new();
+                                if (!arg_fds)
+                                        return log_oom();
+                        }
+
+                        r = fdset_consume(arg_fds, TAKE_FD(owned_fd));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add file descriptor to set: %m");
+                        break;
+                }
+
+                case ARG_FDNAME:
+                        if (!fdname_is_valid(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg);
+
+                        if (free_and_strdup(&arg_fdname, optarg) < 0)
+                                return log_oom();
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -212,11 +279,15 @@ static int parse_argv(int argc, char *argv[]) {
             !arg_reloading &&
             !arg_status &&
             !arg_pid &&
-            !arg_booted) {
+            !arg_booted &&
+            fdset_isempty(arg_fds)) {
                 help();
                 return -EINVAL;
         }
 
+        if (arg_fdname && fdset_isempty(arg_fds))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
+
         if (do_exec) {
                 int i;
 
@@ -243,13 +314,16 @@ static int parse_argv(int argc, char *argv[]) {
                         return log_oom();
         }
 
+        if (!fdset_isempty(passed))
+                log_warning("Warning: %u more file descriptors passed than referenced with --fd=.", fdset_size(passed));
+
         return 1;
 }
 
 static int run(int argc, char* argv[]) {
-        _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL;
+        _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL, *fdn = NULL;
         _cleanup_strv_free_ char **final_env = NULL;
-        char* our_env[7];
+        char* our_env[9];
         size_t i = 0;
         pid_t source_pid;
         int r;
@@ -302,6 +376,18 @@ static int run(int argc, char* argv[]) {
                 our_env[i++] = cpid;
         }
 
+        if (!fdset_isempty(arg_fds)) {
+                our_env[i++] = (char*) "FDSTORE=1";
+
+                if (arg_fdname) {
+                        fdn = strjoin("FDNAME=", arg_fdname);
+                        if (!fdn)
+                                return log_oom();
+
+                        our_env[i++] = fdn;
+                }
+        }
+
         our_env[i++] = NULL;
 
         final_env = strv_env_merge(our_env, arg_env);
@@ -338,13 +424,28 @@ static int run(int argc, char* argv[]) {
                                                   * or the service manager itself */
                         source_pid = 0;
         }
-        r = sd_pid_notify(source_pid, false, n);
+
+        if (fdset_isempty(arg_fds))
+                r = sd_pid_notify(source_pid, /* unset_environment= */ false, n);
+        else {
+                _cleanup_free_ int *a = NULL;
+                int k;
+
+                k = fdset_to_array(arg_fds, &a);
+                if (k < 0)
+                        return log_error_errno(k, "Failed to convert file descriptor set to array: %m");
+
+                r = sd_pid_notify_with_fds(source_pid, /* unset_environment= */ false, n, a, k);
+
+        }
         if (r < 0)
                 return log_error_errno(r, "Failed to notify init system: %m");
         if (r == 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                        "No status data could be sent: $NOTIFY_SOCKET was not set");
 
+        arg_fds = fdset_free(arg_fds); /* Close before we execute anything */
+
         if (!arg_no_block) {
                 r = sd_notify_barrier(0, 5 * USEC_PER_SEC);
                 if (r < 0)
index 2e3c3c3072990e41460883b08b77d274daf366e4..0a8653033d0689c2968a9f192f3d72733d9a8a23 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
@@ -21,7 +21,7 @@ static int check_etc_passwd_collisions(
         assert(directory);
         assert(name || uid_is_valid(uid));
 
-        r = chase_symlinks_and_fopen_unlocked("/etc/passwd", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
+        r = chase_and_fopen_unlocked("/etc/passwd", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
         if (r == -ENOENT)
                 return 0; /* no user database? then no user, hence no collision */
         if (r < 0)
@@ -54,7 +54,7 @@ static int check_etc_group_collisions(
         assert(directory);
         assert(name || gid_is_valid(gid));
 
-        r = chase_symlinks_and_fopen_unlocked("/etc/group", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
+        r = chase_and_fopen_unlocked("/etc/group", directory, CHASE_PREFIX_ROOT, "re", NULL, &f);
         if (r == -ENOENT)
                 return 0; /* no group database? then no group, hence no collision */
         if (r < 0)
index a93b8c38c933e926b338cd27682c7a73a362ccb0..9e1210f876ef72f6d9e5ac5163479b2ca93de652 100644 (file)
@@ -72,9 +72,9 @@ Files.PrivateUsersChown,      config_parse_userns_chown,   0,
 Files.PrivateUsersOwnership,  config_parse_userns_ownership, 0,                      offsetof(Settings, userns_ownership)
 Files.BindUser,               config_parse_bind_user,      0,                        offsetof(Settings, bind_user)
 Network.Private,              config_parse_tristate,       0,                        offsetof(Settings, private_network)
-Network.Interface,            config_parse_strv,           0,                        offsetof(Settings, network_interfaces)
-Network.MACVLAN,              config_parse_strv,           0,                        offsetof(Settings, network_macvlan)
-Network.IPVLAN,               config_parse_strv,           0,                        offsetof(Settings, network_ipvlan)
+Network.Interface,            config_parse_network_iface_pair, 0,                    offsetof(Settings, network_interfaces)
+Network.MACVLAN,              config_parse_macvlan_iface_pair, 0,                    offsetof(Settings, network_macvlan)
+Network.IPVLAN,               config_parse_ipvlan_iface_pair, 0,                     offsetof(Settings, network_ipvlan)
 Network.VirtualEthernet,      config_parse_tristate,       0,                        offsetof(Settings, network_veth)
 Network.VirtualEthernetExtra, config_parse_veth_extra,     0,                        0
 Network.Bridge,               config_parse_ifname,         0,                        offsetof(Settings, network_bridge)
index f190239cad70a2dc78489edb8765014149a50a74..ac9dc5116e8d72c3d4819e4f246b8d6b5f736307 100644 (file)
@@ -4,7 +4,7 @@
 #include <linux/magic.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "escape.h"
 #include "fd-util.h"
 #include "format-util.h"
@@ -636,7 +636,7 @@ int mount_all(const char *dest,
                 if (!tmpfs_tmp && FLAGS_SET(mount_table[k].mount_settings, MOUNT_APPLY_TMPFS_TMP))
                         continue;
 
-                r = chase_symlinks(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where, NULL);
+                r = chase(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].where);
 
@@ -695,7 +695,7 @@ int mount_all(const char *dest,
                          * mounts to be created within the container image before we transition into it. Note
                          * that MOUNT_IN_USERNS is run after we transitioned hence prefixing is not ncessary
                          * for those. */
-                        r = chase_symlinks(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL);
+                        r = chase(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].what);
                 }
@@ -779,7 +779,7 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u
         if (stat(m->source, &source_st) < 0)
                 return log_error_errno(errno, "Failed to stat %s: %m", m->source);
 
-        r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+        r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
         if (r > 0) { /* Path exists already? */
@@ -844,7 +844,7 @@ static int mount_tmpfs(const char *dest, CustomMount *m, uid_t uid_shift, const
         assert(dest);
         assert(m);
 
-        r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+        r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
         if (r == 0) { /* Doesn't exist yet? */
@@ -884,7 +884,7 @@ static int mount_overlay(const char *dest, CustomMount *m) {
         assert(dest);
         assert(m);
 
-        r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+        r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
         if (r == 0) { /* Doesn't exist yet? */
@@ -926,7 +926,7 @@ static int mount_inaccessible(const char *dest, CustomMount *m) {
         assert(dest);
         assert(m);
 
-        r = chase_symlinks_and_stat(m->destination, dest, CHASE_PREFIX_ROOT, &where, &st);
+        r = chase_and_stat(m->destination, dest, CHASE_PREFIX_ROOT, &where, &st);
         if (r < 0) {
                 log_full_errno(m->graceful ? LOG_DEBUG : LOG_ERR, r, "Failed to resolve %s/%s: %m", dest, m->destination);
                 return m->graceful ? 0 : r;
@@ -956,7 +956,7 @@ static int mount_arbitrary(const char *dest, CustomMount *m) {
         assert(dest);
         assert(m);
 
-        r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
+        r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
         if (r == 0) { /* Doesn't exist yet? */
index 143156484bc4e5e9cdd9509c27e67a1cb991561b..d898f0d4c911803a122cd50c471444fd1f1e00cb 100644 (file)
@@ -463,7 +463,7 @@ int remove_bridge(const char *bridge_name) {
         return remove_one_link(rtnl, bridge_name);
 }
 
-int test_network_interface_initialized(const char *name) {
+static int test_network_interface_initialized(const char *name) {
         _cleanup_(sd_device_unrefp) sd_device *d = NULL;
         int r;
 
@@ -491,18 +491,28 @@ int test_network_interface_initialized(const char *name) {
         return 0;
 }
 
-int move_network_interfaces(int netns_fd, char **ifaces) {
+int test_network_interfaces_initialized(char **iface_pairs) {
+        int r;
+        STRV_FOREACH_PAIR(a, b, iface_pairs) {
+                r = test_network_interface_initialized(*a);
+                if (r < 0)
+                        return r;
+        }
+        return 0;
+}
+
+int move_network_interfaces(int netns_fd, char **iface_pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int r;
 
-        if (strv_isempty(ifaces))
+        if (strv_isempty(iface_pairs))
                 return 0;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        STRV_FOREACH(i, ifaces) {
+        STRV_FOREACH_PAIR(i, b, iface_pairs) {
                 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
                 int ifi;
 
@@ -518,6 +528,12 @@ int move_network_interfaces(int netns_fd, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to append namespace fd to netlink message: %m");
 
+                if (!streq(*b, *i)) {
+                        r = sd_netlink_message_append_string(m, IFLA_IFNAME, *b);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add netlink interface name: %m");
+                }
+
                 r = sd_netlink_call(rtnl, m, 0, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i);
@@ -526,23 +542,23 @@ int move_network_interfaces(int netns_fd, char **ifaces) {
         return 0;
 }
 
-int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
+int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         unsigned idx = 0;
         int r;
 
-        if (strv_isempty(ifaces))
+        if (strv_isempty(iface_pairs))
                 return 0;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        STRV_FOREACH(i, ifaces) {
+        STRV_FOREACH_PAIR(i, b, iface_pairs) {
                 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-                _cleanup_free_ char *n = NULL, *a = NULL;
+                _cleanup_free_ char *n = NULL;
+                int shortened, ifi;
                 struct ether_addr mac;
-                int ifi;
 
                 ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
                 if (ifi < 0)
@@ -560,16 +576,11 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add netlink interface index: %m");
 
-                n = strjoin("mv-", *i);
+                n = strdup(*b);
                 if (!n)
                         return log_oom();
 
-                r = shorten_ifname(n);
-                if (r > 0) {
-                        a = strjoin("mv-", *i);
-                        if (!a)
-                                return log_oom();
-                }
+                shortened = shorten_ifname(n);
 
                 r = sd_netlink_message_append_string(m, IFLA_IFNAME, n);
                 if (r < 0)
@@ -607,27 +618,28 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add new macvlan interfaces: %m");
 
-                (void) set_alternative_ifname(rtnl, n, a);
+                if (shortened > 0)
+                        (void) set_alternative_ifname(rtnl, n, *b);
         }
 
         return 0;
 }
 
-int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
+int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int r;
 
-        if (strv_isempty(ifaces))
+        if (strv_isempty(iface_pairs))
                 return 0;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        STRV_FOREACH(i, ifaces) {
+        STRV_FOREACH_PAIR(i, b, iface_pairs) {
                 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-                _cleanup_free_ char *n = NULL, *a = NULL;
-                int ifi;
+                _cleanup_free_ char *n = NULL;
+                int shortened, ifi ;
 
                 ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
                 if (ifi < 0)
@@ -641,16 +653,11 @@ int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add netlink interface index: %m");
 
-                n = strjoin("iv-", *i);
+                n = strdup(*b);
                 if (!n)
                         return log_oom();
 
-                r = shorten_ifname(n);
-                if (r > 0) {
-                        a = strjoin("iv-", *i);
-                        if (!a)
-                                return log_oom();
-                }
+                shortened = shorten_ifname(n);
 
                 r = sd_netlink_message_append_string(m, IFLA_IFNAME, n);
                 if (r < 0)
@@ -684,7 +691,8 @@ int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add new ipvlan interfaces: %m");
 
-                (void) set_alternative_ifname(rtnl, n, a);
+                if (shortened > 0)
+                        (void) set_alternative_ifname(rtnl, n, *b);
         }
 
         return 0;
@@ -742,3 +750,51 @@ int remove_veth_links(const char *primary, char **pairs) {
 
         return 0;
 }
+
+static int network_iface_pair_parse(const char* iftype, char ***l, const char *p, const char* ifprefix) {
+        _cleanup_free_ char *a = NULL, *b = NULL;
+        int r;
+
+        r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r < 0)
+                return log_error_errno(r, "Failed to extract first word in %s parameter: %m", iftype);
+        if (r == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Short read while reading %s parameter: %m", iftype);
+        if (!ifname_valid(a))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "%s, interface name not valid: %s", iftype, a);
+
+        if (isempty(p)) {
+                if (ifprefix)
+                        b = strjoin(ifprefix, a);
+                else
+                        b = strdup(a);
+        } else
+                b = strdup(p);
+        if (!b)
+                return log_oom();
+
+        if (!ifname_valid(b))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                        "%s, interface name not valid: %s", iftype, b);
+
+        r = strv_push_pair(l, a, b);
+        if (r < 0)
+                return log_oom();
+
+        a = b = NULL;
+        return 0;
+}
+
+int interface_pair_parse(char ***l, const char *p) {
+        return network_iface_pair_parse("Network interface", l, p, NULL);
+}
+
+int macvlan_pair_parse(char ***l, const char *p) {
+        return network_iface_pair_parse("MACVLAN network interface", l, p, "mv-");
+}
+
+int ipvlan_pair_parse(char ***l, const char *p) {
+        return network_iface_pair_parse("IPVLAN network interface", l, p, "iv-");
+}
index 5c2d9834180a504ca5cebba16844052de5a93b4f..355d813c963d28f64ae7950a3764f45ab60d095f 100644 (file)
@@ -5,7 +5,7 @@
 #include <stdbool.h>
 #include <sys/types.h>
 
-int test_network_interface_initialized(const char *name);
+int test_network_interfaces_initialized(char **iface_pairs);
 
 int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge);
 int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs);
@@ -13,11 +13,15 @@ int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs);
 int setup_bridge(const char *veth_name, const char *bridge_name, bool create);
 int remove_bridge(const char *bridge_name);
 
-int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces);
-int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces);
+int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs);
+int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs);
 
-int move_network_interfaces(int netns_fd, char **ifaces);
+int move_network_interfaces(int netns_fd, char **iface_pairs);
 
 int veth_extra_parse(char ***l, const char *p);
 
 int remove_veth_links(const char *primary, char **pairs);
+
+int interface_pair_parse(char ***l, const char *p);
+int macvlan_pair_parse(char ***l, const char *p);
+int ipvlan_pair_parse(char ***l, const char *p);
index 05bde1c7567772e01591e1e98ce014e11af0b54b..7500eabd1882cd98c0e9172bef3187a0a649a692 100644 (file)
@@ -469,6 +469,69 @@ int config_parse_veth_extra(
         return 0;
 }
 
+int config_parse_network_iface_pair(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char*** l = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        return interface_pair_parse(l, rvalue);
+}
+
+int config_parse_macvlan_iface_pair(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char*** l = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        return macvlan_pair_parse(l, rvalue);
+}
+
+int config_parse_ipvlan_iface_pair(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char*** l = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        return ipvlan_pair_parse(l, rvalue);
+}
+
 int config_parse_network_zone(
                 const char *unit,
                 const char *filename,
index 004b663e9e4011e7719d2b9db41e9d96c874dbcd..0a3d975364954113fffc936f94346cc143f51eb1 100644 (file)
@@ -259,6 +259,9 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tmpfs);
 CONFIG_PARSER_PROTOTYPE(config_parse_overlay);
 CONFIG_PARSER_PROTOTYPE(config_parse_inaccessible);
 CONFIG_PARSER_PROTOTYPE(config_parse_veth_extra);
+CONFIG_PARSER_PROTOTYPE(config_parse_network_iface_pair);
+CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_iface_pair);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_iface_pair);
 CONFIG_PARSER_PROTOTYPE(config_parse_network_zone);
 CONFIG_PARSER_PROTOTYPE(config_parse_boot);
 CONFIG_PARSER_PROTOTYPE(config_parse_pid2);
index 7122902aa0291235e806ad0482bc73a40a8ac41a..9e5b7772ac27264c7e6213f96f6d86b3ad7a29a7 100644 (file)
@@ -34,7 +34,7 @@
 #include "cap-list.h"
 #include "capability-util.h"
 #include "cgroup-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "common-signal.h"
 #include "copy.h"
 #include "cpu-set-util.h"
@@ -234,6 +234,7 @@ static char **arg_bind_user = NULL;
 static bool arg_suppress_sync = false;
 static char *arg_settings_filename = NULL;
 static Architecture arg_architecture = _ARCHITECTURE_INVALID;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
@@ -268,6 +269,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_cpu_set, cpu_set_reset);
 STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static int handle_arg_console(const char *arg) {
         if (streq(arg, "help")) {
@@ -330,6 +332,7 @@ static int help(void) {
                "                            remove it after exit\n"
                "  -i --image=PATH           Root file system disk image (or device node) for\n"
                "                            the container\n"
+               "     --image-policy=POLICY  Specify disk image dissection policy\n"
                "     --oci-bundle=PATH      OCI bundle directory\n"
                "     --read-only            Mount the root directory read-only\n"
                "     --volatile[=MODE]      Run the system in volatile mode\n"
@@ -374,13 +377,13 @@ static int help(void) {
                "                            --private-users-ownership=auto\n\n"
                "%3$sNetworking:%4$s\n"
                "     --private-network      Disable network in container\n"
-               "     --network-interface=INTERFACE\n"
+               "     --network-interface=HOSTIF[:CONTAINERIF]\n"
                "                            Assign an existing network interface to the\n"
                "                            container\n"
-               "     --network-macvlan=INTERFACE\n"
+               "     --network-macvlan=HOSTIF[:CONTAINERIF]\n"
                "                            Create a macvlan network interface based on an\n"
                "                            existing network interface to the container\n"
-               "     --network-ipvlan=INTERFACE\n"
+               "     --network-ipvlan=HOSTIF[:CONTAINERIF]\n"
                "                            Create an ipvlan network interface based on an\n"
                "                            existing network interface to the container\n"
                "  -n --network-veth         Add a virtual Ethernet connection between host\n"
@@ -732,6 +735,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_LOAD_CREDENTIAL,
                 ARG_BIND_USER,
                 ARG_SUPPRESS_SYNC,
+                ARG_IMAGE_POLICY,
         };
 
         static const struct option options[] = {
@@ -805,6 +809,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "load-credential",        required_argument, NULL, ARG_LOAD_CREDENTIAL        },
                 { "bind-user",              required_argument, NULL, ARG_BIND_USER              },
                 { "suppress-sync",          required_argument, NULL, ARG_SUPPRESS_SYNC          },
+                { "image-policy",           required_argument, NULL, ARG_IMAGE_POLICY           },
                 {}
         };
 
@@ -815,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) {
 
@@ -916,51 +924,28 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_NETWORK_INTERFACE:
-                        if (!ifname_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Network interface name not valid: %s", optarg);
-
-                        r = test_network_interface_initialized(optarg);
+                        r = interface_pair_parse(&arg_network_interfaces, optarg);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend(&arg_network_interfaces, optarg) < 0)
-                                return log_oom();
-
                         arg_private_network = true;
                         arg_settings_mask |= SETTING_NETWORK;
                         break;
 
                 case ARG_NETWORK_MACVLAN:
-
-                        if (!ifname_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "MACVLAN network interface name not valid: %s", optarg);
-
-                        r = test_network_interface_initialized(optarg);
+                        r = macvlan_pair_parse(&arg_network_macvlan, optarg);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend(&arg_network_macvlan, optarg) < 0)
-                                return log_oom();
-
                         arg_private_network = true;
                         arg_settings_mask |= SETTING_NETWORK;
                         break;
 
                 case ARG_NETWORK_IPVLAN:
-
-                        if (!ifname_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "IPVLAN network interface name not valid: %s", optarg);
-
-                        r = test_network_interface_initialized(optarg);
+                        r = ipvlan_pair_parse(&arg_network_ipvlan, optarg);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend(&arg_network_ipvlan, optarg) < 0)
-                                return log_oom();
-
                         _fallthrough_;
                 case ARG_PRIVATE_NETWORK:
                         arg_private_network = true;
@@ -1696,6 +1681,12 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_settings_mask |= SETTING_SUPPRESS_SYNC;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1874,6 +1865,23 @@ static int verify_arguments(void) {
         return 0;
 }
 
+static int verify_network_interfaces_initialized(void) {
+        int r;
+        r = test_network_interfaces_initialized(arg_network_interfaces);
+        if (r < 0)
+                return r;
+
+        r = test_network_interfaces_initialized(arg_network_macvlan);
+        if (r < 0)
+                return r;
+
+        r = test_network_interfaces_initialized(arg_network_ipvlan);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 int userns_lchown(const char *p, uid_t uid, gid_t gid) {
         assert(p);
 
@@ -1958,7 +1966,7 @@ static int setup_timezone(const char *dest) {
         if (m == TIMEZONE_OFF)
                 return 0;
 
-        r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
+        r = chase("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
         if (r < 0) {
                 log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m");
                 return 0;
@@ -1989,7 +1997,7 @@ static int setup_timezone(const char *dest) {
                         return 0; /* Already pointing to the right place? Then do nothing .. */
 
                 check = strjoina(dest, "/usr/share/zoneinfo/", z);
-                r = chase_symlinks(check, dest, 0, NULL, NULL);
+                r = chase(check, dest, 0, NULL, NULL);
                 if (r < 0)
                         log_debug_errno(r, "Timezone %s does not exist (or is not accessible) in container, not creating symlink: %m", z);
                 else {
@@ -2016,7 +2024,7 @@ static int setup_timezone(const char *dest) {
                 _cleanup_free_ char *resolved = NULL;
                 int found;
 
-                found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved, NULL);
+                found = chase(where, dest, CHASE_NONEXISTENT, &resolved, NULL);
                 if (found < 0) {
                         log_warning_errno(found, "Failed to resolve /etc/localtime path in container, ignoring: %m");
                         return 0;
@@ -2034,7 +2042,7 @@ static int setup_timezone(const char *dest) {
 
         case TIMEZONE_COPY:
                 /* If mounting failed, try to copy */
-                r = copy_file_atomic("/etc/localtime", where, 0644, 0, 0, COPY_REFLINK|COPY_REPLACE);
+                r = copy_file_atomic("/etc/localtime", where, 0644, COPY_REFLINK|COPY_REPLACE);
                 if (r < 0) {
                         log_full_errno(IN_SET(r, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
                                        "Failed to copy /etc/localtime to %s, ignoring: %m", where);
@@ -2123,7 +2131,7 @@ static int setup_resolv_conf(const char *dest) {
         if (m == RESOLV_CONF_OFF)
                 return 0;
 
-        r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
+        r = chase("/etc", dest, CHASE_PREFIX_ROOT, &etc, NULL);
         if (r < 0) {
                 log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m");
                 return 0;
@@ -2151,7 +2159,7 @@ static int setup_resolv_conf(const char *dest) {
                 _cleanup_free_ char *resolved = NULL;
                 int found;
 
-                found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved, NULL);
+                found = chase(where, dest, CHASE_NONEXISTENT|CHASE_NOFOLLOW, &resolved, NULL);
                 if (found < 0) {
                         log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m");
                         return 0;
@@ -2168,9 +2176,9 @@ static int setup_resolv_conf(const char *dest) {
         }
 
         if (IN_SET(m, RESOLV_CONF_REPLACE_HOST, RESOLV_CONF_REPLACE_STATIC, RESOLV_CONF_REPLACE_UPLINK, RESOLV_CONF_REPLACE_STUB))
-                r = copy_file_atomic(what, where, 0644, 0, 0, COPY_REFLINK|COPY_REPLACE);
+                r = copy_file_atomic(what, where, 0644, COPY_REFLINK|COPY_REPLACE);
         else
-                r = copy_file(what, where, O_TRUNC|O_NOFOLLOW, 0644, 0, 0, COPY_REFLINK);
+                r = copy_file(what, where, O_TRUNC|O_NOFOLLOW, 0644, COPY_REFLINK);
         if (r < 0) {
                 /* If the file already exists as symlink, let's suppress the warning, under the assumption that
                  * resolved or something similar runs inside and the symlink points there.
@@ -2828,8 +2836,6 @@ static int mount_tunnel_open(void) {
 }
 
 static int setup_machine_id(const char *directory) {
-        const char *etc_machine_id;
-        sd_id128_t id;
         int r;
 
         /* If the UUID in the container is already set, then that's what counts, and we use. If it isn't set, and the
@@ -2839,9 +2845,7 @@ static int setup_machine_id(const char *directory) {
          * in the container and our idea of the container UUID will always be in sync (at least if PID 1 in the
          * container behaves nicely). */
 
-        etc_machine_id = prefix_roota(directory, "/etc/machine-id");
-
-        r = id128_read(etc_machine_id, ID128_FORMAT_PLAIN, &id);
+        r = id128_get_machine(directory, &arg_uuid);
         if (r < 0) {
                 if (!ERRNO_IS_MACHINE_ID_UNSET(r)) /* If the file is missing, empty, or uninitialized, we don't mind */
                         return log_error_errno(r, "Failed to read machine ID from container image: %m");
@@ -2851,12 +2855,6 @@ static int setup_machine_id(const char *directory) {
                         if (r < 0)
                                 return log_error_errno(r, "Failed to acquire randomized machine UUID: %m");
                 }
-        } else {
-                if (sd_id128_is_null(id))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "Machine ID in container image is zero, refusing.");
-
-                arg_uuid = id;
         }
 
         return 0;
@@ -3094,7 +3092,7 @@ static int determine_names(void) {
         return 0;
 }
 
-static int chase_symlinks_and_update(char **p, unsigned flags) {
+static int chase_and_update(char **p, unsigned flags) {
         char *chased;
         int r;
 
@@ -3103,7 +3101,7 @@ static int chase_symlinks_and_update(char **p, unsigned flags) {
         if (!*p)
                 return 0;
 
-        r = chase_symlinks(*p, NULL, flags, &chased, NULL);
+        r = chase(*p, NULL, flags, &chased, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve path %s: %m", *p);
 
@@ -3551,6 +3549,7 @@ static int inner_child(
          * it again. Note that the other fds closed here are at least the locking and barrier fds. */
         log_close();
         log_set_open_when_needed(true);
+        log_settle_target();
 
         (void) fdset_close_others(fds);
 
@@ -4579,8 +4578,7 @@ static int merge_settings(Settings *settings, const char *path) {
                         log_warning("Ignoring CPUAffinity= setting, file '%s' is not trusted.", path);
                 else {
                         cpu_set_reset(&arg_cpu_set);
-                        arg_cpu_set = settings->cpu_set;
-                        settings->cpu_set = (CPUSet) {};
+                        arg_cpu_set = TAKE_STRUCT(settings->cpu_set);
                 }
         }
 
@@ -5277,6 +5275,10 @@ static int run_container(
                                 _exit(EXIT_FAILURE);
                         }
 
+                        /* Reverse network interfaces pair list so that interfaces get their initial name back.
+                         * This is about ensuring interfaces get their old name back when being moved back. */
+                        arg_network_interfaces = strv_reverse(arg_network_interfaces);
+
                         r = move_network_interfaces(parent_netns_fd, arg_network_interfaces);
                         if (r < 0)
                                 log_error_errno(r, "Failed to move network interfaces back to parent network namespace: %m");
@@ -5495,6 +5497,10 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 goto finish;
 
+        r = verify_network_interfaces_initialized();
+        if (r < 0)
+                goto finish;
+
         /* Reapply environment settings. */
         (void) detect_unified_cgroup_hierarchy_from_environment();
 
@@ -5533,7 +5539,7 @@ static int run(int argc, char *argv[]) {
                 if (arg_ephemeral) {
                         _cleanup_free_ char *np = NULL;
 
-                        r = chase_symlinks_and_update(&arg_directory, 0);
+                        r = chase_and_update(&arg_directory, 0);
                         if (r < 0)
                                 goto finish;
 
@@ -5584,7 +5590,7 @@ static int run(int argc, char *argv[]) {
                         free_and_replace(arg_directory, np);
                         remove_directory = true;
                 } else {
-                        r = chase_symlinks_and_update(&arg_directory, arg_template ? CHASE_NONEXISTENT : 0);
+                        r = chase_and_update(&arg_directory, arg_template ? CHASE_NONEXISTENT : 0);
                         if (r < 0)
                                 goto finish;
 
@@ -5599,7 +5605,7 @@ static int run(int argc, char *argv[]) {
                         }
 
                         if (arg_template) {
-                                r = chase_symlinks_and_update(&arg_template, 0);
+                                r = chase_and_update(&arg_template, 0);
                                 if (r < 0)
                                         goto finish;
 
@@ -5675,7 +5681,7 @@ static int run(int argc, char *argv[]) {
                 assert(arg_image);
                 assert(!arg_template);
 
-                r = chase_symlinks_and_update(&arg_image, 0);
+                r = chase_and_update(&arg_image, 0);
                 if (r < 0)
                         goto finish;
 
@@ -5697,7 +5703,10 @@ static int run(int argc, char *argv[]) {
 
                         {
                                 BLOCK_SIGNALS(SIGINT);
-                                r = copy_file(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600, FS_NOCOW_FL, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME|COPY_SIGINT);
+                                r = copy_file_full(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600,
+                                                   FS_NOCOW_FL, FS_NOCOW_FL,
+                                                   COPY_REFLINK|COPY_CRTIME|COPY_SIGINT,
+                                                   NULL, NULL);
                         }
                         if (r == -EINTR) {
                                 log_error_errno(r, "Interrupted while copying image file to %s, removed again.", np);
@@ -5761,7 +5770,8 @@ static int run(int argc, char *argv[]) {
                 r = dissect_loop_device_and_warn(
                                 loop,
                                 &arg_verity_settings,
-                                NULL,
+                                /* mount_options=*/ NULL,
+                                arg_image_policy ?: &image_policy_container,
                                 dissect_image_flags,
                                 &dissected_image);
                 if (r == -ENOPKG) {
index 4a43807b879408d0336fd010fe6bab47c339e209..08a29ec77bd84ec075ecf50401ec07cc1c9c70d9 100644 (file)
@@ -516,7 +516,10 @@ static int monitor_memory_pressure_contexts_handler(sd_event_source *s, uint64_t
                         else
                                 clear_candidates = NULL;
 
-                        r = oomd_kill_by_pgscan_rate(m->monitored_mem_pressure_cgroup_contexts_candidates, t->path, m->dry_run, &selected);
+                        r = oomd_kill_by_pgscan_rate(m->monitored_mem_pressure_cgroup_contexts_candidates,
+                                                     /* prefix= */ t->path,
+                                                     /* dry_run= */ m->dry_run,
+                                                     &selected);
                         if (r == -ENOMEM)
                                 return log_oom();
                         if (r < 0)
index f97b771c5053cd681ce653fc0f8ad06fae69aa93..49c10b5e16c235d4f9860531e33a6eb76e664975 100644 (file)
@@ -321,7 +321,7 @@ int oomd_kill_by_pgscan_rate(Hashmap *h, const char *prefix, bool dry_run, char
                 if (sorted[i]->pgscan == 0 && sorted[i]->current_memory_usage == 0)
                         continue;
 
-                r = oomd_cgroup_kill(sorted[i]->path, true, dry_run);
+                r = oomd_cgroup_kill(sorted[i]->path, /* recurse= */ true, /* dry_run= */ dry_run);
                 if (r == -ENOMEM)
                         return r; /* Treat oom as a hard error */
                 if (r < 0) {
@@ -365,7 +365,7 @@ int oomd_kill_by_swap_usage(Hashmap *h, uint64_t threshold_usage, bool dry_run,
                 if (sorted[i]->swap_usage <= threshold_usage)
                         continue;
 
-                r = oomd_cgroup_kill(sorted[i]->path, true, dry_run);
+                r = oomd_cgroup_kill(sorted[i]->path, /* recurse= */ true, /* dry_run= */ dry_run);
                 if (r == -ENOMEM)
                         return r; /* Treat oom as a hard error */
                 if (r < 0) {
index 44c8384e1cd918e0b980153b571a35c64100fd27..3a4808d6a4a09d0c95daceb0d10eb9f99a2929d2 100644 (file)
@@ -20,7 +20,7 @@
 #include "blockdev-util.h"
 #include "btrfs-util.h"
 #include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "conf-parser.h"
 #include "constants.h"
@@ -148,6 +148,7 @@ static FilterPartitionsType arg_filter_partitions_type = FILTER_PARTITIONS_NONE;
 static sd_id128_t *arg_defer_partitions = NULL;
 static size_t arg_n_defer_partitions = 0;
 static uint64_t arg_sector_size = 0;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -158,6 +159,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 typedef struct FreeArea FreeArea;
 
@@ -1996,6 +1998,28 @@ static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) {
         return 0;
 }
 
+static int context_open_and_lock_backing_fd(Context *context, const char *node) {
+        _cleanup_close_ int fd = -EBADF;
+
+        assert(context);
+        assert(node);
+
+        if (context->backing_fd >= 0)
+                return 0;
+
+        fd = open(node, O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return log_error_errno(errno, "Failed to open device '%s': %m", node);
+
+        /* Tell udev not to interfere while we are processing the device */
+        if (flock(fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
+                return log_error_errno(errno, "Failed to lock device '%s': %m", node);
+
+        log_debug("Device %s opened and locked.", node);
+        context->backing_fd = TAKE_FD(fd);
+        return 1;
+}
+
 static int context_load_partition_table(Context *context) {
         _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
         _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
@@ -2024,11 +2048,9 @@ static int context_load_partition_table(Context *context) {
         else {
                 uint32_t ssz;
 
-                if (context->backing_fd < 0) {
-                        context->backing_fd = open(context->node, O_RDONLY|O_CLOEXEC);
-                        if (context->backing_fd < 0)
-                                return log_error_errno(errno, "Failed to open device '%s': %m", context->node);
-                }
+                r = context_open_and_lock_backing_fd(context, context->node);
+                if (r < 0)
+                        return r;
 
                 /* Auto-detect sector size if not specified. */
                 r = probe_sector_size_prefer_ioctl(context->backing_fd, &ssz);
@@ -2073,13 +2095,9 @@ static int context_load_partition_table(Context *context) {
 
         if (context->backing_fd < 0) {
                 /* If we have no fd referencing the device yet, make a copy of the fd now, so that we have one */
-                context->backing_fd = fd_reopen(fdisk_get_devfd(c), O_RDONLY|O_CLOEXEC);
-                if (context->backing_fd < 0)
-                        return log_error_errno(context->backing_fd, "Failed to duplicate fdisk fd: %m");
-
-                /* Tell udev not to interfere while we are processing the device */
-                if (flock(context->backing_fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
-                        return log_error_errno(errno, "Failed to lock block device: %m");
+                r = context_open_and_lock_backing_fd(context, FORMAT_PROC_FD_PATH(fdisk_get_devfd(c)));
+                if (r < 0)
+                        return r;
         }
 
         /* The offsets/sizes libfdisk returns to us will be in multiple of the sector size of the
@@ -3018,7 +3036,11 @@ static int context_discard_gap_after(Context *context, Partition *p) {
         if (p)
                 gap = p->offset + p->new_size;
         else
-                gap = context->start;
+                /* The context start gets rounded up to grain_size, however
+                 * existing partitions may be before that so ensure the gap
+                 * starts at the first actually usable lba
+                 */
+                gap = fdisk_get_first_lba(context->fdisk_context) * context->sector_size;
 
         LIST_FOREACH(partitions, q, context->partitions) {
                 if (q->dropped)
@@ -3035,7 +3057,7 @@ static int context_discard_gap_after(Context *context, Partition *p) {
         }
 
         if (next == UINT64_MAX) {
-                next = context->end;
+                next = (fdisk_get_last_lba(context->fdisk_context) + 1) * context->sector_size;
                 if (gap > next)
                         return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end.");
         }
@@ -3389,8 +3411,8 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) {
                 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
                 _cleanup_(erase_and_freep) void *secret = NULL;
                 _cleanup_free_ void *pubkey = NULL;
-                _cleanup_free_ void *blob = NULL, *hash = NULL;
-                size_t secret_size, blob_size, hash_size, pubkey_size = 0;
+                _cleanup_free_ void *blob = NULL, *hash = NULL, *srk_buf = NULL;
+                size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0;
                 ssize_t base64_encoded_size;
                 uint16_t pcr_bank, primary_alg;
                 int keyslot;
@@ -3415,7 +3437,9 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) {
                               &blob, &blob_size,
                               &hash, &hash_size,
                               &pcr_bank,
-                              &primary_alg);
+                              &primary_alg,
+                              &srk_buf,
+                              &srk_buf_size);
                 if (r < 0)
                         return log_error_errno(r, "Failed to seal to TPM2: %m");
 
@@ -3447,6 +3471,7 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) {
                                 blob, blob_size,
                                 hash, hash_size,
                                 NULL, 0, /* no salt because tpm2_seal has no pin */
+                                srk_buf, srk_buf_size,
                                 0,
                                 &v);
                 if (r < 0)
@@ -3828,7 +3853,7 @@ static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
                 if (rfd < 0)
                         return rfd;
 
-                sfd = chase_symlinks_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
+                sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
                 if (sfd < 0)
                         return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source);
 
@@ -3842,7 +3867,7 @@ static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
         STRV_FOREACH_PAIR(source, target, p->copy_files) {
                 _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
 
-                sfd = chase_symlinks_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
+                sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
                 if (sfd < 0)
                         return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source);
 
@@ -3852,7 +3877,7 @@ static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
                                 return log_error_errno(r, "Failed to check type of source file '%s': %m", *source);
 
                         /* We are looking at a directory */
-                        tfd = chase_symlinks_and_open(*target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+                        tfd = chase_and_open(*target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
                         if (tfd < 0) {
                                 _cleanup_free_ char *dn = NULL, *fn = NULL;
 
@@ -3871,7 +3896,7 @@ static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
                                 if (r < 0)
                                         return log_error_errno(r, "Failed to create parent directory '%s': %m", dn);
 
-                                pfd = chase_symlinks_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+                                pfd = chase_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
                                 if (pfd < 0)
                                         return log_error_errno(pfd, "Failed to open parent directory of target: %m");
 
@@ -3911,7 +3936,7 @@ static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
                         if (r < 0)
                                 return log_error_errno(r, "Failed to create parent directory: %m");
 
-                        pfd = chase_symlinks_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+                        pfd = chase_and_open(dn, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
                         if (pfd < 0)
                                 return log_error_errno(pfd, "Failed to open parent directory of target: %m");
 
@@ -3960,6 +3985,8 @@ static int partition_populate_directory(Partition *p, Hashmap *denylist, char **
 
         assert(ret);
 
+        log_info("Populating %s filesystem.", p->format);
+
         r = var_tmp_dir(&vt);
         if (r < 0)
                 return log_error_errno(r, "Could not determine temporary directory: %m");
@@ -3980,6 +4007,8 @@ static int partition_populate_directory(Partition *p, Hashmap *denylist, char **
         if (r < 0)
                 return r;
 
+        log_info("Successfully populated %s filesystem.", p->format);
+
         *ret = TAKE_PTR(root);
         return 0;
 }
@@ -3990,7 +4019,7 @@ static int partition_populate_filesystem(Partition *p, const char *node, Hashmap
         assert(p);
         assert(node);
 
-        log_info("Populating %s filesystem with files.", p->format);
+        log_info("Populating %s filesystem.", p->format);
 
         /* We copy in a child process, since we have to mount the fs for that, and we don't want that fs to
          * appear in the host namespace. Hence we fork a child that has its own file system namespace and
@@ -4027,7 +4056,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;
 }
 
@@ -4042,7 +4071,7 @@ static int add_exclude_path(const char *path, Hashmap **denylist, DenyType type)
         if (!st)
                 return log_oom();
 
-        r = chase_symlinks_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
+        r = chase_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
@@ -4906,24 +4935,14 @@ static int context_read_seed(Context *context, const char *root) {
                 return 0;
 
         if (!arg_randomize) {
-                _cleanup_close_ int fd = -EBADF;
-
-                fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, NULL);
-                if (fd == -ENOENT)
-                        log_info("No machine ID set, using randomized partition UUIDs.");
-                else if (fd < 0)
-                        return log_error_errno(fd, "Failed to determine machine ID of image: %m");
-                else {
-                        r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &context->seed);
-                        if (r < 0) {
-                                if (!ERRNO_IS_MACHINE_ID_UNSET(r))
-                                        return log_error_errno(r, "Failed to parse machine ID of image: %m");
+                r = id128_get_machine(root, &context->seed);
+                if (r >= 0)
+                        return 0;
 
-                                log_info("No machine ID set, using randomized partition UUIDs.");
-                        }
+                if (!ERRNO_IS_MACHINE_ID_UNSET(r))
+                        return log_error_errno(r, "Failed to parse machine ID of image: %m");
 
-                        return 0;
-                }
+                log_info("No machine ID set, using randomized partition UUIDs.");
         }
 
         r = sd_id128_randomize(&context->seed);
@@ -5123,7 +5142,7 @@ static int find_backing_devno(
 
         assert(path);
 
-        r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
+        r = chase(path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
         if (r < 0)
                 return r;
 
@@ -5297,7 +5316,7 @@ static int context_open_copy_block_paths(
 
                 if (p->copy_blocks_path) {
 
-                        source_fd = chase_symlinks_and_open(p->copy_blocks_path, p->copy_blocks_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
+                        source_fd = chase_and_open(p->copy_blocks_path, p->copy_blocks_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
                         if (source_fd < 0)
                                 return log_error_errno(source_fd, "Failed to open '%s': %m", p->copy_blocks_path);
 
@@ -5443,6 +5462,7 @@ static int context_minimize(Context *context) {
                 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
                 _cleanup_strv_free_ char **extra_mkfs_options = NULL;
                 _cleanup_close_ int fd = -EBADF;
+                _cleanup_free_ char *hint = NULL;
                 sd_id128_t fs_uuid;
                 uint64_t fsz;
 
@@ -5463,6 +5483,11 @@ static int context_minimize(Context *context) {
 
                 assert(!p->copy_blocks_path);
 
+                (void) partition_hint(p, context->node, &hint);
+
+                log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size",
+                         p->format, strna(hint));
+
                 r = make_copy_files_denylist(context, p, &denylist);
                 if (r < 0)
                         return r;
@@ -5520,6 +5545,14 @@ static int context_minimize(Context *context) {
                 /* Read-only filesystems are minimal from the first try because they create and size the
                  * loopback file for us. */
                 if (fstype_is_ro(p->format)) {
+                        struct stat st;
+
+                        if (stat(temp, &st) < 0)
+                                return log_error_errno(errno, "Failed to stat temporary file: %m");
+
+                        log_info("Minimal partition size of %s filesystem of partition %s is %s",
+                                 p->format, strna(hint), FORMAT_BYTES(st.st_size));
+
                         p->copy_blocks_path = TAKE_PTR(temp);
                         p->copy_blocks_path_is_our_file = true;
                         continue;
@@ -5553,6 +5586,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. */
@@ -5639,6 +5675,8 @@ static int help(void) {
                "     --can-factory-reset  Test whether factory reset is defined\n"
                "     --root=PATH          Operate relative to root path\n"
                "     --image=PATH         Operate relative to image file\n"
+               "     --image-policy=POLICY\n"
+               "                          Specify disk image dissection policy\n"
                "     --definitions=DIR    Find partition definitions in specified directory\n"
                "     --key-file=PATH      Key to use when encrypting partitions\n"
                "     --private-key=PATH   Private key to use when generating verity roothash\n"
@@ -5687,6 +5725,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_CAN_FACTORY_RESET,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_SEED,
                 ARG_PRETTY,
                 ARG_DEFINITIONS,
@@ -5704,6 +5743,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_EXCLUDE_PARTITIONS,
                 ARG_DEFER_PARTITIONS,
                 ARG_SECTOR_SIZE,
+                ARG_SKIP_PARTITIONS,
         };
 
         static const struct option options[] = {
@@ -5718,6 +5758,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "can-factory-reset",    no_argument,       NULL, ARG_CAN_FACTORY_RESET    },
                 { "root",                 required_argument, NULL, ARG_ROOT                 },
                 { "image",                required_argument, NULL, ARG_IMAGE                },
+                { "image-policy",         required_argument, NULL, ARG_IMAGE_POLICY         },
                 { "seed",                 required_argument, NULL, ARG_SEED                 },
                 { "pretty",               required_argument, NULL, ARG_PRETTY               },
                 { "definitions",          required_argument, NULL, ARG_DEFINITIONS          },
@@ -5817,6 +5858,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_SEED:
                         if (isempty(optarg)) {
                                 arg_seed = SD_ID128_NULL;
@@ -6165,7 +6212,7 @@ static int acquire_root_devno(
                 char **ret,
                 int *ret_fd) {
 
-        _cleanup_free_ char *found_path = NULL;
+        _cleanup_free_ char *found_path = NULL, *node = NULL;
         dev_t devno, fd_devno = MODE_INVALID;
         _cleanup_close_ int fd = -EBADF;
         struct stat st;
@@ -6175,7 +6222,7 @@ static int acquire_root_devno(
         assert(ret);
         assert(ret_fd);
 
-        fd = chase_symlinks_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path);
+        fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path);
         if (fd < 0)
                 return fd;
 
@@ -6220,13 +6267,22 @@ static int acquire_root_devno(
         if (r < 0)
                 log_debug_errno(r, "Failed to find whole disk block device for '%s', ignoring: %m", p);
 
-        r = devname_from_devnum(S_IFBLK, devno, ret);
+        r = devname_from_devnum(S_IFBLK, devno, &node);
         if (r < 0)
                 return log_debug_errno(r, "Failed to determine canonical path for '%s': %m", p);
 
         /* Only if we still look at the same block device we can reuse the fd. Otherwise return an
          * invalidated fd. */
-        *ret_fd = fd_devno != MODE_INVALID && fd_devno == devno ? TAKE_FD(fd) : -1;
+        if (fd_devno != MODE_INVALID && fd_devno == devno) {
+                /* Tell udev not to interfere while we are processing the device */
+                if (flock(fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
+                        return log_error_errno(errno, "Failed to lock device '%s': %m", node);
+
+                *ret_fd = TAKE_FD(fd);
+        } else
+                *ret_fd = -EBADF;
+
+        *ret = TAKE_PTR(node);
         return 0;
 }
 
@@ -6529,6 +6585,7 @@ static int run(int argc, char *argv[]) {
                  * systems */
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_MOUNT_READ_ONLY |
                                 (arg_node ? DISSECT_IMAGE_DEVICE_READ_ONLY : 0) | /* If a different node to make changes to is specified let's open the device in read-only mode) */
                                 DISSECT_IMAGE_GPT_ONLY |
index 7b0ee97c8c31f292c2393c7fb1e8723a01a80e9e..e5f2f38a211a8bc797b3da1aaaafc5632989e3ed 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "bus-common-errors.h"
 #include "bus-error.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "copy.h"
 #include "data-fd-util.h"
@@ -16,7 +16,7 @@
 #include "env-util.h"
 #include "errno-list.h"
 #include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
@@ -198,7 +198,7 @@ static int extract_now(
         /* First, find os-release/extension-release and send it upstream (or just save it). */
         if (path_is_extension) {
                 os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name);
-                r = open_extension_release(where, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
+                r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
         } else {
                 os_release_id = "/etc/os-release";
                 r = open_os_release(where, &os_release_path, &os_release_fd);
@@ -244,7 +244,7 @@ static int extract_now(
                 _cleanup_free_ char *resolved = NULL;
                 _cleanup_closedir_ DIR *d = NULL;
 
-                r = chase_symlinks_and_opendir(*i, where, 0, &resolved, &d);
+                r = chase_and_opendir(*i, where, 0, &resolved, &d);
                 if (r < 0) {
                         log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i);
                         continue;
@@ -324,6 +324,7 @@ static int portable_extract_by_path(
                 bool path_is_extension,
                 bool relax_extension_release_check,
                 char **matches,
+                const ImagePolicy *image_policy,
                 PortableMetadata **ret_os_release,
                 Hashmap **ret_unit_files,
                 sd_bus_error *error) {
@@ -369,7 +370,9 @@ static int portable_extract_by_path(
 
                 r = dissect_loop_device(
                                 d,
-                                NULL, NULL,
+                                /* verity= */ NULL,
+                                /* mount_options= */ NULL,
+                                image_policy,
                                 DISSECT_IMAGE_READ_ONLY |
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
@@ -510,6 +513,7 @@ static int extract_image_and_extensions(
                 char **extension_image_paths,
                 bool validate_sysext,
                 bool relax_extension_release_check,
+                const ImagePolicy *image_policy,
                 Image **ret_image,
                 OrderedHashmap **ret_extension_images,
                 OrderedHashmap **ret_extension_releases,
@@ -558,7 +562,15 @@ static int extract_image_and_extensions(
                 }
         }
 
-        r = portable_extract_by_path(image->path, /* path_is_extension= */ false, /* relax_extension_release_check= */ false, matches, &os_release, &unit_files, error);
+        r = portable_extract_by_path(
+                        image->path,
+                        /* path_is_extension= */ false,
+                        /* relax_extension_release_check= */ false,
+                        matches,
+                        image_policy,
+                        &os_release,
+                        &unit_files,
+                        error);
         if (r < 0)
                 return r;
 
@@ -566,18 +578,13 @@ static int extract_image_and_extensions(
          * extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
          * the units are started. Also, collect valid portable prefixes if caller requested that. */
         if (validate_sysext || ret_valid_prefixes) {
-                _cleanup_fclose_ FILE *f = NULL;
                 _cleanup_free_ char *prefixes = NULL;
 
-                r = take_fdopen_unlocked(&os_release->fd, "r", &f);
-                if (r < 0)
-                        return r;
-
-                r = parse_env_file(f, os_release->name,
-                                   "ID", &id,
-                                   "VERSION_ID", &version_id,
-                                   "SYSEXT_LEVEL", &sysext_level,
-                                   "PORTABLE_PREFIXES", &prefixes);
+                r = parse_env_file_fd(os_release->fd, os_release->name,
+                                     "ID", &id,
+                                     "VERSION_ID", &version_id,
+                                     "SYSEXT_LEVEL", &sysext_level,
+                                     "PORTABLE_PREFIXES", &prefixes);
                 if (r < 0)
                         return r;
                 if (isempty(id))
@@ -594,11 +601,17 @@ static int extract_image_and_extensions(
                 _cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL;
                 _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
                 _cleanup_strv_free_ char **extension_release = NULL;
-                _cleanup_close_ int extension_release_fd = -EBADF;
-                _cleanup_fclose_ FILE *f = NULL;
                 const char *e;
 
-                r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, relax_extension_release_check, matches, &extension_release_meta, &extra_unit_files, error);
+                r = portable_extract_by_path(
+                                ext->path,
+                                /* path_is_extension= */ true,
+                                relax_extension_release_check,
+                                matches,
+                                image_policy,
+                                &extension_release_meta,
+                                &extra_unit_files,
+                                error);
                 if (r < 0)
                         return r;
 
@@ -609,21 +622,12 @@ static int extract_image_and_extensions(
                 if (!validate_sysext && !ret_valid_prefixes && !ret_extension_releases)
                         continue;
 
-                /* We need to keep the fd valid, to return the PortableMetadata to the caller. */
-                extension_release_fd = fd_reopen(extension_release_meta->fd, O_CLOEXEC|O_RDONLY);
-                if (extension_release_fd < 0)
-                        return extension_release_fd;
-
-                r = take_fdopen_unlocked(&extension_release_fd, "r", &f);
-                if (r < 0)
-                        return r;
-
-                r = load_env_file_pairs(f, extension_release_meta->name, &extension_release);
+                r = load_env_file_pairs_fd(extension_release_meta->fd, extension_release_meta->name, &extension_release);
                 if (r < 0)
                         return r;
 
                 if (validate_sysext) {
-                        r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release);
+                        r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
                         if (r == 0)
                                 return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
                         if (r < 0)
@@ -673,6 +677,7 @@ int portable_extract(
                 const char *name_or_path,
                 char **matches,
                 char **extension_image_paths,
+                const ImagePolicy *image_policy,
                 PortableFlags flags,
                 PortableMetadata **ret_os_release,
                 OrderedHashmap **ret_extension_releases,
@@ -695,6 +700,7 @@ int portable_extract(
                         extension_image_paths,
                         /* validate_sysext= */ false,
                         /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
+                        image_policy,
                         &image,
                         &extension_images,
                         &extension_releases,
@@ -956,11 +962,67 @@ static int make_marker_text(const char *image_path, OrderedHashmap *extension_im
         return 0;
 }
 
+static int append_release_log_fields(
+                char **text,
+                const PortableMetadata *release,
+                ImageClass type,
+                const char *field_name) {
+
+        static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
+                 [IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
+                 [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
+        };
+        static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
+                 [IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
+                 [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
+        };
+        _cleanup_strv_free_ char **fields = NULL;
+        const char *id = NULL, *version = NULL;
+        int r;
+
+        assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT));
+        assert(!strv_isempty((char *const *)field_ids[type]));
+        assert(!strv_isempty((char *const *)field_versions[type]));
+        assert(field_name);
+        assert(text);
+
+        if (!release)
+                return 0; /* Nothing to do. */
+
+        r = load_env_file_pairs_fd(release->fd, release->name, &fields);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse '%s': %m", release->name);
+
+        /* Find an ID first, in order of preference from more specific to less specific: IMAGE_ID -> ID */
+        id = strv_find_first_field((char *const *)field_ids[type], fields);
+
+        /* Then the version, same logic, prefer the more specific one */
+        version = strv_find_first_field((char *const *)field_versions[type], fields);
+
+        /* If there's no valid version to be found, simply omit it. */
+        if (!id && !version)
+                return 0;
+
+        if (!strextend(text,
+                       "LogExtraFields=",
+                       field_name,
+                       "=",
+                       strempty(id),
+                       id && version ? "_" : "",
+                       strempty(version),
+                       "\n"))
+                return -ENOMEM;
+
+        return 0;
+}
+
 static int install_chroot_dropin(
                 const char *image_path,
                 ImageType type,
                 OrderedHashmap *extension_images,
+                OrderedHashmap *extension_releases,
                 const PortableMetadata *m,
+                const PortableMetadata *os_release,
                 const char *dropin_dir,
                 PortableFlags flags,
                 char **ret_dropin,
@@ -1010,19 +1072,67 @@ static int install_chroot_dropin(
                                "LogExtraFields=PORTABLE=", base_name, "\n"))
                         return -ENOMEM;
 
+                /* If we have a single image then PORTABLE= will point to it, so we add
+                 * PORTABLE_NAME_AND_VERSION= with the os-release fields and we are done. But if we have
+                 * extensions, PORTABLE= will point to the image where the current unit was found in. So we
+                 * also list PORTABLE_ROOT= and PORTABLE_ROOT_NAME_AND_VERSION= for the base image, and
+                 * PORTABLE_EXTENSION= and PORTABLE_EXTENSION_NAME_AND_VERSION= for each extension, so that
+                 * all needed metadata is available. */
+                if (ordered_hashmap_isempty(extension_images))
+                        r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_NAME_AND_VERSION");
+                else {
+                        _cleanup_free_ char *root_base_name = NULL;
+
+                        r = path_extract_filename(image_path, &root_base_name);
+                        if (r < 0)
+                                return log_debug_errno(r, "Failed to extract basename from '%s': %m", image_path);
+
+                        if (!strextend(&text,
+                                       "Environment=PORTABLE_ROOT=", root_base_name, "\n",
+                                       "LogExtraFields=PORTABLE_ROOT=", root_base_name, "\n"))
+                                return -ENOMEM;
+
+                        r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_ROOT_NAME_AND_VERSION");
+                }
+                if (r < 0)
+                        return r;
+
                 if (m->image_path && !path_equal(m->image_path, image_path))
-                        ORDERED_HASHMAP_FOREACH(ext, extension_images)
+                        ORDERED_HASHMAP_FOREACH(ext, extension_images) {
+                                _cleanup_free_ char *extension_base_name = NULL;
+
+                                r = path_extract_filename(ext->path, &extension_base_name);
+                                if (r < 0)
+                                        return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path);
+
                                 if (!strextend(&text,
+                                               "\n",
                                                extension_setting_from_image(ext->type),
                                                ext->path,
                                                /* With --force tell PID1 to avoid enforcing that the image <name> and
                                                 * extension-release.<name> have to match. */
                                                !IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) &&
                                                    FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT) ?
-                                                       ":x-systemd.relax-extension-release-check" :
-                                                       "",
-                                               "\n"))
+                                                       ":x-systemd.relax-extension-release-check\n" :
+                                                       "\n",
+                                               /* In PORTABLE= we list the 'main' image name for this unit
+                                                * (the image where the unit was extracted from), but we are
+                                                * stacking multiple images, so list those too. */
+                                               "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n"))
                                         return -ENOMEM;
+
+                                /* Look for image/version identifiers in the extension release files. We
+                                 * look for all possible IDs, but typically only 1 or 2 will be set, so
+                                 * the number of fields added shouldn't be too large. We prefix the DDI
+                                 * name to the value, so that we can add the same field multiple times and
+                                 * still be able to identify what applies to what. */
+                                r = append_release_log_fields(&text,
+                                                              ordered_hashmap_get(extension_releases, ext->name),
+                                                              IMAGE_SYSEXT,
+                                                              "PORTABLE_EXTENSION_NAME_AND_VERSION");
+                                if (r < 0)
+                                        return r;
+                        }
         }
 
         r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
@@ -1072,7 +1182,7 @@ static int install_profile_dropin(
 
         if (flags & PORTABLE_PREFER_COPY) {
 
-                r = copy_file_atomic(from, dropin, 0644, 0, 0, COPY_REFLINK);
+                r = copy_file_atomic(from, dropin, 0644, COPY_REFLINK);
                 if (r < 0)
                         return log_debug_errno(r, "Failed to copy %s %s %s: %m", from, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), dropin);
 
@@ -1111,7 +1221,9 @@ static int attach_unit_file(
                 const char *image_path,
                 ImageType type,
                 OrderedHashmap *extension_images,
+                OrderedHashmap *extension_releases,
                 const PortableMetadata *m,
+                const PortableMetadata *os_release,
                 const char *profile,
                 PortableFlags flags,
                 PortableChange **changes,
@@ -1155,7 +1267,7 @@ static int attach_unit_file(
          * is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
          * all for PID 1. */
 
-        r = install_chroot_dropin(image_path, type, extension_images, m, dropin_dir, flags, &chroot_dropin, changes, n_changes);
+        r = install_chroot_dropin(image_path, type, extension_images, extension_releases, m, os_release, dropin_dir, flags, &chroot_dropin, changes, n_changes);
         if (r < 0)
                 return r;
 
@@ -1302,12 +1414,14 @@ int portable_attach(
                 char **matches,
                 const char *profile,
                 char **extension_image_paths,
+                const ImagePolicy *image_policy,
                 PortableFlags flags,
                 PortableChange **changes,
                 size_t *n_changes,
                 sd_bus_error *error) {
 
-        _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
+        _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
+        _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
         _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
         _cleanup_(lookup_paths_free) LookupPaths paths = {};
         _cleanup_strv_free_ char **valid_prefixes = NULL;
@@ -1321,10 +1435,11 @@ int portable_attach(
                         extension_image_paths,
                         /* validate_sysext= */ true,
                         /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
+                        image_policy,
                         &image,
                         &extension_images,
-                        /* extension_releases= */ NULL,
-                        /* os_release= */ NULL,
+                        &extension_releases,
+                        &os_release,
                         &unit_files,
                         &valid_prefixes,
                         error);
@@ -1391,8 +1506,8 @@ int portable_attach(
                 }
 
         HASHMAP_FOREACH(item, unit_files) {
-                r = attach_unit_file(&paths, image->path, image->type, extension_images,
-                                     item, profile, flags, changes, n_changes);
+                r = attach_unit_file(&paths, image->path, image->type, extension_images, extension_releases,
+                                     item, os_release, profile, flags, changes, n_changes);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name);
         }
index 1a33f30944c24cc3065897f880e424f4d9414c31..c61d65fed3500f94874d415ac658bbe37da1eb39 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "sd-bus.h"
 
+#include "dissect-image.h"
 #include "hashmap.h"
 #include "macro.h"
 #include "set.h"
@@ -67,9 +68,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
 
 int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
 
-int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
+int portable_extract(const char *image, char **matches, char **extension_image_paths, const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
 
-int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
+int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, const ImagePolicy* image_policy, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
 int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
 
 int portable_get_state(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableState *ret, sd_bus_error *error);
index 6a638c35c55e1328c1468a492ab72d1f0961c598..eec9c63d0b8ca0f1026c5b4ed6c2265ae7544784 100644 (file)
@@ -11,7 +11,7 @@
 #include "bus-locator.h"
 #include "bus-unit-util.h"
 #include "bus-wait-for-jobs.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "constants.h"
 #include "dirent-util.h"
 #include "env-file.h"
@@ -84,7 +84,7 @@ static int determine_image(const char *image, bool permit_non_existing, char **r
                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                        "Operations on images by path not supported when connecting to remote systems.");
 
-        r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
+        r = chase(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret, NULL);
         if (r < 0)
                 return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
 
@@ -400,7 +400,8 @@ static int inspect_image(int argc, char *argv[], void *userdata) {
                                 nl = true;
                         } else {
                                 _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL,
-                                        *id = NULL, *version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL;
+                                        *sysext_id = NULL, *sysext_version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL,
+                                        *id = NULL, *version_id = NULL, *image_id = NULL, *image_version = NULL, *build_id = NULL;
                                 _cleanup_fclose_ FILE *f = NULL;
 
                                 f = fmemopen_unlocked((void*) data, sz, "r");
@@ -408,30 +409,42 @@ static int inspect_image(int argc, char *argv[], void *userdata) {
                                         return log_error_errno(errno, "Failed to open extension-release buffer: %m");
 
                                 r = parse_env_file(f, name,
-                                                   "ID", &id,
-                                                   "VERSION_ID", &version_id,
+                                                   "SYSEXT_ID", &sysext_id,
+                                                   "SYSEXT_VERSION_ID", &sysext_version_id,
+                                                   "SYSEXT_BUILD_ID", &build_id,
+                                                   "SYSEXT_IMAGE_ID", &image_id,
+                                                   "SYSEXT_IMAGE_VERSION", &image_version,
+                                                   "SYSEXT_PRETTY_NAME", &pretty_os,
                                                    "SYSEXT_SCOPE", &sysext_scope,
                                                    "SYSEXT_LEVEL", &sysext_level,
+                                                   "ID", &id,
+                                                   "VERSION_ID", &version_id,
                                                    "PORTABLE_PRETTY_NAME", &pretty_portable,
-                                                   "PORTABLE_PREFIXES", &portable_prefixes,
-                                                   "PRETTY_NAME", &pretty_os);
+                                                   "PORTABLE_PREFIXES", &portable_prefixes);
                                 if (r < 0)
                                         return log_error_errno(r, "Failed to parse extension release from '%s': %m", name);
 
                                 printf("Extension:\n\t%s\n"
                                        "\tExtension Scope:\n\t\t%s\n"
                                        "\tExtension Compatibility Level:\n\t\t%s\n"
+                                       "\tExtension Compatibility OS:\n\t\t%s\n"
+                                       "\tExtension Compatibility OS Version:\n\t\t%s\n"
                                        "\tPortable Service:\n\t\t%s\n"
                                        "\tPortable Prefixes:\n\t\t%s\n"
-                                       "\tOperating System:\n\t\t%s (%s %s)\n",
+                                       "\tExtension Image:\n\t\t%s%s%s %s%s%s\n",
                                        name,
                                        strna(sysext_scope),
                                        strna(sysext_level),
+                                       strna(id),
+                                       strna(version_id),
                                        strna(pretty_portable),
                                        strna(portable_prefixes),
-                                       strna(pretty_os),
-                                       strna(id),
-                                       strna(version_id));
+                                       strempty(pretty_os),
+                                       pretty_os ? " (" : "ID: ",
+                                       strna(sysext_id ?: image_id),
+                                       pretty_os ? "" : "Version: ",
+                                       strna(sysext_version_id ?: image_version ?: build_id),
+                                       pretty_os ? ")" : "");
                         }
 
                         r = sd_bus_message_exit_container(reply);
@@ -543,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)
@@ -584,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 6c4cb6ec9de8acf96011a71a1a5326a92730f294..5e46d86b08c902a45c8ee1877e42d7fea2042122 100644 (file)
@@ -60,7 +60,7 @@ int bus_image_common_get_os_release(
                 return 1;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image);
+                r = image_read_metadata(image, &image_policy_service);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -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)
@@ -163,6 +163,7 @@ int bus_image_common_get_metadata(
                         image->path,
                         matches,
                         extension_images,
+                        /* image_policy= */ NULL,
                         flags,
                         &os_release,
                         &extension_releases,
@@ -316,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;
@@ -385,18 +388,15 @@ int bus_image_common_attach(
                         matches,
                         profile,
                         extension_images,
+                        /* image_policy= */ NULL,
                         flags,
                         &changes,
                         &n_changes,
                         error);
         if (r < 0)
-                goto finish;
-
-        r = reply_portable_changes(message, changes, n_changes);
+                return r;
 
-finish:
-        portable_changes_free(changes, n_changes);
-        return r;
+        return reply_portable_changes(message, changes, n_changes);
 }
 
 static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -418,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)
@@ -470,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(
@@ -648,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;
@@ -721,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),
@@ -729,12 +731,13 @@ int bus_image_common_reattach(
                         matches,
                         profile,
                         extension_images,
+                        /* image_policy= */ NULL,
                         flags,
                         &changes_attached,
                         &n_changes_attached,
                         error);
         if (r < 0)
-                goto finish;
+                return r;
 
         /* We want to return the list of units really removed by the detach,
          * and not added again by the attach */
@@ -742,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 fa6d6ec0e9c3d8adf99c444107a7bba344ca3b24..815503c6e7f8cf8d2f4261000625ff2a34bcd215 100644 (file)
@@ -152,7 +152,7 @@ static int move_file(PStoreEntry *pe, const char *subdir1, const char *subdir2)
                 r = mkdir_parents(ofd_path, 0755);
                 if (r < 0)
                         return log_error_errno(r, "Failed to create directory %s: %m", ofd_path);
-                r = copy_file_atomic(ifd_path, ofd_path, 0600, 0, 0, COPY_REPLACE);
+                r = copy_file_atomic(ifd_path, ofd_path, 0600, COPY_REPLACE);
                 if (r < 0)
                         return log_error_errno(r, "Failed to copy_file_atomic: %s to %s", ifd_path, ofd_path);
         }
index 505e3e7ba940731fb7b4351c8726d9107ad072ed..e4d3e94c68379e6e39da92e1ab43d2a0de71fcae 100644 (file)
@@ -55,8 +55,8 @@ assert_cc(sizeof(DnsPacketHeader) == 12);
 /* RFC 1035 say 512 is the maximum, for classic unicast DNS */
 #define DNS_PACKET_UNICAST_SIZE_MAX 512u
 
-/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */
-#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096u
+/* With EDNS0 we can use larger packets, default to 1232, which is what is commonly used */
+#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 1232u
 
 struct DnsPacket {
         unsigned n_ref;
index c95875ec1012c7eaddfaeeef12e7e3f643b23672..45f1d3631196564bc5d61674e3dca19400b10d77 100644 (file)
@@ -556,6 +556,9 @@ static DnsScopeMatch match_subnet_reverse_lookups(
         if (s->family != AF_UNSPEC && f != s->family)
                 return _DNS_SCOPE_MATCH_INVALID; /* Don't look for IPv4 addresses on LLMNR/mDNS over IPv6 and vice versa */
 
+        if (in_addr_is_null(f, &ia))
+                return DNS_SCOPE_NO;
+
         LIST_FOREACH(addresses, a, s->link->addresses) {
 
                 if (a->family != f)
@@ -569,6 +572,10 @@ static DnsScopeMatch match_subnet_reverse_lookups(
                 if (a->prefixlen == UCHAR_MAX) /* don't know subnet mask */
                         continue;
 
+                /* Don't send mDNS queries for the IPv4 broadcast address */
+                if (f == AF_INET && in_addr_equal(f, &a->in_addr_broadcast, &ia) > 0)
+                        return DNS_SCOPE_NO;
+
                 /* Check if the address is in the local subnet */
                 r = in_addr_prefix_covers(f, &a->in_addr, a->prefixlen, &ia);
                 if (r < 0)
index 0a10a0d17e50ca98e04e0d1fb6534fc27437e323..85e4762ba2b93d2e923370b412a2fd5909e61016 100644 (file)
@@ -147,7 +147,7 @@ static int dns_stream_identify(DnsStream *s) {
                         switch (cmsg->cmsg_type) {
 
                         case IPV6_PKTINFO: {
-                                struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+                                struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
 
                                 if (s->ifindex <= 0)
                                         s->ifindex = i->ipi6_ifindex;
@@ -155,7 +155,7 @@ static int dns_stream_identify(DnsStream *s) {
                         }
 
                         case IPV6_HOPLIMIT:
-                                s->ttl = *(int *) CMSG_DATA(cmsg);
+                                s->ttl = *CMSG_TYPED_DATA(cmsg, int);
                                 break;
                         }
 
@@ -165,7 +165,7 @@ static int dns_stream_identify(DnsStream *s) {
                         switch (cmsg->cmsg_type) {
 
                         case IP_PKTINFO: {
-                                struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
+                                struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
 
                                 if (s->ifindex <= 0)
                                         s->ifindex = i->ipi_ifindex;
@@ -173,7 +173,7 @@ static int dns_stream_identify(DnsStream *s) {
                         }
 
                         case IP_TTL:
-                                s->ttl = *(int *) CMSG_DATA(cmsg);
+                                s->ttl = *CMSG_TYPED_DATA(cmsg, int);
                                 break;
                         }
                 }
index cf0e2be5fa0b86aff1c7a5829d6e1f3a6227f4aa..6acae48c2ba2e947ffaa73ac000df066844f8e2b 100644 (file)
@@ -285,6 +285,8 @@ int etc_hosts_parse(EtcHosts *hosts, FILE *f) {
         unsigned nr = 0;
         int r;
 
+        assert(hosts);
+
         for (;;) {
                 _cleanup_free_ char *line = NULL;
                 char *l;
@@ -313,8 +315,7 @@ int etc_hosts_parse(EtcHosts *hosts, FILE *f) {
         strip_localhost(&t);
 
         etc_hosts_clear(hosts);
-        *hosts = t;
-        t = (EtcHosts) {}; /* prevent cleanup */
+        *hosts = TAKE_STRUCT(t);
         return 0;
 }
 
index 70e726e417a9cde74e51e972f767508015cda4eb..9385b75e4b400cc33c1a72026572405737d18432 100644 (file)
@@ -816,7 +816,11 @@ ResolveSupport link_get_mdns_support(Link *link) {
         return MIN(link->mdns_support, link->manager->mdns_support);
 }
 
-int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) {
+int link_address_new(Link *l,
+                LinkAddress **ret,
+                int family,
+                const union in_addr_union *in_addr,
+                const union in_addr_union *in_addr_broadcast) {
         LinkAddress *a;
 
         assert(l);
@@ -829,6 +833,7 @@ int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr
         *a = (LinkAddress) {
                 .family = family,
                 .in_addr = *in_addr,
+                .in_addr_broadcast = *in_addr_broadcast,
                 .link = l,
                 .prefixlen = UCHAR_MAX,
         };
index d2043a10008050b0f84d423760d9257b3de911cc..0695a6ff838b9c0fcf5e0663efbc9398260baa31 100644 (file)
@@ -26,6 +26,7 @@ struct LinkAddress {
 
         int family;
         union in_addr_union in_addr;
+        union in_addr_union in_addr_broadcast;
         unsigned char prefixlen;
 
         unsigned char flags, scope;
@@ -111,7 +112,11 @@ int link_save_user(Link *l);
 int link_load_user(Link *l);
 void link_remove_user(Link *l);
 
-int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr);
+int link_address_new(Link *l,
+                LinkAddress **ret,
+                int family,
+                const union in_addr_union *in_addr,
+                const union in_addr_union *in_addr_broadcast);
 LinkAddress *link_address_free(LinkAddress *a);
 int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m);
 bool link_address_relevant(LinkAddress *l, bool local_multicast);
index ec854774af28195f78583ce583e689d329c0b79b..184d8e3f3d33982123a4c415edd65c0daf8d7c29 100644 (file)
@@ -106,7 +106,7 @@ fail:
 
 static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) {
         Manager *m = ASSERT_PTR(userdata);
-        union in_addr_union address;
+        union in_addr_union address, broadcast = {};
         uint16_t type;
         int r, ifindex, family;
         LinkAddress *a;
@@ -134,6 +134,7 @@ static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, voi
         switch (family) {
 
         case AF_INET:
+                sd_netlink_message_read_in_addr(mm, IFA_BROADCAST, &broadcast.in);
                 r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in);
                 if (r < 0) {
                         r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in);
@@ -164,7 +165,7 @@ static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, voi
         case RTM_NEWADDR:
 
                 if (!a) {
-                        r = link_address_new(l, &a, family, &address);
+                        r = link_address_new(l, &a, family, &address, &broadcast);
                         if (r < 0)
                                 return r;
                 }
@@ -833,7 +834,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
                         switch (cmsg->cmsg_type) {
 
                         case IPV6_PKTINFO: {
-                                struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+                                struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
 
                                 if (p->ifindex <= 0)
                                         p->ifindex = i->ipi6_ifindex;
@@ -843,11 +844,11 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
                         }
 
                         case IPV6_HOPLIMIT:
-                                p->ttl = *(int *) CMSG_DATA(cmsg);
+                                p->ttl = *CMSG_TYPED_DATA(cmsg, int);
                                 break;
 
                         case IPV6_RECVFRAGSIZE:
-                                p->fragsize = *(int *) CMSG_DATA(cmsg);
+                                p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
                                 break;
                         }
                 } else if (cmsg->cmsg_level == IPPROTO_IP) {
@@ -856,7 +857,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
                         switch (cmsg->cmsg_type) {
 
                         case IP_PKTINFO: {
-                                struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
+                                struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
 
                                 if (p->ifindex <= 0)
                                         p->ifindex = i->ipi_ifindex;
@@ -866,11 +867,11 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
                         }
 
                         case IP_TTL:
-                                p->ttl = *(int *) CMSG_DATA(cmsg);
+                                p->ttl = *CMSG_TYPED_DATA(cmsg, int);
                                 break;
 
                         case IP_RECVFRAGSIZE:
-                                p->fragsize = *(int *) CMSG_DATA(cmsg);
+                                p->fragsize = *CMSG_TYPED_DATA(cmsg, int);
                                 break;
                         }
                 }
@@ -1016,7 +1017,7 @@ static int manager_ipv4_send(
                 cmsg->cmsg_level = IPPROTO_IP;
                 cmsg->cmsg_type = IP_PKTINFO;
 
-                pi = (struct in_pktinfo*) CMSG_DATA(cmsg);
+                pi = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
                 pi->ipi_ifindex = ifindex;
 
                 if (source)
@@ -1072,7 +1073,7 @@ static int manager_ipv6_send(
                 cmsg->cmsg_level = IPPROTO_IPV6;
                 cmsg->cmsg_type = IPV6_PKTINFO;
 
-                pi = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+                pi = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo);
                 pi->ipi6_ifindex = ifindex;
 
                 if (source)
index 8377c2e8cdd0a4bbc60670b9ebab3d17b62e6f19..41570bf12ebbea796bf373d8127ab37f267ac712 100644 (file)
@@ -45,6 +45,7 @@ static const char *arg_unit = NULL;
 static const char *arg_description = NULL;
 static const char *arg_slice = NULL;
 static bool arg_slice_inherit = false;
+static bool arg_expand_environment = true;
 static bool arg_send_sighup = false;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 static const char *arg_host = NULL;
@@ -102,6 +103,7 @@ static int help(void) {
                "     --description=TEXT           Description for unit\n"
                "     --slice=SLICE                Run in the specified slice\n"
                "     --slice-inherit              Inherit the slice\n"
+               "     --expand-environment=BOOL    Control expansion of environment variables\n"
                "     --no-block                   Do not wait until operation finished\n"
                "  -r --remain-after-exit          Leave service around until explicitly stopped\n"
                "     --wait                       Wait until service stopped again\n"
@@ -168,6 +170,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_DESCRIPTION,
                 ARG_SLICE,
                 ARG_SLICE_INHERIT,
+                ARG_EXPAND_ENVIRONMENT,
                 ARG_SEND_SIGHUP,
                 ARG_SERVICE_TYPE,
                 ARG_EXEC_USER,
@@ -192,47 +195,48 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         static const struct option options[] = {
-                { "help",              no_argument,       NULL, 'h'                   },
-                { "version",           no_argument,       NULL, ARG_VERSION           },
-                { "user",              no_argument,       NULL, ARG_USER              },
-                { "system",            no_argument,       NULL, ARG_SYSTEM            },
-                { "scope",             no_argument,       NULL, ARG_SCOPE             },
-                { "unit",              required_argument, NULL, 'u'                   },
-                { "description",       required_argument, NULL, ARG_DESCRIPTION       },
-                { "slice",             required_argument, NULL, ARG_SLICE             },
-                { "slice-inherit",     no_argument,       NULL, ARG_SLICE_INHERIT     },
-                { "remain-after-exit", no_argument,       NULL, 'r'                   },
-                { "send-sighup",       no_argument,       NULL, ARG_SEND_SIGHUP       },
-                { "host",              required_argument, NULL, 'H'                   },
-                { "machine",           required_argument, NULL, 'M'                   },
-                { "service-type",      required_argument, NULL, ARG_SERVICE_TYPE      },
-                { "wait",              no_argument,       NULL, ARG_WAIT              },
-                { "uid",               required_argument, NULL, ARG_EXEC_USER         },
-                { "gid",               required_argument, NULL, ARG_EXEC_GROUP        },
-                { "nice",              required_argument, NULL, ARG_NICE              },
-                { "setenv",            required_argument, NULL, 'E'                   },
-                { "property",          required_argument, NULL, 'p'                   },
-                { "tty",               no_argument,       NULL, 't'                   }, /* deprecated alias */
-                { "pty",               no_argument,       NULL, 't'                   },
-                { "pipe",              no_argument,       NULL, 'P'                   },
-                { "quiet",             no_argument,       NULL, 'q'                   },
-                { "on-active",         required_argument, NULL, ARG_ON_ACTIVE         },
-                { "on-boot",           required_argument, NULL, ARG_ON_BOOT           },
-                { "on-startup",        required_argument, NULL, ARG_ON_STARTUP        },
-                { "on-unit-active",    required_argument, NULL, ARG_ON_UNIT_ACTIVE    },
-                { "on-unit-inactive",  required_argument, NULL, ARG_ON_UNIT_INACTIVE  },
-                { "on-calendar",       required_argument, NULL, ARG_ON_CALENDAR       },
-                { "on-timezone-change",no_argument,       NULL, ARG_ON_TIMEZONE_CHANGE},
-                { "on-clock-change",   no_argument,       NULL, ARG_ON_CLOCK_CHANGE   },
-                { "timer-property",    required_argument, NULL, ARG_TIMER_PROPERTY    },
-                { "path-property",     required_argument, NULL, ARG_PATH_PROPERTY     },
-                { "socket-property",   required_argument, NULL, ARG_SOCKET_PROPERTY   },
-                { "no-block",          no_argument,       NULL, ARG_NO_BLOCK          },
-                { "no-ask-password",   no_argument,       NULL, ARG_NO_ASK_PASSWORD   },
-                { "collect",           no_argument,       NULL, 'G'                   },
-                { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
-                { "same-dir",          no_argument,       NULL, 'd'                   },
-                { "shell",             no_argument,       NULL, 'S'                   },
+                { "help",               no_argument,       NULL, 'h'                    },
+                { "version",            no_argument,       NULL, ARG_VERSION            },
+                { "user",               no_argument,       NULL, ARG_USER               },
+                { "system",             no_argument,       NULL, ARG_SYSTEM             },
+                { "scope",              no_argument,       NULL, ARG_SCOPE              },
+                { "unit",               required_argument, NULL, 'u'                    },
+                { "description",        required_argument, NULL, ARG_DESCRIPTION        },
+                { "slice",              required_argument, NULL, ARG_SLICE              },
+                { "slice-inherit",      no_argument,       NULL, ARG_SLICE_INHERIT      },
+                { "remain-after-exit",  no_argument,       NULL, 'r'                    },
+                { "expand-environment", required_argument, NULL, ARG_EXPAND_ENVIRONMENT },
+                { "send-sighup",        no_argument,       NULL, ARG_SEND_SIGHUP        },
+                { "host",               required_argument, NULL, 'H'                    },
+                { "machine",            required_argument, NULL, 'M'                    },
+                { "service-type",       required_argument, NULL, ARG_SERVICE_TYPE       },
+                { "wait",               no_argument,       NULL, ARG_WAIT               },
+                { "uid",                required_argument, NULL, ARG_EXEC_USER          },
+                { "gid",                required_argument, NULL, ARG_EXEC_GROUP         },
+                { "nice",               required_argument, NULL, ARG_NICE               },
+                { "setenv",             required_argument, NULL, 'E'                    },
+                { "property",           required_argument, NULL, 'p'                    },
+                { "tty",                no_argument,       NULL, 't'                    }, /* deprecated alias */
+                { "pty",                no_argument,       NULL, 't'                    },
+                { "pipe",               no_argument,       NULL, 'P'                    },
+                { "quiet",              no_argument,       NULL, 'q'                    },
+                { "on-active",          required_argument, NULL, ARG_ON_ACTIVE          },
+                { "on-boot",            required_argument, NULL, ARG_ON_BOOT            },
+                { "on-startup",         required_argument, NULL, ARG_ON_STARTUP         },
+                { "on-unit-active",     required_argument, NULL, ARG_ON_UNIT_ACTIVE     },
+                { "on-unit-inactive",   required_argument, NULL, ARG_ON_UNIT_INACTIVE   },
+                { "on-calendar",        required_argument, NULL, ARG_ON_CALENDAR        },
+                { "on-timezone-change", no_argument,       NULL, ARG_ON_TIMEZONE_CHANGE },
+                { "on-clock-change",    no_argument,       NULL, ARG_ON_CLOCK_CHANGE    },
+                { "timer-property",     required_argument, NULL, ARG_TIMER_PROPERTY     },
+                { "path-property",      required_argument, NULL, ARG_PATH_PROPERTY      },
+                { "socket-property",    required_argument, NULL, ARG_SOCKET_PROPERTY    },
+                { "no-block",           no_argument,       NULL, ARG_NO_BLOCK           },
+                { "no-ask-password",    no_argument,       NULL, ARG_NO_ASK_PASSWORD    },
+                { "collect",            no_argument,       NULL, 'G'                    },
+                { "working-directory",  required_argument, NULL, ARG_WORKING_DIRECTORY  },
+                { "same-dir",           no_argument,       NULL, 'd'                    },
+                { "shell",              no_argument,       NULL, 'S'                    },
                 {},
         };
 
@@ -242,6 +246,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) {
@@ -284,6 +291,12 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_slice_inherit = true;
                         break;
 
+                case ARG_EXPAND_ENVIRONMENT:
+                        r = parse_boolean_argument("--expand-environment=", optarg, &arg_expand_environment);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_SEND_SIGHUP:
                         arg_send_sighup = true;
                         break;
@@ -716,6 +729,11 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
         bool send_term = false;
         int r;
 
+        /* We disable environment expansion on the server side via ExecStartEx=:.
+         * ExecStartEx was added relatively recently (v243), and some bugs were fixed only later.
+         * So use that feature only if required. It will fail with older systemds. */
+        bool use_ex_prop = !arg_expand_environment;
+
         assert(m);
 
         r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property);
@@ -847,19 +865,23 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_append(m, "s", "ExecStart");
+                r = sd_bus_message_append(m, "s",
+                                          use_ex_prop ? "ExecStartEx" : "ExecStart");
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_open_container(m, 'v', "a(sasb)");
+                r = sd_bus_message_open_container(m, 'v',
+                                                  use_ex_prop ? "a(sasas)" : "a(sasb)");
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_open_container(m, 'a', "(sasb)");
+                r = sd_bus_message_open_container(m, 'a',
+                                                  use_ex_prop ? "(sasas)" : "(sasb)");
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_open_container(m, 'r', "sasb");
+                r = sd_bus_message_open_container(m, 'r',
+                                                  use_ex_prop ? "sasas" : "sasb");
                 if (r < 0)
                         return bus_log_create_error(r);
 
@@ -871,7 +893,12 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_append(m, "b", false);
+                if (use_ex_prop)
+                        r = sd_bus_message_append_strv(
+                                        m,
+                                        STRV_MAKE(arg_expand_environment ? NULL : "no-env-expand"));
+                else
+                        r = sd_bus_message_append(m, "b", false);
                 if (r < 0)
                         return bus_log_create_error(r);
 
@@ -1109,10 +1136,78 @@ static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
         return 0;
 }
 
-static int start_transient_service(
+static int make_transient_service_unit(
+                sd_bus *bus,
+                sd_bus_message **message,
+                const char *service,
+                const char *pty_path) {
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+        int r;
+
+        assert(bus);
+        assert(message);
+        assert(service);
+
+        r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* Name and mode */
+        r = sd_bus_message_append(m, "ss", service, "fail");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* Properties */
+        r = sd_bus_message_open_container(m, 'a', "(sv)");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = transient_service_set_properties(m, pty_path);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_close_container(m);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* Auxiliary units */
+        r = sd_bus_message_append(m, "a(sa(sv))", 0);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        *message = TAKE_PTR(m);
+        return 0;
+}
+
+static int bus_call_with_hint(
                 sd_bus *bus,
-                int *retval) {
+                sd_bus_message *message,
+                const char *name,
+                sd_bus_message **reply) {
+
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
 
+        r = sd_bus_call(bus, message, 0, &error, reply);
+        if (r < 0) {
+                log_error_errno(r, "Failed to start transient %s unit: %s", name, bus_error_message(&error, r));
+
+                if (!arg_expand_environment &&
+                    sd_bus_error_has_names(&error,
+                                           SD_BUS_ERROR_UNKNOWN_PROPERTY,
+                                           SD_BUS_ERROR_PROPERTY_READ_ONLY))
+                        log_notice_errno(r, "Hint: --expand-environment=no is not supported by old systemd");
+        }
+
+        return r;
+}
+
+static int start_transient_service(sd_bus *bus) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
@@ -1121,7 +1216,6 @@ static int start_transient_service(
         int r;
 
         assert(bus);
-        assert(retval);
 
         if (arg_stdio == ARG_STDIO_PTY) {
 
@@ -1194,42 +1288,15 @@ static int start_transient_service(
                         return r;
         }
 
-        r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        /* Name and mode */
-        r = sd_bus_message_append(m, "ss", service, "fail");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        /* Properties */
-        r = sd_bus_message_open_container(m, 'a', "(sv)");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        r = transient_service_set_properties(m, pty_path);
+        r = make_transient_service_unit(bus, &m, service, pty_path);
         if (r < 0)
                 return r;
 
-        r = sd_bus_message_close_container(m);
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        /* Auxiliary units */
-        r = sd_bus_message_append(m, "a(sa(sv))", 0);
-        if (r < 0)
-                return bus_log_create_error(r);
-
         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = sd_bus_call(bus, m, 0, &error, &reply);
+        r = bus_call_with_hint(bus, m, "service", &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r));
+                return r;
 
         if (w) {
                 const char *object;
@@ -1360,16 +1427,15 @@ static int start_transient_service(
                 /* Try to propagate the service's return value. But if the service defines
                  * e.g. SuccessExitStatus, honour this, and return 0 to mean "success". */
                 if (streq_ptr(c.result, "success"))
-                        *retval = 0;
-                else if (streq_ptr(c.result, "exit-code") && c.exit_status > 0)
-                        *retval = c.exit_status;
-                else if (streq_ptr(c.result, "signal"))
-                        *retval = EXIT_EXCEPTION;
-                else
-                        *retval = EXIT_FAILURE;
+                        return EXIT_SUCCESS;
+                if (streq_ptr(c.result, "exit-code") && c.exit_status > 0)
+                        return c.exit_status;
+                if (streq_ptr(c.result, "signal"))
+                        return EXIT_EXCEPTION;
+                return EXIT_FAILURE;
         }
 
-        return 0;
+        return EXIT_SUCCESS;
 }
 
 static int acquire_invocation_id(sd_bus *bus, sd_id128_t *ret) {
@@ -1408,7 +1474,7 @@ static int start_transient_scope(sd_bus *bus) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
-        _cleanup_strv_free_ char **env = NULL, **user_env = NULL;
+        _cleanup_strv_free_ char **env = NULL, **user_env = NULL, **expanded_cmdline = NULL;
         _cleanup_free_ char *scope = NULL;
         const char *object = NULL;
         sd_id128_t invocation_id;
@@ -1550,75 +1616,33 @@ static int start_transient_scope(sd_bus *bus) {
         if (!arg_quiet)
                 log_info("Running scope as unit: %s", scope);
 
+        if (arg_expand_environment) {
+                expanded_cmdline = replace_env_argv(arg_cmdline, env);
+                if (!expanded_cmdline)
+                        return log_oom();
+                arg_cmdline = expanded_cmdline;
+        }
+
         execvpe(arg_cmdline[0], arg_cmdline, env);
 
         return log_error_errno(errno, "Failed to execute: %m");
 }
 
-static int start_transient_trigger(
+static int make_transient_trigger_unit(
                 sd_bus *bus,
-                const char *suffix) {
+                sd_bus_message **message,
+                const char *suffix,
+                const char *trigger,
+                const char *service) {
 
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
-        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
-        _cleanup_free_ char *trigger = NULL, *service = NULL;
-        const char *object = NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
         int r;
 
         assert(bus);
-
-        r = bus_wait_for_jobs_new(bus, &w);
-        if (r < 0)
-                return log_oom();
-
-        if (arg_unit) {
-                switch (unit_name_to_type(arg_unit)) {
-
-                case UNIT_SERVICE:
-                        service = strdup(arg_unit);
-                        if (!service)
-                                return log_oom();
-
-                        r = unit_name_change_suffix(service, suffix, &trigger);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to change unit suffix: %m");
-                        break;
-
-                case UNIT_TIMER:
-                        trigger = strdup(arg_unit);
-                        if (!trigger)
-                                return log_oom();
-
-                        r = unit_name_change_suffix(trigger, ".service", &service);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to change unit suffix: %m");
-                        break;
-
-                default:
-                        r = unit_name_mangle_with_suffix(arg_unit, "as unit",
-                                                         arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
-                                                         ".service", &service);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to mangle unit name: %m");
-
-                        r = unit_name_mangle_with_suffix(arg_unit, "as trigger",
-                                                         arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
-                                                         suffix, &trigger);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to mangle unit name: %m");
-
-                        break;
-                }
-        } else {
-                r = make_unit_name(bus, UNIT_SERVICE, &service);
-                if (r < 0)
-                        return r;
-
-                r = unit_name_change_suffix(service, suffix, &trigger);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to change unit suffix: %m");
-        }
+        assert(message);
+        assert(suffix);
+        assert(trigger);
+        assert(service);
 
         r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
         if (r < 0)
@@ -1687,11 +1711,81 @@ static int start_transient_trigger(
         if (r < 0)
                 return bus_log_create_error(r);
 
+        *message = TAKE_PTR(m);
+        return 0;
+}
+
+static int start_transient_trigger(sd_bus *bus, const char *suffix) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+        _cleanup_free_ char *trigger = NULL, *service = NULL;
+        const char *object = NULL;
+        int r;
+
+        assert(bus);
+        assert(suffix);
+
+        r = bus_wait_for_jobs_new(bus, &w);
+        if (r < 0)
+                return log_oom();
+
+        if (arg_unit) {
+                switch (unit_name_to_type(arg_unit)) {
+
+                case UNIT_SERVICE:
+                        service = strdup(arg_unit);
+                        if (!service)
+                                return log_oom();
+
+                        r = unit_name_change_suffix(service, suffix, &trigger);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to change unit suffix: %m");
+                        break;
+
+                case UNIT_TIMER:
+                        trigger = strdup(arg_unit);
+                        if (!trigger)
+                                return log_oom();
+
+                        r = unit_name_change_suffix(trigger, ".service", &service);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to change unit suffix: %m");
+                        break;
+
+                default:
+                        r = unit_name_mangle_with_suffix(arg_unit, "as unit",
+                                                         arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
+                                                         ".service", &service);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to mangle unit name: %m");
+
+                        r = unit_name_mangle_with_suffix(arg_unit, "as trigger",
+                                                         arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN,
+                                                         suffix, &trigger);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to mangle unit name: %m");
+
+                        break;
+                }
+        } else {
+                r = make_unit_name(bus, UNIT_SERVICE, &service);
+                if (r < 0)
+                        return r;
+
+                r = unit_name_change_suffix(service, suffix, &trigger);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to change unit suffix: %m");
+        }
+
+        r = make_transient_trigger_unit(bus, &m, suffix, trigger, service);
+        if (r < 0)
+                return r;
+
         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = sd_bus_call(bus, m, 0, &error, &reply);
+        r = bus_call_with_hint(bus, m, suffix + 1, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to start transient %s unit: %s", suffix + 1, bus_error_message(&error, r));
+                return r;
 
         r = sd_bus_message_read(reply, "o", &object);
         if (r < 0)
@@ -1707,7 +1801,7 @@ static int start_transient_trigger(
                         log_info("Will run service as unit: %s", service);
         }
 
-        return 0;
+        return EXIT_SUCCESS;
 }
 
 static bool shall_make_executable_absolute(void) {
@@ -1726,7 +1820,7 @@ static bool shall_make_executable_absolute(void) {
 static int run(int argc, char* argv[]) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_free_ char *description = NULL;
-        int r, retval = EXIT_SUCCESS;
+        int r;
 
         log_show_color(true);
         log_parse_environment();
@@ -1773,19 +1867,14 @@ static int run(int argc, char* argv[]) {
                 return bus_log_connect_error(r, arg_transport);
 
         if (arg_scope)
-                r = start_transient_scope(bus);
-        else if (arg_path_property)
-                r = start_transient_trigger(bus, ".path");
-        else if (arg_socket_property)
-                r = start_transient_trigger(bus, ".socket");
-        else if (arg_with_timer)
-                r = start_transient_trigger(bus, ".timer");
-        else
-                r = start_transient_service(bus, &retval);
-        if (r < 0)
-                return r;
-
-        return retval;
+                return start_transient_scope(bus);
+        if (arg_path_property)
+                return start_transient_trigger(bus, ".path");
+        if (arg_socket_property)
+                return start_transient_trigger(bus, ".socket");
+        if (arg_with_timer)
+                return start_transient_trigger(bus, ".timer");
+        return start_transient_service(bus);
 }
 
 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c
new file mode 100644 (file)
index 0000000..0595ac6
--- /dev/null
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "boot-entry.h"
+#include "chase.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "id128-util.h"
+#include "os-util.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "utf8.h"
+
+bool boot_entry_token_valid(const char *p) {
+        return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p);
+}
+
+static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType *type, char **token) {
+        _cleanup_free_ char *buf = NULL, *p = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(type);
+        assert(*type == BOOT_ENTRY_TOKEN_AUTO);
+        assert(token);
+
+        if (!etc_kernel)
+                return 0;
+
+        p = path_join(etc_kernel, "entry-token");
+        if (!p)
+                return log_oom();
+
+        r = chase_and_fopenat_unlocked(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to chase and open '%s': %m", p);
+
+        r = read_line(f, NAME_MAX, &buf);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read %s: %m", p);
+
+        if (isempty(buf))
+                return 0;
+
+        if (!boot_entry_token_valid(buf)) {
+                log_debug("Invalid entry token specified in %s, ignoring.", p);
+                return 0;
+        }
+
+        *token = TAKE_PTR(buf);
+        *type = BOOT_ENTRY_TOKEN_LITERAL;
+        return 1;
+}
+
+static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) {
+        char *p;
+
+        assert(type);
+        assert(IN_SET(*type, BOOT_ENTRY_TOKEN_AUTO, BOOT_ENTRY_TOKEN_MACHINE_ID));
+        assert(token);
+
+        if (sd_id128_is_null(machine_id))
+                return 0;
+
+        p = strdup(SD_ID128_TO_STRING(machine_id));
+        if (!p)
+                return log_oom();
+
+        *token = p;
+        *type = BOOT_ENTRY_TOKEN_MACHINE_ID;
+        return 1;
+}
+
+static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char **token) {
+        _cleanup_free_ char *id = NULL, *image_id = NULL;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(type);
+        assert(IN_SET(*type, BOOT_ENTRY_TOKEN_AUTO, BOOT_ENTRY_TOKEN_OS_IMAGE_ID, BOOT_ENTRY_TOKEN_OS_ID));
+        assert(token);
+
+        switch (*type) {
+        case BOOT_ENTRY_TOKEN_AUTO:
+                r = parse_os_release_at(rfd,
+                                        "IMAGE_ID", &image_id,
+                                        "ID",       &id);
+                break;
+
+        case BOOT_ENTRY_TOKEN_OS_IMAGE_ID:
+                r = parse_os_release_at(rfd, "IMAGE_ID", &image_id);
+                break;
+
+        case BOOT_ENTRY_TOKEN_OS_ID:
+                r = parse_os_release_at(rfd, "ID", &id);
+                break;
+
+        default:
+                assert_not_reached();
+        }
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to load /etc/os-release: %m");
+
+        if (!isempty(image_id) && boot_entry_token_valid(image_id)) {
+                *token = TAKE_PTR(image_id);
+                *type = BOOT_ENTRY_TOKEN_OS_IMAGE_ID;
+                return 1;
+        }
+
+        if (!isempty(id) && boot_entry_token_valid(id)) {
+                *token = TAKE_PTR(id);
+                *type = BOOT_ENTRY_TOKEN_OS_ID;
+                return 1;
+        }
+
+        return 0;
+}
+
+int boot_entry_token_ensure_at(
+                int rfd,
+                const char *etc_kernel,
+                sd_id128_t machine_id,
+                bool machine_id_is_random,
+                BootEntryTokenType *type,
+                char **token) {
+
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(type);
+        assert(token);
+
+        if (*token)
+                return 0; /* Already set. */
+
+        switch (*type) {
+
+        case BOOT_ENTRY_TOKEN_AUTO:
+                r = entry_token_load(rfd, etc_kernel, type, token);
+                if (r != 0)
+                        return r;
+
+                if (!machine_id_is_random) {
+                        r = entry_token_from_machine_id(machine_id, type, token);
+                        if (r != 0)
+                                return r;
+                }
+
+                r = entry_token_from_os_release(rfd, type, token);
+                if (r != 0)
+                        return r;
+
+                if (machine_id_is_random) {
+                        r = entry_token_from_machine_id(machine_id, type, token);
+                        if (r != 0)
+                                return r;
+                }
+
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "No machine ID set, and /etc/os-release carries no ID=/IMAGE_ID= fields.");
+
+        case BOOT_ENTRY_TOKEN_MACHINE_ID:
+                r = entry_token_from_machine_id(machine_id, type, token);
+                if (r != 0)
+                        return r;
+
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set.");
+
+        case BOOT_ENTRY_TOKEN_OS_IMAGE_ID:
+                r = entry_token_from_os_release(rfd, type, token);
+                if (r != 0)
+                        return r;
+
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "IMAGE_ID= field not set in /etc/os-release.");
+
+        case BOOT_ENTRY_TOKEN_OS_ID:
+                r = entry_token_from_os_release(rfd, type, token);
+                if (r != 0)
+                        return r;
+
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ID= field not set in /etc/os-release.");
+
+        case BOOT_ENTRY_TOKEN_LITERAL:
+                /* In this case, the token should be already set by the user input. */
+                return -EINVAL;
+
+        default:
+                assert_not_reached();
+        }
+}
+
+int boot_entry_token_ensure(
+                const char *root,
+                const char *etc_kernel,
+                sd_id128_t machine_id,
+                bool machine_id_is_random,
+                BootEntryTokenType *type,
+                char **token) {
+
+        assert(token);
+
+        if (*token)
+                return 0; /* Already set. */
+
+        _cleanup_close_ int rfd = -EBADF;
+
+        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (rfd < 0)
+                return -errno;
+
+        return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token);
+}
+
+int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) {
+        assert(s);
+        assert(type);
+        assert(token);
+
+        /*
+         * This function is intended to be used in command line parsers, to handle token that are passed in.
+         *
+         * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON SUCCESS!
+         * Hence, do not pass in uninitialized pointers.
+         */
+
+        if (streq(s, "machine-id")) {
+                *type = BOOT_ENTRY_TOKEN_MACHINE_ID;
+                *token = mfree(*token);
+                return 0;
+        }
+
+        if (streq(s, "os-image-id")) {
+                *type = BOOT_ENTRY_TOKEN_OS_IMAGE_ID;
+                *token = mfree(*token);
+                return 0;
+        }
+
+        if (streq(s, "os-id")) {
+                *type = BOOT_ENTRY_TOKEN_OS_ID;
+                *token = mfree(*token);
+                return 0;
+        }
+
+        const char *e = startswith(s, "literal:");
+        if (e) {
+                if (!boot_entry_token_valid(e))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Invalid entry token literal is specified for --entry-token=.");
+
+                *type = BOOT_ENTRY_TOKEN_LITERAL;
+                return free_and_strdup_warn(token, e);
+        }
+
+        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                               "Unexpected parameter for --entry-token=: %s", s);
+}
diff --git a/src/shared/boot-entry.h b/src/shared/boot-entry.h
new file mode 100644 (file)
index 0000000..1d20db6
--- /dev/null
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-id128.h"
+
+typedef enum BootEntryTokenType {
+        BOOT_ENTRY_TOKEN_MACHINE_ID,
+        BOOT_ENTRY_TOKEN_OS_IMAGE_ID,
+        BOOT_ENTRY_TOKEN_OS_ID,
+        BOOT_ENTRY_TOKEN_LITERAL,
+        BOOT_ENTRY_TOKEN_AUTO,
+} BootEntryTokenType;
+
+bool boot_entry_token_valid(const char *p);
+
+int boot_entry_token_ensure(
+                const char *root,
+                const char *etc_kernel,   /* will be prefixed with root, typically /etc/kernel. */
+                sd_id128_t machine_id,
+                bool machine_id_is_random,
+                BootEntryTokenType *type, /* input and output */
+                char **token);            /* output, but do not pass uninitialized value. */
+int boot_entry_token_ensure_at(
+                int rfd,
+                const char *etc_kernel,
+                sd_id128_t machine_id,
+                bool machine_id_is_random,
+                BootEntryTokenType *type,
+                char **token);
+
+int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token);
index 9dddb0e289c7955466339463e340f63a9d5e88bf..a5ae2478fe85fd853abc6a3bd7b8ee60cf741823 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "bootspec-fundamental.h"
 #include "bootspec.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "devnum-util.h"
 #include "dirent-util.h"
@@ -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);
 
@@ -380,8 +389,7 @@ static int boot_entry_load_type1(
                         return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while parsing: %m");
         }
 
-        *entry = tmp;
-        tmp = (BootEntry) {};
+        *entry = TAKE_STRUCT(tmp);
         return 0;
 }
 
@@ -507,7 +515,7 @@ static int boot_loader_read_conf_path(BootConfig *config, const char *root, cons
         assert(config);
         assert(path);
 
-        r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
+        r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
@@ -592,7 +600,7 @@ static int boot_entries_find_type1(
         assert(root);
         assert(dir);
 
-        dir_fd = chase_symlinks_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
+        dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
         if (dir_fd == -ENOENT)
                 return 0;
         if (dir_fd < 0)
@@ -735,8 +743,7 @@ static int boot_entry_load_unified(
                         return log_oom();
         }
 
-        *ret = tmp;
-        tmp = (BootEntry) {};
+        *ret = TAKE_STRUCT(tmp);
         return 0;
 }
 
@@ -854,7 +861,7 @@ static int boot_entries_find_unified(
         assert(config);
         assert(dir);
 
-        r = chase_symlinks_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
+        r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
@@ -1269,7 +1276,7 @@ static void boot_entry_file_list(
         assert(p);
         assert(ret_status);
 
-        int status = chase_symlinks_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
+        int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
 
         /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
          * the absence of color support) to the user that the boot loader is only interested in the second
@@ -1422,6 +1429,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 +1452,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 531ae9b6805e667f82aa24936a8ba2a22476bada..d5eb6f4ccbaa64347a0b4a09cffffd674f6b6df9 100644 (file)
@@ -300,8 +300,16 @@ int bpf_program_cgroup_detach(BPFProgram *p) {
         return 0;
 }
 
-int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags) {
+int bpf_map_new(
+                const char *name,
+                enum bpf_map_type type,
+                size_t key_size,
+                size_t value_size,
+                size_t max_entries,
+                uint32_t flags) {
+
         union bpf_attr attr;
+        const char *n = name;
 
         zero(attr);
         attr.map_type = type;
@@ -310,6 +318,13 @@ int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size
         attr.max_entries = max_entries;
         attr.map_flags = flags;
 
+        /* The map name is primarily informational for debugging purposes, and typically too short
+         * to carry the full unit name, hence we employ a trivial lossy escaping to make it fit
+         * (truncation + only alphanumerical, "." and "_" are allowed as per
+         * https://www.kernel.org/doc/html/next/bpf/maps.html#usage-notes) */
+        for (size_t i = 0; i < sizeof(attr.map_name) - 1 && *n; i++, n++)
+                attr.map_name[i] = strchr(ALPHANUMERICAL ".", *n) ? *n : '_';
+
         return RET_NERRNO(bpf(BPF_MAP_CREATE, &attr, sizeof(attr)));
 }
 
index b640fb9d9f655fa5c9f3c30aa179a3461604f773..0e0b666df6ff9ab8ae44a35e14f028bec04cbdeb 100644 (file)
@@ -54,7 +54,8 @@ int bpf_program_deserialize_attachment_set(const char *v, FDSet *fds, Set **bpfs
 
 extern const struct hash_ops bpf_program_hash_ops;
 
-int bpf_map_new(enum bpf_map_type type, size_t key_size, size_t value_size, size_t max_entries, uint32_t flags);
+int bpf_map_new(const char *name, enum bpf_map_type type, size_t key_size, size_t value_size,
+                size_t max_entries, uint32_t flags);
 int bpf_map_update_element(int fd, const void *key, void *value);
 int bpf_map_lookup_element(int fd, const void *key, void *value);
 
index c68405754152805e825b2b40d64434beb60d6bf0..2789364c98fe84bbcd1b3f01e49a0e94f88ebaef 100644 (file)
@@ -247,16 +247,17 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs
         return RET_NERRNO(ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args));
 }
 
-int btrfs_get_block_device_fd(int fd, dev_t *dev) {
+int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) {
         struct btrfs_ioctl_fs_info_args fsi = {};
-        _cleanup_close_ int regfd = -EBADF;
+        _cleanup_close_ int fd = -EBADF;
         uint64_t id;
         int r;
 
-        assert(fd >= 0);
-        assert(dev);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(ret);
 
-        fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, O_PATH, &regfd);
+        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, 0);
         if (fd < 0)
                 return fd;
 
@@ -271,7 +272,7 @@ int btrfs_get_block_device_fd(int fd, dev_t *dev) {
 
         /* We won't do this for btrfs RAID */
         if (fsi.num_devices != 1) {
-                *dev = 0;
+                *ret = 0;
                 return 0;
         }
 
@@ -306,26 +307,13 @@ int btrfs_get_block_device_fd(int fd, dev_t *dev) {
                 if (major(st.st_rdev) == 0)
                         return -ENODEV;
 
-                *dev = st.st_rdev;
+                *ret = st.st_rdev;
                 return 1;
         }
 
         return -ENODEV;
 }
 
-int btrfs_get_block_device(const char *path, dev_t *dev) {
-        _cleanup_close_ int fd = -EBADF;
-
-        assert(path);
-        assert(dev);
-
-        fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
-
-        return btrfs_get_block_device_fd(fd, dev);
-}
-
 int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) {
         struct btrfs_ioctl_ino_lookup_args args = {
                 .objectid = BTRFS_FIRST_FREE_OBJECTID
index 080d5a607d8d5c5e64a8deaa2f9aee85f4aee65a..75a8ed85490ec0f37fcbd9e0f048873b7e2d8e55 100644 (file)
@@ -49,8 +49,13 @@ int btrfs_is_subvol(const char *path);
 int btrfs_reflink(int infd, int outfd);
 int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz);
 
-int btrfs_get_block_device_fd(int fd, dev_t *dev);
-int btrfs_get_block_device(const char *path, dev_t *dev);
+int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret);
+static inline int btrfs_get_block_device(const char *path, dev_t *ret) {
+        return btrfs_get_block_device_at(AT_FDCWD, path, ret);
+}
+static inline int btrfs_get_block_device_fd(int fd, dev_t *ret) {
+        return btrfs_get_block_device_at(fd, "", ret);
+}
 
 int btrfs_defrag_fd(int fd);
 int btrfs_defrag(const char *p);
index 3d0887e6df72ea4b61bc8967c6f00d274676bc59..53e5d6b99f18715ad61f99e53260997bbad76937 100644 (file)
@@ -149,9 +149,9 @@ int bus_property_get_rlimit(
                 s = is_soft ? strndupa_safe(property, is_soft - property) : property;
 
                 /* Skip over any prefix, such as "Default" */
-                assert_se(p = strstr(s, "Limit"));
+                assert_se(p = strstrafter(s, "Limit"));
 
-                z = rlimit_from_string(p + 5);
+                z = rlimit_from_string(p);
                 assert(z >= 0);
 
                 (void) getrlimit(z, &buf);
index 6966cfd8389372abde0011a6f57c1e3c417abc42..ebbd1f7f28e502b8b848ac3f7577182a4ee6a14c 100644 (file)
@@ -959,7 +959,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "ProcSubset",
                               "NetworkNamespacePath",
                               "IPCNamespacePath",
-                              "LogNamespace"))
+                              "LogNamespace",
+                              "RootImagePolicy",
+                              "MountImagePolicy",
+                              "ExtensionImagePolicy"))
                 return bus_append_string(m, field, eq);
 
         if (STR_IN_SET(field, "IgnoreSIGPIPE",
@@ -2195,7 +2198,8 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con
                               "USBFunctionStrings",
                               "OOMPolicy",
                               "TimeoutStartFailureMode",
-                              "TimeoutStopFailureMode"))
+                              "TimeoutStopFailureMode",
+                              "FileDescriptorStorePreserve"))
                 return bus_append_string(m, field, eq);
 
         if (STR_IN_SET(field, "PermissionsStartOnly",
@@ -2708,14 +2712,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)
@@ -2733,7 +2736,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;
         }
@@ -2744,7 +2747,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 146293acdff700be4fb4903695d2b41463272f4f..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)) {
@@ -1293,7 +1301,8 @@ int copy_directory_full(
         return 0;
 }
 
-int copy_file_fd_full(
+int copy_file_fd_at_full(
+                int dir_fdf,
                 const char *from,
                 int fdt,
                 CopyFlags copy_flags,
@@ -1304,10 +1313,11 @@ int copy_file_fd_full(
         struct stat st;
         int r;
 
+        assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD);
         assert(from);
         assert(fdt >= 0);
 
-        fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        fdf = openat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fdf < 0)
                 return -errno;
 
@@ -1435,8 +1445,10 @@ fail:
         return r;
 }
 
-int copy_file_atomic_full(
+int copy_file_atomic_at_full(
+                int dir_fdf,
                 const char *from,
+                int dir_fdt,
                 const char *to,
                 mode_t mode,
                 unsigned chattr_flags,
@@ -1453,11 +1465,11 @@ int copy_file_atomic_full(
         assert(to);
 
         if (copy_flags & COPY_MAC_CREATE) {
-                r = mac_selinux_create_file_prepare(to, S_IFREG);
+                r = mac_selinux_create_file_prepare_at(dir_fdt, to, S_IFREG);
                 if (r < 0)
                         return r;
         }
-        fdt = open_tmpfile_linkable(to, O_WRONLY|O_CLOEXEC, &t);
+        fdt = open_tmpfile_linkable_at(dir_fdt, to, O_WRONLY|O_CLOEXEC, &t);
         if (copy_flags & COPY_MAC_CREATE)
                 mac_selinux_create_file_clear();
         if (fdt < 0)
@@ -1466,7 +1478,7 @@ int copy_file_atomic_full(
         if (chattr_mask != 0)
                 (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL);
 
-        r = copy_file_fd_full(from, fdt, copy_flags, progress_bytes, userdata);
+        r = copy_file_fd_at_full(dir_fdf, from, fdt, copy_flags, progress_bytes, userdata);
         if (r < 0)
                 return r;
 
@@ -1479,7 +1491,7 @@ int copy_file_atomic_full(
                         return -errno;
         }
 
-        r = link_tmpfile(fdt, t, to, copy_flags & COPY_REPLACE);
+        r = link_tmpfile_at(fdt, dir_fdt, t, to, copy_flags & COPY_REPLACE);
         if (r < 0)
                 return r;
 
@@ -1494,7 +1506,7 @@ int copy_file_atomic_full(
 
         if (copy_flags & COPY_FSYNC_FULL) {
                 /* Sync the parent directory */
-                r = fsync_parent_at(AT_FDCWD, to);
+                r = fsync_parent_at(dir_fdt, to);
                 if (r < 0)
                         goto fail;
         }
@@ -1502,7 +1514,7 @@ int copy_file_atomic_full(
         return 0;
 
 fail:
-        (void) unlink(to);
+        (void) unlinkat(dir_fdt, to, 0);
         return r;
 }
 
index ab2e256915c9c9c29efdd8c4f4c80287d114c957..9e67838a99a4e8fbbefe75f38a43d0b3ffc93604 100644 (file)
@@ -41,25 +41,37 @@ typedef enum DenyType {
 typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);
 typedef int (*copy_progress_path_t)(const char *path, const struct stat *st, void *userdata);
 
-int copy_file_fd_full(const char *from, int to, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
+int copy_file_fd_at_full(int dir_fdf, const char *from, int to, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
+static inline int copy_file_fd_at(int dir_fdf, const char *from, int to, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata) {
+        return copy_file_fd_at_full(dir_fdf, from, to, copy_flags, progress, userdata);
+}
+static inline int copy_file_fd_full(const char *from, int to, CopyFlags copy_flags) {
+        return copy_file_fd_at_full(AT_FDCWD, from, to, copy_flags, NULL, NULL);
+}
 static inline int copy_file_fd(const char *from, int to, CopyFlags copy_flags) {
-        return copy_file_fd_full(from, to, copy_flags, NULL, NULL);
+        return copy_file_fd_at(AT_FDCWD, from, to, copy_flags, NULL, NULL);
 }
 
 int copy_file_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
-static inline int copy_file_at(int dir_fdf, const char *from, int dir_fdt, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags) {
-        return copy_file_at_full(dir_fdf, from, dir_fdt, to, open_flags, mode, chattr_flags, chattr_mask, copy_flags, NULL, NULL);
+static inline int copy_file_at(int dir_fdf, const char *from, int dir_fdt, const char *to, int open_flags, mode_t mode, CopyFlags copy_flags) {
+        return copy_file_at_full(dir_fdf, from, dir_fdt, to, open_flags, mode, 0, 0, copy_flags, NULL, NULL);
 }
 static inline int copy_file_full(const char *from, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata) {
         return copy_file_at_full(AT_FDCWD, from, AT_FDCWD, to, open_flags, mode, chattr_flags, chattr_mask, copy_flags, progress, userdata);
 }
-static inline int copy_file(const char *from, const char *to, int open_flags, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags) {
-        return copy_file_at(AT_FDCWD, from, AT_FDCWD, to, open_flags, mode, chattr_flags, chattr_mask, copy_flags);
+static inline int copy_file(const char *from, const char *to, int open_flags, mode_t mode, CopyFlags copy_flags) {
+        return copy_file_at(AT_FDCWD, from, AT_FDCWD, to, open_flags, mode, copy_flags);
 }
 
-int copy_file_atomic_full(const char *from, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
-static inline int copy_file_atomic(const char *from, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags) {
-        return copy_file_atomic_full(from, to, mode, chattr_flags, chattr_mask, copy_flags, NULL, NULL);
+int copy_file_atomic_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata);
+static inline int copy_file_atomic_at(int dir_fdf, const char *from, int dir_fdt, const char *to, mode_t mode, CopyFlags copy_flags) {
+        return copy_file_atomic_at_full(dir_fdf, from, dir_fdt, to, mode, 0, 0, copy_flags, NULL, NULL);
+}
+static inline int copy_file_atomic_full(const char *from, const char *to, mode_t mode, unsigned chattr_flags, unsigned chattr_mask, CopyFlags copy_flags, copy_progress_bytes_t progress, void *userdata) {
+        return copy_file_atomic_at_full(AT_FDCWD, from, AT_FDCWD, to, mode, 0, 0, copy_flags, NULL, NULL);
+}
+static inline int copy_file_atomic(const char *from, const char *to, mode_t mode, CopyFlags copy_flags) {
+        return copy_file_atomic_full(from, to, mode, 0, 0, copy_flags, NULL, NULL);
 }
 
 int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
index 3d2f1790492183ec20969eabcda1bb815b327370..bf8ea00b14cf00f72153d9859bc36f6465181cd6 100644 (file)
@@ -1,9 +1,12 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <elf.h>
+
 #include "coredump-util.h"
 #include "extract-word.h"
 #include "fileio.h"
 #include "string-table.h"
+#include "unaligned.h"
 #include "virt.h"
 
 static const char *const coredump_filter_table[_COREDUMP_FILTER_MAX] = {
@@ -65,6 +68,95 @@ int coredump_filter_mask_from_string(const char *s, uint64_t *ret) {
         return 0;
 }
 
+#define _DEFINE_PARSE_AUXV(size, type, unaligned_read)                  \
+        static int parse_auxv##size(                                    \
+                        int log_level,                                  \
+                        const void *auxv,                               \
+                        size_t size_bytes,                              \
+                        int *at_secure,                                 \
+                        uid_t *uid,                                     \
+                        uid_t *euid,                                    \
+                        gid_t *gid,                                     \
+                        gid_t *egid) {                                  \
+                                                                        \
+                assert(auxv || size_bytes == 0);                        \
+                assert(at_secure);                                      \
+                assert(uid);                                            \
+                assert(euid);                                           \
+                assert(gid);                                            \
+                assert(egid);                                           \
+                                                                        \
+                if (size_bytes % (2 * sizeof(type)) != 0)               \
+                        return log_full_errno(log_level,                \
+                                              SYNTHETIC_ERRNO(EIO),     \
+                                              "Incomplete auxv structure (%zu bytes).", \
+                                              size_bytes);              \
+                                                                        \
+                size_t words = size_bytes / sizeof(type);               \
+                                                                        \
+                /* Note that we set output variables even on error. */  \
+                                                                        \
+                for (size_t i = 0; i + 1 < words; i += 2) {             \
+                        type key, val;                                  \
+                                                                        \
+                        key = unaligned_read((uint8_t*) auxv + i * sizeof(type)); \
+                        val = unaligned_read((uint8_t*) auxv + (i + 1) * sizeof(type)); \
+                                                                        \
+                        switch (key) {                                  \
+                        case AT_SECURE:                                 \
+                                *at_secure = val != 0;                  \
+                                break;                                  \
+                        case AT_UID:                                    \
+                                *uid = val;                             \
+                                break;                                  \
+                        case AT_EUID:                                   \
+                                *euid = val;                            \
+                                break;                                  \
+                        case AT_GID:                                    \
+                                *gid = val;                             \
+                                break;                                  \
+                        case AT_EGID:                                   \
+                                *egid = val;                            \
+                                break;                                  \
+                        case AT_NULL:                                   \
+                                if (val != 0)                           \
+                                        goto error;                     \
+                                return 0;                               \
+                        }                                               \
+                }                                                       \
+        error:                                                          \
+                return log_full_errno(log_level,                        \
+                                      SYNTHETIC_ERRNO(ENODATA),         \
+                                      "AT_NULL terminator not found, cannot parse auxv structure."); \
+        }
+
+#define DEFINE_PARSE_AUXV(size)                                         \
+        _DEFINE_PARSE_AUXV(size, uint##size##_t, unaligned_read_ne##size)
+
+DEFINE_PARSE_AUXV(32);
+DEFINE_PARSE_AUXV(64);
+
+int parse_auxv(int log_level,
+               uint8_t elf_class,
+               const void *auxv,
+               size_t size_bytes,
+               int *at_secure,
+               uid_t *uid,
+               uid_t *euid,
+               gid_t *gid,
+               gid_t *egid) {
+
+        switch (elf_class) {
+        case ELFCLASS64:
+                return parse_auxv64(log_level, auxv, size_bytes, at_secure, uid, euid, gid, egid);
+        case ELFCLASS32:
+                return parse_auxv32(log_level, auxv, size_bytes, at_secure, uid, euid, gid, egid);
+        default:
+                return log_full_errno(log_level, SYNTHETIC_ERRNO(EPROTONOSUPPORT),
+                                      "Unknown ELF class %d.", elf_class);
+        }
+}
+
 int set_coredump_filter(uint64_t value) {
         char t[STRLEN("0xFFFFFFFF")];
 
index 8eda86dfdb795432a90cb5852d33e6d7b084497d..99dbfde730ecd26eecad9974368a3f06a5c36dde 100644 (file)
@@ -26,5 +26,15 @@ const char* coredump_filter_to_string(CoredumpFilter i) _const_;
 CoredumpFilter coredump_filter_from_string(const char *s) _pure_;
 int coredump_filter_mask_from_string(const char *s, uint64_t *ret);
 
+int parse_auxv(int log_level,
+               uint8_t elf_class,
+               const void *auxv,
+               size_t size_bytes,
+               int *at_secure,
+               uid_t *uid,
+               uid_t *euid,
+               gid_t *gid,
+               gid_t *egid);
+
 int set_coredump_filter(uint64_t value);
 void disable_coredumps(void);
index 34c13cf9692d9644fc30f16d38d686cbb208ce6f..d096576cd6c37c214c1d2fb2e1de951c6f5bb358 100644 (file)
@@ -145,6 +145,8 @@ int parse_cpu_set_full(
         _cleanup_(cpu_set_reset) CPUSet c = {};
         const char *p = ASSERT_PTR(rvalue);
 
+        assert(cpu_set);
+
         for (;;) {
                 _cleanup_free_ char *word = NULL;
                 unsigned cpu_lower, cpu_upper;
@@ -181,9 +183,7 @@ int parse_cpu_set_full(
                 }
         }
 
-        /* On success, transfer ownership to the output variable */
-        *cpu_set = c;
-        c = (CPUSet) {};
+        *cpu_set = TAKE_STRUCT(c);
 
         return 0;
 }
@@ -200,6 +200,8 @@ int parse_cpu_set_extend(
         _cleanup_(cpu_set_reset) CPUSet cpuset = {};
         int r;
 
+        assert(old);
+
         r = parse_cpu_set_full(rvalue, &cpuset, true, unit, filename, line, lvalue);
         if (r < 0)
                 return r;
@@ -211,8 +213,7 @@ int parse_cpu_set_extend(
         }
 
         if (!old->set) {
-                *old = cpuset;
-                cpuset = (CPUSet) {};
+                *old = TAKE_STRUCT(cpuset);
                 return 1;
         }
 
@@ -286,7 +287,6 @@ int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set) {
                                 return r;
                 }
 
-        *set = s;
-        s = (CPUSet) {};
+        *set = TAKE_STRUCT(s);
         return 0;
 }
index 750ee2571e91e3568cd0a3def796dd7acd823e7e..d570f49e7b51619b8dc5f39751b99c489c52d61c 100644 (file)
@@ -703,7 +703,9 @@ int encrypt_credential_and_warn(
                               &tpm2_blob, &tpm2_blob_size,
                               &tpm2_policy_hash, &tpm2_policy_hash_size,
                               &tpm2_pcr_bank,
-                              &tpm2_primary_alg);
+                              &tpm2_primary_alg,
+                              /* ret_srk_buf= */ NULL,
+                              /* ret_srk_buf_size= */ 0);
                 if (r < 0) {
                         if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
                                 log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
@@ -1033,6 +1035,8 @@ int decrypt_credential_and_warn(
                                     le32toh(z->size));
                 }
 
+                 // TODO: Add the SRK data to the credential structure so it can be plumbed
+                 // through and used to verify the TPM session.
                 r = tpm2_unseal(tpm2_device,
                                 le64toh(t->pcr_mask),
                                 le16toh(t->pcr_bank),
@@ -1046,6 +1050,8 @@ int decrypt_credential_and_warn(
                                 le32toh(t->blob_size),
                                 t->policy_hash_and_blob + le32toh(t->blob_size),
                                 le32toh(t->policy_hash_size),
+                                /* srk_buf= */ NULL,
+                                /* srk_buf_size= */ 0,
                                 &tpm2_key,
                                 &tpm2_key_size);
                 if (r < 0)
index 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 17a3ac679af8f75c6dd72347926de1153f3ac600..198c975c443c85320c11e5ee7334da9f0fde1c06 100644 (file)
@@ -14,7 +14,7 @@
 
 #include "alloc-util.h"
 #include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "chattr-util.h"
 #include "copy.h"
 #include "dirent-util.h"
 #include "dissect-image.h"
 #include "env-file.h"
 #include "env-util.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "fs-util.h"
 #include "hashmap.h"
 #include "hostname-setup.h"
 #include "id128-util.h"
+#include "initrd-util.h"
 #include "lock-util.h"
 #include "log.h"
 #include "loop-util.h"
@@ -58,11 +60,29 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
                             "/usr/local/lib/portables\0"
                             "/usr/lib/portables\0",
 
-        [IMAGE_EXTENSION] = "/etc/extensions\0"             /* only place symlinks here */
-                            "/run/extensions\0"             /* and here too */
-                            "/var/lib/extensions\0"         /* the main place for images */
-                            "/usr/local/lib/extensions\0"
-                            "/usr/lib/extensions\0",
+        /* Note that we don't allow storing extensions under /usr/, unlike with other image types. That's
+         * because extension images are supposed to extend /usr/, so you get into recursive races, especially
+         * with directory-based extensions, as the kernel's OverlayFS explicitly checks for this and errors
+         * out with -ELOOP if it finds that a lowerdir= is a child of another lowerdir=. */
+        [IMAGE_SYSEXT] =    "/etc/extensions\0"            /* only place symlinks here */
+                            "/run/extensions\0"            /* and here too */
+                            "/var/lib/extensions\0",       /* the main place for images */
+
+        [IMAGE_CONFEXT] =   "/run/confexts\0"              /* only place symlinks here */
+                            "/var/lib/confexts\0"          /* the main place for images */
+                            "/usr/local/lib/confexts\0"
+                            "/usr/lib/confexts\0",
+};
+
+/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
+ * search dir) */
+static const char* const image_search_path_initrd[_IMAGE_CLASS_MAX] = {
+        /* (entries that aren't listed here will get the same search path as for the non initrd-case) */
+
+        [IMAGE_SYSEXT] =    "/etc/extensions\0"            /* only place symlinks here */
+                            "/run/extensions\0"            /* and here too */
+                            "/var/lib/extensions\0"        /* the main place for images */
+                            "/.extra/sysext\0"             /* put sysext picked up by systemd-stub last, since not trusted */
 };
 
 static Image *image_free(Image *i) {
@@ -438,6 +458,14 @@ static int image_make(
         return -EMEDIUMTYPE;
 }
 
+static const char *pick_image_search_path(ImageClass class) {
+        if (class < 0 || class >= _IMAGE_CLASS_MAX)
+                return NULL;
+
+        /* Use the initrd search path if there is one, otherwise use the common one */
+        return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
+}
+
 int image_find(ImageClass class,
                const char *name,
                const char *root,
@@ -453,13 +481,13 @@ int image_find(ImageClass class,
         if (!image_name_is_valid(name))
                 return -ENOENT;
 
-        NULSTR_FOREACH(path, image_search_path[class]) {
+        NULSTR_FOREACH(path, pick_image_search_path(class)) {
                 _cleanup_free_ char *resolved = NULL;
                 _cleanup_closedir_ DIR *d = NULL;
                 struct stat st;
                 int flags;
 
-                r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+                r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0)
@@ -552,11 +580,11 @@ int image_discover(
         assert(class < _IMAGE_CLASS_MAX);
         assert(h);
 
-        NULSTR_FOREACH(path, image_search_path[class]) {
+        NULSTR_FOREACH(path, pick_image_search_path(class)) {
                 _cleanup_free_ char *resolved = NULL;
                 _cleanup_closedir_ DIR *d = NULL;
 
-                r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+                r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0)
@@ -850,7 +878,7 @@ static int clone_auxiliary_file(const char *path, const char *new_name, const ch
         if (r < 0)
                 return r;
 
-        return copy_file_atomic(path, rs, 0664, 0, 0, COPY_REFLINK);
+        return copy_file_atomic(path, rs, 0664, COPY_REFLINK);
 }
 
 int image_clone(Image *i, const char *new_name, bool read_only) {
@@ -911,7 +939,8 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         case IMAGE_RAW:
                 new_path = strjoina("/var/lib/machines/", new_name, ".raw");
 
-                r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME);
+                r = copy_file_atomic_full(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, FS_NOCOW_FL,
+                                          COPY_REFLINK|COPY_CRTIME, NULL, NULL);
                 break;
 
         case IMAGE_BLOCK:
@@ -1129,7 +1158,7 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
         return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
 }
 
-int image_read_metadata(Image *i) {
+int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
         _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
         int r;
 
@@ -1148,7 +1177,17 @@ int image_read_metadata(Image *i) {
                 _cleanup_free_ char *hostname = NULL;
                 _cleanup_free_ char *path = NULL;
 
-                r = chase_symlinks("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
+                if (i->class == IMAGE_SYSEXT) {
+                        r = extension_has_forbidden_content(i->path);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+                                                       "Conflicting content found in image %s, refusing.",
+                                                       i->name);
+                }
+
+                r = chase("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
                 if (r < 0 && r != -ENOENT)
                         log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name);
                 else if (r >= 0) {
@@ -1159,25 +1198,11 @@ int image_read_metadata(Image *i) {
 
                 path = mfree(path);
 
-                r = chase_symlinks("/etc/machine-id", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
-                if (r < 0 && r != -ENOENT)
-                        log_debug_errno(r, "Failed to chase /etc/machine-id in image %s: %m", i->name);
-                else if (r >= 0) {
-                        _cleanup_close_ int fd = -EBADF;
-
-                        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                        if (fd < 0)
-                                log_debug_errno(errno, "Failed to open %s: %m", path);
-                        else {
-                                r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id);
-                                if (r < 0)
-                                        log_debug_errno(r, "Image %s contains invalid machine ID.", i->name);
-                        }
-                }
-
-                path = mfree(path);
+                r = id128_get_machine(i->path, &machine_id);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read machine ID in image %s, ignoring: %m", i->name);
 
-                r = chase_symlinks("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
+                r = chase("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
                 if (r < 0 && r != -ENOENT)
                         log_debug_errno(r, "Failed to chase /etc/machine-info in image %s: %m", i->name);
                 else if (r >= 0) {
@@ -1190,7 +1215,7 @@ int image_read_metadata(Image *i) {
                 if (r < 0)
                         log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
 
-                r = load_extension_release_pairs(i->path, i->name, /* relax_extension_release_check= */ false, &extension_release);
+                r = load_extension_release_pairs(i->path, i->class, i->name, /* relax_extension_release_check= */ false, &extension_release);
                 if (r < 0)
                         log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m");
 
@@ -1214,7 +1239,9 @@ int image_read_metadata(Image *i) {
 
                 r = dissect_loop_device(
                                 d,
-                                NULL, NULL,
+                                /* verity= */ NULL,
+                                /* mount_options= */ NULL,
+                                image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK |
@@ -1282,7 +1309,7 @@ bool image_in_search_path(
 
         assert(image);
 
-        NULSTR_FOREACH(path, image_search_path[class]) {
+        NULSTR_FOREACH(path, pick_image_search_path(class)) {
                 const char *p, *q;
                 size_t k;
 
@@ -1321,11 +1348,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 3c6928619c0eb02d579f250379284bfcc5883093..edfb1412a446d61d940f641f54a69889db210d56 100644 (file)
@@ -7,20 +7,14 @@
 #include "sd-id128.h"
 
 #include "hashmap.h"
+#include "image-policy.h"
 #include "lock-util.h"
 #include "macro.h"
+#include "os-util.h"
 #include "path-util.h"
 #include "string-util.h"
 #include "time-util.h"
 
-typedef enum ImageClass {
-        IMAGE_MACHINE,
-        IMAGE_PORTABLE,
-        IMAGE_EXTENSION,
-        _IMAGE_CLASS_MAX,
-        _IMAGE_CLASS_INVALID = -EINVAL,
-} ImageClass;
-
 typedef enum ImageType {
         IMAGE_DIRECTORY,
         IMAGE_SUBVOLUME,
@@ -77,15 +71,12 @@ 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);
 
 int image_set_limit(Image *i, uint64_t referenced_max);
 
-int image_read_metadata(Image *i);
+int image_read_metadata(Image *i, const ImagePolicy *image_policy);
 
 bool image_in_search_path(ImageClass class, const char *root, const char *image);
 
index 2502e3a0eb3f37a06c0dcb28b0b599a2c727be63..b84ef464420989bbb09eb3bb1298230cfaeef732 100644 (file)
@@ -26,7 +26,7 @@
 #include "blkid-util.h"
 #include "blockdev-util.h"
 #include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "constants.h"
 #include "copy.h"
@@ -39,7 +39,7 @@
 #include "dm-util.h"
 #include "env-file.h"
 #include "env-util.h"
-#include "extension-release.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
@@ -252,6 +252,21 @@ int probe_filesystem_full(
         if (!b)
                 return -ENOMEM;
 
+        /* The Linux kernel maintains separate block device caches for main ("whole") and partition block
+         * devices, which means making a change to one might not be reflected immediately when reading via
+         * the other. That's massively confusing when mixing accesses to such devices. Let's address this in
+         * a limited way: when probing a file system that is not at the beginning of the block device we
+         * apparently probe a partition via the main block device, and in that case let's first flush the
+         * main block device cache, so that we get the data that the per-partition block device last
+         * sync'ed on.
+         *
+         * This only works under the assumption that any tools that write to the partition block devices
+         * issue an syncfs()/fsync() on the device after making changes. Typically file system formatting
+         * tools that write a superblock onto a partition block device do that, however. */
+        if (offset != 0)
+                if (ioctl(fd, BLKFLSBUF, 0) < 0)
+                        log_debug_errno(errno, "Failed to flush block device cache, ignoring: %m");
+
         errno = 0;
         r = blkid_probe_set_device(
                         b,
@@ -301,7 +316,99 @@ not_found:
 }
 
 #if HAVE_BLKID
-static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
+static int image_policy_may_use(
+                const ImagePolicy *policy,
+                PartitionDesignator designator) {
+
+        PartitionPolicyFlags f;
+
+        /* For each partition we find in the partition table do a first check if it may exist at all given
+         * the policy, or if it shall be ignored. */
+
+        f = image_policy_get_exhaustively(policy, designator);
+        if (f < 0)
+                return f;
+
+        if ((f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
+                /* only flag set in policy is "absent"? then this partition may not exist at all */
+                return log_debug_errno(
+                                SYNTHETIC_ERRNO(ERFKILL),
+                                "Partition of designator '%s' exists, but not allowed by policy, refusing.",
+                                partition_designator_to_string(designator));
+        if ((f & _PARTITION_POLICY_USE_MASK & ~PARTITION_POLICY_ABSENT) == PARTITION_POLICY_UNUSED) {
+                /* only "unused" or "unused" + "absent" are set? then don't use it */
+                log_debug("Partition of designator '%s' exists, and policy dictates to ignore it, doing so.",
+                          partition_designator_to_string(designator));
+                return false; /* ignore! */
+        }
+
+        return true; /* use! */
+}
+
+static int image_policy_check_protection(
+                const ImagePolicy *policy,
+                PartitionDesignator designator,
+                PartitionPolicyFlags found_flags) {
+
+        PartitionPolicyFlags policy_flags;
+
+        /* Checks if the flags in the policy for the designated partition overlap the flags of what we found */
+
+        if (found_flags < 0)
+                return found_flags;
+
+        policy_flags = image_policy_get_exhaustively(policy, designator);
+        if (policy_flags < 0)
+                return policy_flags;
+
+        if ((found_flags & policy_flags) == 0) {
+                _cleanup_free_ char *found_flags_string = NULL, *policy_flags_string = NULL;
+
+                (void) partition_policy_flags_to_string(found_flags, /* simplify= */ true, &found_flags_string);
+                (void) partition_policy_flags_to_string(policy_flags, /* simplify= */ true, &policy_flags_string);
+
+                return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s discovered with policy '%s' but '%s' was required, refusing.",
+                                       partition_designator_to_string(designator),
+                                       strnull(found_flags_string), strnull(policy_flags_string));
+        }
+
+        return 0;
+}
+
+static int image_policy_check_partition_flags(
+                const ImagePolicy *policy,
+                PartitionDesignator designator,
+                uint64_t gpt_flags) {
+
+        PartitionPolicyFlags policy_flags;
+        bool b;
+
+        /* Checks if the partition flags in the policy match reality */
+
+        policy_flags = image_policy_get_exhaustively(policy, designator);
+        if (policy_flags < 0)
+                return policy_flags;
+
+        b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_READ_ONLY);
+        if ((policy_flags & _PARTITION_POLICY_READ_ONLY_MASK) == (b ? PARTITION_POLICY_READ_ONLY_OFF : PARTITION_POLICY_READ_ONLY_ON))
+                return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'read-only' flag incorrectly set (must be %s, is %s), refusing.",
+                                       partition_designator_to_string(designator),
+                                       one_zero(!b), one_zero(b));
+
+        b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_GROWFS);
+        if ((policy_flags & _PARTITION_POLICY_GROWFS_MASK) == (b ? PARTITION_POLICY_GROWFS_OFF : PARTITION_POLICY_GROWFS_ON))
+                return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'growfs' flag incorrectly set (must be %s, is %s), refusing.",
+                                       partition_designator_to_string(designator),
+                                       one_zero(!b), one_zero(b));
+
+        return 0;
+}
+
+static int dissected_image_probe_filesystems(
+                DissectedImage *m,
+                int fd,
+                const ImagePolicy *policy) {
+
         int r;
 
         assert(m);
@@ -310,6 +417,7 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
 
         for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
                 DissectedPartition *p = m->partitions + i;
+                PartitionPolicyFlags found_flags;
 
                 if (!p->found)
                         continue;
@@ -325,14 +433,34 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
                                 return r;
                 }
 
-                if (streq_ptr(p->fstype, "crypto_LUKS"))
+                if (streq_ptr(p->fstype, "crypto_LUKS")) {
                         m->encrypted = true;
+                        found_flags = PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */
+                } else
+                        /* found it, but it's definitely not encrypted, hence mask the encrypted flag, but
+                         * set all other ways that indicate "present". */
+                        found_flags = PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED;
 
                 if (p->fstype && fstype_is_ro(p->fstype))
                         p->rw = false;
 
                 if (!p->rw)
                         p->growfs = false;
+
+                /* We might have learnt more about the file system now (i.e. whether it is encrypted or not),
+                 * hence we need to validate this against policy again, to see if the policy still matches
+                 * with this new information. Note that image_policy_check_protection() will check for
+                 * overlap between what's allowed in the policy and what we pass as 'found_policy' here. In
+                 * the unencrypted case we thus might pass an overly unspecific mask here (i.e. unprotected
+                 * OR verity OR signed), but that's fine since the earlier policy check already checked more
+                 * specific which of those three cases where OK. Keep in mind that this function here only
+                 * looks at specific partitions (and thus can only deduce encryption or not) but not the
+                 * overall partition table (and thus cannot deduce verity or not). The earlier dissection
+                 * checks already did the relevant checks that look at the whole partition table, and
+                 * enforced policy there as needed. */
+                r = image_policy_check_protection(policy, i, found_flags);
+                if (r < 0)
+                        return r;
         }
 
         return 0;
@@ -363,9 +491,7 @@ static void check_partition_flags(
                 log_debug("Unexpected partition flag %llu set on %s!", bit, node);
         }
 }
-#endif
 
-#if HAVE_BLKID
 static int dissected_image_new(const char *path, DissectedImage **ret) {
         _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
         _cleanup_free_ char *name = NULL;
@@ -543,6 +669,7 @@ static int dissect_image(
                 const char *devname,
                 const VeritySettings *verity,
                 const MountOptions *mount_options,
+                const ImagePolicy *policy,
                 DissectImageFlags flags) {
 
         sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
@@ -572,7 +699,11 @@ static int dissect_image(
          * Returns -ENOPKG if no suitable partition table or file system could be found.
          * Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found.
          * Returns -ENXIO if we couldn't find any partition suitable as root or /usr partition
-         * Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that */
+         * Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that
+         * Returns -ERFKILL if image doesn't match image policy
+         * Returns -EBADR if verity data was provided externally for an image that has a GPT partition table (i.e. is not just a naked fs)
+         * Returns -EPROTONOSUPPORT if DISSECT_IMAGE_ADD_PARTITION_DEVICES is set but the block device does not have partition logic enabled
+         * Returns -ENOMSG if we didn't find a single usable partition (and DISSECT_IMAGE_REFUSE_EMPTY is set) */
 
         uint64_t diskseq = m->loop ? m->loop->diskseq : 0;
 
@@ -650,6 +781,34 @@ static int dissect_image(
                         const char *fstype = NULL, *options = NULL, *suuid = NULL;
                         _cleanup_close_ int mount_node_fd = -EBADF;
                         sd_id128_t uuid = SD_ID128_NULL;
+                        PartitionPolicyFlags found_flags;
+                        bool encrypted;
+
+                        /* OK, we have found a file system, that's our root partition then. */
+
+                        r = image_policy_may_use(policy, PARTITION_ROOT);
+                        if (r < 0)
+                                return r;
+                        if (r == 0) /* policy says ignore this, so we ignore it */
+                                return -ENOPKG;
+
+                        (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+                        (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
+
+                        encrypted = streq_ptr(fstype, "crypto_LUKS");
+
+                        if (verity_settings_data_covers(verity, PARTITION_ROOT))
+                                found_flags = verity->root_hash_sig ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
+                        else
+                                found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED;
+
+                        r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags);
+                        if (r < 0)
+                                return r;
+
+                        r = image_policy_check_partition_flags(policy, PARTITION_ROOT, 0); /* we have no gpt partition flags, hence check against all bits off */
+                        if (r < 0)
+                                return r;
 
                         if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
                                 mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop);
@@ -657,10 +816,6 @@ static int dissect_image(
                                         return mount_node_fd;
                         }
 
-                        /* OK, we have found a file system, that's our root partition then. */
-                        (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
-                        (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
-
                         if (fstype) {
                                 t = strdup(fstype);
                                 if (!t)
@@ -681,7 +836,7 @@ static int dissect_image(
                                 return r;
 
                         m->single_file_system = true;
-                        m->encrypted = streq_ptr(fstype, "crypto_LUKS");
+                        m->encrypted = encrypted;
 
                         m->has_verity = verity && verity->data_path;
                         m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
@@ -1049,6 +1204,18 @@ static int dissect_image(
                                 _cleanup_close_ int mount_node_fd = -EBADF;
                                 const char *options = NULL;
 
+                                r = image_policy_may_use(policy, type.designator);
+                                if (r < 0)
+                                        return r;
+                                if (r == 0) {
+                                        /* Policy says: ignore; Remember this fact, so that we later can distinguish between "found but ignored" and "not found at all" */
+
+                                        if (!m->partitions[type.designator].found)
+                                                m->partitions[type.designator].ignored = true;
+
+                                        continue;
+                                }
+
                                 if (m->partitions[type.designator].found) {
                                         /* For most partition types the first one we see wins. Except for the
                                          * rootfs and /usr, where we do a version compare of the label, and
@@ -1139,6 +1306,16 @@ static int dissect_image(
                                 sd_id128_t id = SD_ID128_NULL;
                                 const char *options = NULL;
 
+                                r = image_policy_may_use(policy, PARTITION_XBOOTLDR);
+                                if (r < 0)
+                                        return r;
+                                if (r == 0) { /* policy says: ignore */
+                                        if (!m->partitions[PARTITION_XBOOTLDR].found)
+                                                m->partitions[PARTITION_XBOOTLDR].ignored = true;
+
+                                        continue;
+                                }
+
                                 /* First one wins */
                                 if (m->partitions[PARTITION_XBOOTLDR].found)
                                         continue;
@@ -1223,41 +1400,49 @@ static int dissect_image(
 
                 /* If we didn't find a generic node, then we can't fix this up either */
                 if (generic_node) {
-                        _cleanup_close_ int mount_node_fd = -EBADF;
-                        _cleanup_free_ char *o = NULL, *n = NULL;
-                        const char *options;
-
-                        if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
-                                mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
-                                if (mount_node_fd < 0)
-                                        return mount_node_fd;
-                        }
-
-                        r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
+                        r = image_policy_may_use(policy, PARTITION_ROOT);
                         if (r < 0)
                                 return r;
+                        if (r == 0)
+                                /* Policy says: ignore; remember that we did */
+                                m->partitions[PARTITION_ROOT].ignored = true;
+                        else {
+                                _cleanup_close_ int mount_node_fd = -EBADF;
+                                _cleanup_free_ char *o = NULL, *n = NULL;
+                                const char *options;
 
-                        options = mount_options_from_designator(mount_options, PARTITION_ROOT);
-                        if (options) {
-                                o = strdup(options);
-                                if (!o)
-                                        return -ENOMEM;
-                        }
+                                if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
+                                        mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
+                                        if (mount_node_fd < 0)
+                                                return mount_node_fd;
+                                }
 
-                        assert(generic_nr >= 0);
-                        m->partitions[PARTITION_ROOT] = (DissectedPartition) {
-                                .found = true,
-                                .rw = generic_rw,
-                                .growfs = generic_growfs,
-                                .partno = generic_nr,
-                                .architecture = _ARCHITECTURE_INVALID,
-                                .node = TAKE_PTR(n),
-                                .uuid = generic_uuid,
-                                .mount_options = TAKE_PTR(o),
-                                .mount_node_fd = TAKE_FD(mount_node_fd),
-                                .offset = UINT64_MAX,
-                                .size = UINT64_MAX,
-                        };
+                                r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
+                                if (r < 0)
+                                        return r;
+
+                                options = mount_options_from_designator(mount_options, PARTITION_ROOT);
+                                if (options) {
+                                        o = strdup(options);
+                                        if (!o)
+                                                return -ENOMEM;
+                                }
+
+                                assert(generic_nr >= 0);
+                                m->partitions[PARTITION_ROOT] = (DissectedPartition) {
+                                        .found = true,
+                                        .rw = generic_rw,
+                                        .growfs = generic_growfs,
+                                        .partno = generic_nr,
+                                        .architecture = _ARCHITECTURE_INVALID,
+                                        .node = TAKE_PTR(n),
+                                        .uuid = generic_uuid,
+                                        .mount_options = TAKE_PTR(o),
+                                        .mount_node_fd = TAKE_FD(mount_node_fd),
+                                        .offset = UINT64_MAX,
+                                        .size = UINT64_MAX,
+                                };
+                        }
                 }
         }
 
@@ -1319,7 +1504,42 @@ static int dissect_image(
                 }
         }
 
-        r = dissected_image_probe_filesystems(m, fd);
+        bool any = false;
+
+        /* After we discovered all partitions let's see if the verity requirements match the policy. (Note:
+         * we don't check encryption requirements here, because we haven't probed the file system yet, hence
+         * don't know if this is encrypted or not) */
+        for (PartitionDesignator di = 0; di < _PARTITION_DESIGNATOR_MAX; di++) {
+                PartitionDesignator vi, si;
+                PartitionPolicyFlags found_flags;
+
+                any = any || m->partitions[di].found;
+
+                vi = partition_verity_of(di);
+                si = partition_verity_sig_of(di);
+
+                /* Determine the verity protection level for this partition. */
+                found_flags = m->partitions[di].found ?
+                        (vi >= 0 && m->partitions[vi].found ?
+                         (si >= 0 && m->partitions[si].found ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY) :
+                         PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED) :
+                        (m->partitions[di].ignored ? PARTITION_POLICY_UNUSED : PARTITION_POLICY_ABSENT);
+
+                r = image_policy_check_protection(policy, di, found_flags);
+                if (r < 0)
+                        return r;
+
+                if (m->partitions[di].found) {
+                        r = image_policy_check_partition_flags(policy, di, m->partitions[di].gpt_flags);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (!any && !FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_EMPTY))
+                return -ENOMSG;
+
+        r = dissected_image_probe_filesystems(m, fd, policy);
         if (r < 0)
                 return r;
 
@@ -1331,6 +1551,7 @@ int dissect_image_file(
                 const char *path,
                 const VeritySettings *verity,
                 const MountOptions *mount_options,
+                const ImagePolicy *image_policy,
                 DissectImageFlags flags,
                 DissectedImage **ret) {
 
@@ -1340,7 +1561,6 @@ int dissect_image_file(
         int r;
 
         assert(path);
-        assert(ret);
 
         fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
         if (fd < 0)
@@ -1358,17 +1578,83 @@ int dissect_image_file(
         if (r < 0)
                 return r;
 
-        r = dissect_image(m, fd, path, verity, mount_options, flags);
+        r = dissect_image(m, fd, path, verity, mount_options, image_policy, flags);
         if (r < 0)
                 return r;
 
-        *ret = TAKE_PTR(m);
+        if (ret)
+                *ret = TAKE_PTR(m);
         return 0;
 #else
         return -EOPNOTSUPP;
 #endif
 }
 
+int dissect_log_error(int log_level, int r, const char *name, const VeritySettings *verity) {
+        assert(log_level >= 0 && log_level <= LOG_DEBUG);
+        assert(name);
+
+        switch (r) {
+
+        case 0 ... INT_MAX: /* success! */
+                return r;
+
+        case -EOPNOTSUPP:
+                return log_full_errno(log_level, r, "Dissecting images is not supported, compiled without blkid support.");
+
+        case -ENOPKG:
+                return log_full_errno(log_level, r, "%s: Couldn't identify a suitable partition table or file system.", name);
+
+        case -ENOMEDIUM:
+                return log_full_errno(log_level, r, "%s: The image does not pass os-release/extension-release validation.", name);
+
+        case -EADDRNOTAVAIL:
+                return log_full_errno(log_level, r, "%s: No root partition for specified root hash found.", name);
+
+        case -ENOTUNIQ:
+                return log_full_errno(log_level, r, "%s: Multiple suitable root partitions found in image.", name);
+
+        case -ENXIO:
+                return log_full_errno(log_level, r, "%s: No suitable root partition found in image.", name);
+
+        case -EPROTONOSUPPORT:
+                return log_full_errno(log_level, r, "Device '%s' is a loopback block device with partition scanning turned off, please turn it on.", name);
+
+        case -ENOTBLK:
+                return log_full_errno(log_level, r, "%s: Image is not a block device.", name);
+
+        case -EBADR:
+                return log_full_errno(log_level, r,
+                                      "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
+                                      "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
+                                      name, strna(verity ? verity->data_path : NULL));
+
+        case -ERFKILL:
+                return log_full_errno(log_level, r, "%s: image does not match image policy.", name);
+
+        case -ENOMSG:
+                return log_full_errno(log_level, r, "%s: no suitable partitions found.", name);
+
+        default:
+                return log_full_errno(log_level, r, "%s: cannot dissect image: %m", name);
+        }
+}
+
+int dissect_image_file_and_warn(
+                const char *path,
+                const VeritySettings *verity,
+                const MountOptions *mount_options,
+                const ImagePolicy *image_policy,
+                DissectImageFlags flags,
+                DissectedImage **ret) {
+
+        return dissect_log_error(
+                        LOG_ERR,
+                        dissect_image_file(path, verity, mount_options, image_policy, flags, ret),
+                        path,
+                        verity);
+}
+
 DissectedImage* dissected_image_unref(DissectedImage *m) {
         if (!m)
                 return NULL;
@@ -1448,7 +1734,7 @@ static int run_fsck(int node_fd, const char *fstype) {
                 return log_debug_errno(r, "Failed to fork off fsck: %m");
         if (r == 0) {
                 /* Child */
-                execl("/sbin/fsck", "/sbin/fsck", "-aT", FORMAT_PROC_FD_PATH(node_fd), NULL);
+                execlp("fsck", "fsck", "-aT", FORMAT_PROC_FD_PATH(node_fd), NULL);
                 log_open();
                 log_debug_errno(errno, "Failed to execl() fsck: %m");
                 _exit(FSCK_OPERATIONAL_ERROR);
@@ -1456,7 +1742,7 @@ static int run_fsck(int node_fd, const char *fstype) {
 
         exit_status = wait_for_terminate_and_check("fsck", pid, 0);
         if (exit_status < 0)
-                return log_debug_errno(exit_status, "Failed to fork off /sbin/fsck: %m");
+                return log_debug_errno(exit_status, "Failed to fork off fsck: %m");
 
         if ((exit_status & ~FSCK_ERROR_CORRECTED) != FSCK_SUCCESS) {
                 log_debug("fsck failed with exit status %i.", exit_status);
@@ -1621,11 +1907,6 @@ static int mount_partition(
 
         if (!fstype)
                 return -EAFNOSUPPORT;
-        r = dissect_fstype_ok(fstype);
-        if (r < 0)
-                return r;
-        if (!r)
-                return -EIDRM; /* Recognizable error */
 
         /* We are looking at an encrypted partition? This either means stacked encryption, or the caller
          * didn't call dissected_image_decrypt() beforehand. Let's return a recognizable error for this
@@ -1633,6 +1914,12 @@ static int mount_partition(
         if (streq(fstype, "crypto_LUKS"))
                 return -EUNATCH;
 
+        r = dissect_fstype_ok(fstype);
+        if (r < 0)
+                return r;
+        if (!r)
+                return -EIDRM; /* Recognizable error */
+
         rw = m->rw && !(flags & DISSECT_IMAGE_MOUNT_READ_ONLY);
 
         discard = ((flags & DISSECT_IMAGE_DISCARD) ||
@@ -1650,7 +1937,7 @@ static int mount_partition(
                 if (r < 0 && r != -EROFS)
                         return r;
 
-                r = chase_symlinks(directory, where, CHASE_PREFIX_ROOT, &chased, NULL);
+                r = chase(directory, where, CHASE_PREFIX_ROOT, &chased, NULL);
                 if (r < 0)
                         return r;
 
@@ -1787,11 +2074,16 @@ int dissected_image_mount(
                                         ok = true;
                         }
                         if (!ok && FLAGS_SET(flags, DISSECT_IMAGE_VALIDATE_OS_EXT)) {
-                                r = path_is_extension_tree(where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
+                                r = extension_has_forbidden_content(where);
                                 if (r < 0)
                                         return r;
-                                if (r > 0)
-                                        ok = true;
+                                if (r == 0) {
+                                        r = path_is_extension_tree(IMAGE_SYSEXT, where, m->image_name, FLAGS_SET(flags, DISSECT_IMAGE_RELAX_SYSEXT_CHECK));
+                                        if (r < 0)
+                                                return r;
+                                        if (r > 0)
+                                                ok = true;
+                                }
                         }
 
                         if (!ok)
@@ -1828,7 +2120,7 @@ int dissected_image_mount(
                 /* Mount the ESP to /efi if it exists. If it doesn't exist, use /boot instead, but only if it
                  * exists and is empty, and we didn't already mount the XBOOTLDR partition into it. */
 
-                r = chase_symlinks("/efi", where, CHASE_PREFIX_ROOT, NULL, NULL);
+                r = chase("/efi", where, CHASE_PREFIX_ROOT, NULL, NULL);
                 if (r < 0) {
                         if (r != -ENOENT)
                                 return r;
@@ -1838,7 +2130,7 @@ int dissected_image_mount(
                         if (!xbootldr_mounted) {
                                 _cleanup_free_ char *p = NULL;
 
-                                r = chase_symlinks("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
+                                r = chase("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
                                 if (r < 0) {
                                         if (r != -ENOENT)
                                                 return r;
@@ -3049,7 +3341,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;
@@ -3062,7 +3354,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
                                                "/lib/systemd/systemd",      /* systemd on /usr non-merged systems */
                                                "/sbin/init") {              /* traditional path the Linux kernel invokes */
 
-                                        r = chase_symlinks(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
+                                        r = chase(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
                                         if (r < 0) {
                                                 if (r != -ENOENT)
                                                         log_debug_errno(r, "Failed to resolve %s, ignoring: %m", init);
@@ -3081,7 +3373,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
 
                         default:
                                 NULSTR_FOREACH(p, paths[k]) {
-                                        fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+                                        fd = chase_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
                                         if (fd >= 0)
                                                 break;
                                 }
@@ -3245,6 +3537,7 @@ int dissect_loop_device(
                 LoopDevice *loop,
                 const VeritySettings *verity,
                 const MountOptions *mount_options,
+                const ImagePolicy *image_policy,
                 DissectImageFlags flags,
                 DissectedImage **ret) {
 
@@ -3253,7 +3546,6 @@ int dissect_loop_device(
         int r;
 
         assert(loop);
-        assert(ret);
 
         r = dissected_image_new(loop->backing_file ?: loop->node, &m);
         if (r < 0)
@@ -3262,11 +3554,13 @@ int dissect_loop_device(
         m->loop = loop_device_ref(loop);
         m->sector_size = m->loop->sector_size;
 
-        r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags);
+        r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
         if (r < 0)
                 return r;
 
-        *ret = TAKE_PTR(m);
+        if (ret)
+                *ret = TAKE_PTR(m);
+
         return 0;
 #else
         return -EOPNOTSUPP;
@@ -3277,56 +3571,18 @@ int dissect_loop_device_and_warn(
                 LoopDevice *loop,
                 const VeritySettings *verity,
                 const MountOptions *mount_options,
+                const ImagePolicy *image_policy,
                 DissectImageFlags flags,
                 DissectedImage **ret) {
 
-        const char *name;
-        int r;
-
         assert(loop);
-        assert(loop->fd >= 0);
 
-        name = ASSERT_PTR(loop->backing_file ?: loop->node);
+        return dissect_log_error(
+                        LOG_ERR,
+                        dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
+                        loop->backing_file ?: loop->node,
+                        verity);
 
-        r = dissect_loop_device(loop, verity, mount_options, flags, ret);
-        switch (r) {
-
-        case -EOPNOTSUPP:
-                return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
-
-        case -ENOPKG:
-                return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
-
-        case -ENOMEDIUM:
-                return log_error_errno(r, "%s: The image does not pass validation.", name);
-
-        case -EADDRNOTAVAIL:
-                return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
-
-        case -ENOTUNIQ:
-                return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
-
-        case -ENXIO:
-                return log_error_errno(r, "%s: No suitable root partition found in image.", name);
-
-        case -EPROTONOSUPPORT:
-                return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
-
-        case -ENOTBLK:
-                return log_error_errno(r, "%s: Image is not a block device.", name);
-
-        case -EBADR:
-                return log_error_errno(r,
-                                       "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
-                                       "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
-                                       name, strna(verity ? verity->data_path : NULL));
-
-        default:
-                if (r < 0)
-                        return log_error_errno(r, "Failed to dissect image '%s': %m", name);
-
-                return r;
-        }
 }
 
 bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator partition_designator) {
@@ -3402,6 +3658,7 @@ const char* mount_options_from_designator(const MountOptions *options, Partition
 
 int mount_image_privately_interactively(
                 const char *image,
+                const ImagePolicy *image_policy,
                 DissectImageFlags flags,
                 char **ret_directory,
                 int *ret_dir_fd,
@@ -3444,7 +3701,13 @@ int mount_image_privately_interactively(
         if (r < 0)
                 return log_error_errno(r, "Failed to set up loopback device for %s: %m", image);
 
-        r = dissect_loop_device_and_warn(d, &verity, NULL, flags, &dissected_image);
+        r = dissect_loop_device_and_warn(
+                        d,
+                        &verity,
+                        /* mount_options= */ NULL,
+                        image_policy,
+                        flags,
+                        &dissected_image);
         if (r < 0)
                 return r;
 
@@ -3508,6 +3771,7 @@ int verity_dissect_and_mount(
                 const char *src,
                 const char *dest,
                 const MountOptions *options,
+                const ImagePolicy *image_policy,
                 const char *required_host_os_release_id,
                 const char *required_host_os_release_version_id,
                 const char *required_host_os_release_sysext_level,
@@ -3551,6 +3815,7 @@ int verity_dissect_and_mount(
                         loop_device,
                         &verity,
                         options,
+                        image_policy,
                         dissect_image_flags,
                         &dissected_image);
         /* No partition table? Might be a single-filesystem image, try again */
@@ -3559,6 +3824,7 @@ int verity_dissect_and_mount(
                                 loop_device,
                                 &verity,
                                 options,
+                                image_policy,
                                 dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
                                 &dissected_image);
         if (r < 0)
@@ -3601,7 +3867,7 @@ int verity_dissect_and_mount(
 
                 assert(!isempty(required_host_os_release_id));
 
-                r = load_extension_release_pairs(dest, dissected_image->image_name, relax_extension_release_check, &extension_release);
+                r = load_extension_release_pairs(dest, IMAGE_SYSEXT, dissected_image->image_name, relax_extension_release_check, &extension_release);
                 if (r < 0)
                         return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
 
@@ -3611,7 +3877,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 2e741e826764047efe138fb367c98855fa95d7b6..184e6151ed386635692673064b92fad5dddc2b7c 100644 (file)
@@ -19,6 +19,7 @@ typedef struct VeritySettings VeritySettings;
 
 struct DissectedPartition {
         bool found:1;
+        bool ignored:1;
         bool rw:1;
         bool growfs:1;
         int partno;                 /* -1 if there was no partition and the images contains a file system directly */
@@ -79,6 +80,7 @@ typedef enum DissectImageFlags {
         DISSECT_IMAGE_PIN_PARTITION_DEVICES    = 1 << 21, /* Open dissected partitions and decrypted partitions and pin them by fd */
         DISSECT_IMAGE_RELAX_SYSEXT_CHECK       = 1 << 22, /* Don't insist that the extension-release file name matches the image name */
         DISSECT_IMAGE_DISKSEQ_DEVNODE          = 1 << 23, /* Prefer /dev/disk/by-diskseq/… device nodes */
+        DISSECT_IMAGE_ALLOW_EMPTY              = 1 << 24, /* Allow that no usable partitions is present */
 } DissectImageFlags;
 
 struct DissectedImage {
@@ -133,6 +135,9 @@ struct VeritySettings {
                 .designator = _PARTITION_DESIGNATOR_INVALID     \
         }
 
+/* We include image-policy.h down here, since ImagePolicy wants a complete definition of PartitionDesignator first. */
+#include "image-policy.h"
+
 MountOptions* mount_options_free_all(MountOptions *options);
 DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all);
 const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator);
@@ -141,14 +146,12 @@ int probe_filesystem_full(int fd, const char *path, uint64_t offset, uint64_t si
 static inline int probe_filesystem(const char *path, char **ret_fstype) {
         return probe_filesystem_full(-1, path, 0, UINT64_MAX, ret_fstype);
 }
-int dissect_image_file(
-                const char *path,
-                const VeritySettings *verity,
-                const MountOptions *mount_options,
-                DissectImageFlags flags,
-                DissectedImage **ret);
-int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
-int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
+
+int dissect_log_error(int log_level, int r, const char *name, const VeritySettings *verity);
+int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
 
 DissectedImage* dissected_image_unref(DissectedImage *m);
 DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
@@ -185,9 +188,9 @@ bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesi
 bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator d);
 bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesignator d);
 
-int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
+int mount_image_privately_interactively(const char *path, const ImagePolicy *image_policy, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
 
-int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
+int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
 
 int dissect_fstype_ok(const char *fstype);
 
index 375a3ca600414e572cc2b1d7b4e93ba2c860c217..d46e8380440921994eac61837f7c519a8529633a 100644 (file)
@@ -6,7 +6,7 @@
 #include <stdlib.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "dirent-util.h"
 #include "dropin.h"
@@ -105,7 +105,7 @@ static int unit_file_add_dir(
 
         /* This adds [original_root]/path to dirs, if it exists. */
 
-        r = chase_symlinks(path, original_root, 0, &chased, NULL);
+        r = chase(path, original_root, 0, &chased, NULL);
         if (r == -ENOENT) /* Ignore -ENOENT, after all most units won't have a drop-in dir. */
                 return 0;
         if (r == -ENAMETOOLONG) {
index 58cd7fe9f3dae64b36fa995bdd99b30423f2898b..96ad72134332b5050fca2371642b24c516d72451 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;
@@ -71,7 +69,7 @@ int edit_files_add(
         if (edit_files_contains(context, path))
                 return 0;
 
-        if (!GREEDY_REALLOC0(context->files, context->n_files + 2))
+        if (!GREEDY_REALLOC(context->files, context->n_files + 1))
                 return log_oom();
 
         new_path = strdup(path);
@@ -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, 0, 0, COPY_REFLINK);
-                if (r == -ENOENT) {
-                        r = touch(temp);
-                        mac_selinux_create_file_clear();
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp);
-                } else {
-                        mac_selinux_create_file_clear();
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", e->path);
-                }
-        }
+        if (has_original && (!has_target || e->context->overwrite_with_origin))
+                /* We are asked to overwrite target with original_path or target doesn't exist. */
+                source = e->original_path;
+        else if (has_target)
+                /* Target exists and shouldn't be overwritten. */
+                source = e->path;
+        else
+                source = NULL;
 
         if (e->comment_paths) {
-                _cleanup_free_ char *target_contents = NULL;
-                _cleanup_fclose_ FILE *f = NULL;
+                _cleanup_free_ char *source_contents = NULL;
 
-                r = mac_selinux_create_file_prepare(e->path, S_IFREG);
-                if (r < 0)
-                        return r;
-
-                f = fopen(temp, "we");
-                mac_selinux_create_file_clear();
-                if (!f)
-                        return log_error_errno(errno, "Failed to open temporary file \"%s\": %m", temp);
-
-                if (fchmod(fileno(f), 0644) < 0)
-                        return log_error_errno(errno, "Failed to change mode of temporary file \"%s\": %m", temp);
-
-                r = read_full_file(e->path, &target_contents, NULL);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read target file \"%s\": %m", e->path);
+                if (source) {
+                        r = read_full_file(source, &source_contents, NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to read source file '%s': %m", source);
+                }
 
                 fprintf(f,
                         "### Editing %s\n"
@@ -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 */
@@ -318,10 +300,8 @@ static int strip_edit_temp_file(EditFile *e) {
 
                 assert(e->context->marker_end);
 
-                contents_start = strstr(old_contents, e->context->marker_start);
-                if (contents_start)
-                        contents_start += strlen(e->context->marker_start);
-                else
+                contents_start = strstrafter(old_contents, e->context->marker_start);
+                if (!contents_start)
                         contents_start = old_contents;
 
                 contents_end = strstr(contents_start, e->context->marker_end);
@@ -344,7 +324,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 +357,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 ac68cbc4cb16681e3d25cecb3cba6567310c4b67..735334719ba0880c4be91717a09fcd2130fe6732 100644 (file)
@@ -39,7 +39,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b
         pid_t _pid;
         int r;
 
-        if (null_or_empty_path(path)) {
+        if (null_or_empty_path(path) > 0) {
                 log_debug("%s is empty (a mask).", path);
                 return 0;
         }
@@ -80,6 +80,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b
 
 static int do_execute(
                 char* const* paths,
+                const char *root,
                 usec_t timeout,
                 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
                 void* const callback_args[_STDOUT_CONSUME_MAX],
@@ -121,7 +122,7 @@ static int do_execute(
                 _cleanup_close_ int fd = -EBADF;
                 pid_t pid;
 
-                t = strdup(*path);
+                t = path_join(root, *path);
                 if (!t)
                         return log_oom();
 
@@ -207,6 +208,7 @@ static int do_execute(
 int execute_strv(
                 const char *name,
                 char* const* paths,
+                const char *root,
                 usec_t timeout,
                 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
                 void* const callback_args[_STDOUT_CONSUME_MAX],
@@ -243,7 +245,7 @@ int execute_strv(
         if (r < 0)
                 return r;
         if (r == 0) {
-                r = do_execute(paths, timeout, callbacks, callback_args, fd, argv, envp, flags);
+                r = do_execute(paths, root, timeout, callbacks, callback_args, fd, argv, envp, flags);
                 _exit(r < 0 ? EXIT_FAILURE : r);
         }
 
@@ -295,7 +297,7 @@ int execute_directories(
                         return log_error_errno(r, "Failed to extract file name from '%s': %m", directories[0]);
         }
 
-        return execute_strv(name, paths, timeout, callbacks, callback_args, argv, envp, flags);
+        return execute_strv(name, paths, NULL, timeout, callbacks, callback_args, argv, envp, flags);
 }
 
 static int gather_environment_generate(int fd, void *arg) {
@@ -427,14 +429,14 @@ int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ex_opts) {
         _cleanup_strv_free_ char **ret_opts = NULL;
         ExecCommandFlags it = flags;
         const char *str;
-        int i, r;
+        int r;
 
         assert(ex_opts);
 
         if (flags < 0)
                 return flags;
 
-        for (i = 0; it != 0; it &= ~(1 << i), i++) {
+        for (unsigned i = 0; it != 0; it &= ~(1 << i), i++)
                 if (FLAGS_SET(flags, (1 << i))) {
                         str = exec_command_flags_to_string(1 << i);
                         if (!str)
@@ -444,7 +446,6 @@ int exec_command_flags_to_strv(ExecCommandFlags flags, char ***ex_opts) {
                         if (r < 0)
                                 return r;
                 }
-        }
 
         *ex_opts = TAKE_PTR(ret_opts);
 
@@ -466,9 +467,7 @@ static const char* const exec_command_strings[] = {
 };
 
 const char* exec_command_flags_to_string(ExecCommandFlags i) {
-        size_t idx;
-
-        for (idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
+        for (size_t idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++)
                 if (i == (1 << idx))
                         return exec_command_strings[idx];
 
index 91dbe3f3b4074f1900d3e0b0cffde4cbfe7b0550..b99336ee3be40398909a25b988a36a869d8b0082 100644 (file)
@@ -34,6 +34,7 @@ typedef enum ExecCommandFlags {
 int execute_strv(
                 const char *name,
                 char* const* paths,
+                const char *root,
                 usec_t timeout,
                 gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX],
                 void* const callback_args[_STDOUT_CONSUME_MAX],
diff --git a/src/shared/extension-release.h b/src/shared/extension-release.h
deleted file mode 100644 (file)
index 5c3fee2..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-/* Given an image name (for logging purposes), a set of os-release values from the host and a key-value pair
- * vector of extension-release variables, check that the distro and (system extension level or distro
- * version) match and return 1, and 0 otherwise. */
-int extension_release_validate(
-                const char *name,
-                const char *host_os_release_id,
-                const char *host_os_release_version_id,
-                const char *host_os_release_sysext_level,
-                const char *host_sysext_scope,
-                char **extension_release);
-
-/* Parse SYSTEMD_SYSEXT_HIERARCHIES and if not set, return "/usr /opt" */
-int parse_env_extension_hierarchies(char ***ret_hierarchies);
similarity index 51%
rename from src/shared/extension-release.c
rename to src/shared/extension-util.c
index 2da8e7ea94bec49d261fac8891d42da32d8a7b7b..43a19bf2625d21d6eeb327a6afb4adef61c4a8a1 100644 (file)
@@ -2,8 +2,9 @@
 
 #include "alloc-util.h"
 #include "architecture.h"
+#include "chase.h"
 #include "env-util.h"
-#include "extension-release.h"
+#include "extension-util.h"
 #include "log.h"
 #include "os-util.h"
 #include "strv.h"
@@ -12,39 +13,42 @@ int extension_release_validate(
                 const char *name,
                 const char *host_os_release_id,
                 const char *host_os_release_version_id,
-                const char *host_os_release_sysext_level,
-                const char *host_sysext_scope,
-                char **extension_release) {
+                const char *host_os_extension_release_level,
+                const char *host_extension_scope,
+                char **extension_release,
+                ImageClass image_class) {
 
-        const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;
+        const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL;
+        const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
+        const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
 
         assert(name);
         assert(!isempty(host_os_release_id));
 
-        /* Now that we can look into the extension image, let's see if the OS version is compatible */
+        /* Now that we can look into the extension/confext image, let's see if the OS version is compatible */
         if (strv_isempty(extension_release)) {
-                log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
+                log_debug("Extension '%s' carries no release data, ignoring.", name);
                 return 0;
         }
 
-        if (host_sysext_scope) {
-                _cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
-                const char *extension_sysext_scope;
+        if (host_extension_scope) {
+                _cleanup_strv_free_ char **scope_list = NULL;
+                const char *scope;
                 bool valid;
 
-                extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
-                if (extension_sysext_scope) {
-                        extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
-                        if (!extension_sysext_scope_list)
+                scope = strv_env_pairs_get(extension_release, extension_scope);
+                if (scope) {
+                        scope_list = strv_split(scope, WHITESPACE);
+                        if (!scope_list)
                                 return -ENOMEM;
                 }
 
-                /* by default extension are good for attachment in portable service and on the system */
+                /* By default extension are good for attachment in portable service and on the system */
                 valid = strv_contains(
-                                extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
-                                host_sysext_scope);
+                        scope_list ?: STRV_MAKE("system", "portable"),
+                        host_extension_scope);
                 if (!valid) {
-                        log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
+                        log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope);
                         return 0;
                 }
         }
@@ -53,21 +57,21 @@ int extension_release_validate(
          * the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
         extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
         if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
-            !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
+        !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
                 log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
-                          name, extension_architecture, architecture_to_string(uname_architecture()));
+                        name, extension_architecture, architecture_to_string(uname_architecture()));
                 return 0;
         }
 
         extension_release_id = strv_env_pairs_get(extension_release, "ID");
         if (isempty(extension_release_id)) {
-                log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s' or be '_any'",
-                          name, host_os_release_id);
+                log_debug("Extension '%s' does not contain ID in release file but requested to match '%s' or be '_any'",
+                        name, host_os_release_id);
                 return 0;
         }
 
-        /* A sysext with no host OS dependency (static binaries or scripts) can match
-         * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL are not required anywhere */
+        /* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match
+         * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */
         if (streq(extension_release_id, "_any")) {
                 log_debug("Extension '%s' matches '_any' OS.", name);
                 return 1;
@@ -80,18 +84,18 @@ int extension_release_validate(
         }
 
         /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
-        if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
+        if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
                 log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
                 return 1;
         }
 
         /* If the extension has a sysext API level declared, then it must match the host API
          * level. Otherwise, compare OS version as a whole */
-        extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
-        if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
-                if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
-                        log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
-                                  name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
+        extension_release_level = strv_env_pairs_get(extension_release, extension_level);
+        if (!isempty(host_os_extension_release_level) && !isempty(extension_release_level)) {
+                if (!streq_ptr(host_os_extension_release_level, extension_release_level)) {
+                        log_debug("Extension '%s' is for API level '%s', but running on API level '%s'",
+                                name, strna(extension_release_level), strna(host_os_extension_release_level));
                         return 0;
                 }
         } else if (!isempty(host_os_release_version_id)) {
@@ -99,7 +103,7 @@ int extension_release_validate(
 
                 extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
                 if (isempty(extension_release_version_id)) {
-                        log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
+                        log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'",
                                   name, strna(host_os_release_version_id));
                         return 0;
                 }
@@ -109,7 +113,7 @@ int extension_release_validate(
                                   name, strna(extension_release_version_id), strna(host_os_release_version_id));
                         return 0;
                 }
-        } else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
+        } else if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) {
                 /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
                 log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
                 return 1;
@@ -119,19 +123,41 @@ int extension_release_validate(
         return 1;
 }
 
-int parse_env_extension_hierarchies(char ***ret_hierarchies) {
+int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) {
         _cleanup_free_ char **l = NULL;
         int r;
 
-        r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &l);
+        assert(hierarchy_env);
+        r = getenv_path_list(hierarchy_env, &l);
         if (r == -ENXIO) {
-                /* Default when unset */
-                l = strv_new("/usr", "/opt");
-                if (!l)
-                        return -ENOMEM;
+                if (streq(hierarchy_env, "SYSTEMD_CONFEXT_HIERARCHIES"))
+                        /* Default for confext when unset */
+                        l = strv_new("/etc");
+                else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_HIERARCHIES"))
+                        /* Default for sysext when unset */
+                        l = strv_new("/usr", "/opt");
+                else
+                        return -ENXIO;
         } else if (r < 0)
                 return r;
 
         *ret_hierarchies = TAKE_PTR(l);
         return 0;
 }
+
+int extension_has_forbidden_content(const char *root) {
+        int r;
+
+        /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
+         * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
+         * merged.) */
+        r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
+        if (r > 0) {
+                log_debug("Extension contains '/usr/lib/os-release', which is not allowed, refusing.");
+                return 1;
+        }
+        if (r < 0 && r != -ENOENT)
+                return log_debug_errno(r, "Failed to determine whether '/usr/lib/os-release' exists in the extension: %m");
+
+        return 0;
+}
diff --git a/src/shared/extension-util.h b/src/shared/extension-util.h
new file mode 100644 (file)
index 0000000..3cad219
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "os-util.h"
+
+/* Given an image name (for logging purposes), a set of os-release values from the host and a key-value pair
+ * vector of extension-release variables, check that the distro and (system extension level or distro
+ * version) match and return 1, and 0 otherwise. */
+int extension_release_validate(
+                const char *name,
+                const char *host_os_release_id,
+                const char *host_os_release_version_id,
+                const char *host_os_extension_release_level,
+                const char *host_extension_scope,
+                char **extension_release,
+                ImageClass image_class);
+
+/* Parse hierarchy variables and if not set, return "/usr /opt" for sysext and "/etc" for confext */
+int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env);
+
+/* Insist that extension images do not overwrite the underlying OS release file (it's fine if they place one
+ * in /etc/os-release, i.e. where things don't matter, as they aren't merged.) */
+int extension_has_forbidden_content(const char *root);
index cfa12a509e30a2b483a9f94b6932ec9d23ec7fdb..d816a3e4efb9640dcbfeec8be3244a054466f95b 100644 (file)
@@ -15,6 +15,7 @@
 #include "parse-util.h"
 #include "path-util.h"
 #include "set.h"
+#include "stat-util.h"
 
 #define MAKE_SET(s) ((Set*) s)
 #define MAKE_FDSET(s) ((FDSet*) s)
@@ -23,27 +24,29 @@ FDSet *fdset_new(void) {
         return MAKE_FDSET(set_new(NULL));
 }
 
-int fdset_new_array(FDSet **ret, const int *fds, size_t n_fds) {
-        size_t i;
-        FDSet *s;
+static inline void fdset_shallow_freep(FDSet **s) {
+        /* Destroys the set, but does not free the fds inside, like fdset_free()! */
+        set_free(MAKE_SET(*ASSERT_PTR(s)));
+}
+
+int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) {
+        _cleanup_(fdset_shallow_freep) FDSet *s = NULL;
         int r;
 
         assert(ret);
+        assert(fds || n_fds == 0);
 
         s = fdset_new();
         if (!s)
                 return -ENOMEM;
 
-        for (i = 0; i < n_fds; i++) {
-
+        for (size_t i = 0; i < n_fds; i++) {
                 r = fdset_put(s, fds[i]);
-                if (r < 0) {
-                        set_free(MAKE_SET(s));
+                if (r < 0)
                         return r;
-                }
         }
 
-        *ret = s;
+        *ret = TAKE_PTR(s);
         return 0;
 }
 
@@ -77,8 +80,22 @@ int fdset_put(FDSet *s, int fd) {
         return set_put(MAKE_SET(s), FD_TO_PTR(fd));
 }
 
+int fdset_consume(FDSet *s, int fd) {
+        int r;
+
+        assert(s);
+        assert(fd >= 0);
+
+        r = fdset_put(s, fd);
+        if (r < 0)
+                safe_close(fd);
+
+        return r;
+}
+
 int fdset_put_dup(FDSet *s, int fd) {
-        int copy, r;
+        _cleanup_close_ int copy = -EBADF;
+        int r;
 
         assert(s);
         assert(fd >= 0);
@@ -88,12 +105,10 @@ int fdset_put_dup(FDSet *s, int fd) {
                 return -errno;
 
         r = fdset_put(s, copy);
-        if (r < 0) {
-                safe_close(copy);
+        if (r < 0)
                 return r;
-        }
 
-        return copy;
+        return TAKE_FD(copy);
 }
 
 bool fdset_contains(FDSet *s, int fd) {
@@ -110,53 +125,46 @@ int fdset_remove(FDSet *s, int fd) {
         return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT;
 }
 
-int fdset_new_fill(FDSet **_s) {
+int fdset_new_fill(FDSet **ret) {
+        _cleanup_(fdset_shallow_freep) FDSet *s = NULL;
         _cleanup_closedir_ DIR *d = NULL;
-        int r = 0;
-        FDSet *s;
+        int r;
 
-        assert(_s);
+        assert(ret);
 
-        /* Creates an fdset and fills in all currently open file
-         * descriptors. */
+        /* Creates an fdset and fills in all currently open file descriptors. */
 
         d = opendir("/proc/self/fd");
-        if (!d)
+        if (!d) {
+                if (errno == ENOENT && proc_mounted() == 0)
+                        return -ENOSYS;
+
                 return -errno;
+        }
 
         s = fdset_new();
-        if (!s) {
-                r = -ENOMEM;
-                goto finish;
-        }
+        if (!s)
+                return -ENOMEM;
 
         FOREACH_DIRENT(de, d, return -errno) {
                 int fd = -EBADF;
 
                 r = safe_atoi(de->d_name, &fd);
                 if (r < 0)
-                        goto finish;
+                        return r;
 
                 if (fd < 3)
                         continue;
-
                 if (fd == dirfd(d))
                         continue;
 
                 r = fdset_put(s, fd);
                 if (r < 0)
-                        goto finish;
+                        return r;
         }
 
-        r = 0;
-        *_s = TAKE_PTR(s);
-
-finish:
-        /* We won't close the fds here! */
-        if (s)
-                set_free(MAKE_SET(s));
-
-        return r;
+        *ret = TAKE_PTR(s);
+        return 0;
 }
 
 int fdset_cloexec(FDSet *fds, bool b) {
@@ -174,53 +182,66 @@ int fdset_cloexec(FDSet *fds, bool b) {
         return 0;
 }
 
-int fdset_new_listen_fds(FDSet **_s, bool unset) {
+int fdset_new_listen_fds(FDSet **ret, bool unset) {
+        _cleanup_(fdset_shallow_freep) FDSet *s = NULL;
         int n, fd, r;
-        FDSet *s;
 
-        assert(_s);
+        assert(ret);
 
         /* Creates an fdset and fills in all passed file descriptors */
 
         s = fdset_new();
-        if (!s) {
-                r = -ENOMEM;
-                goto fail;
-        }
+        if (!s)
+                return -ENOMEM;
 
         n = sd_listen_fds(unset);
         for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
                 r = fdset_put(s, fd);
                 if (r < 0)
-                        goto fail;
+                        return r;
         }
 
-        *_s = s;
+        *ret = TAKE_PTR(s);
         return 0;
-
-fail:
-        if (s)
-                set_free(MAKE_SET(s));
-
-        return r;
 }
 
-int fdset_close_others(FDSet *fds) {
+int fdset_to_array(FDSet *fds, int **ret) {
+        unsigned j = 0, m;
         void *e;
-        int *a = NULL;
-        size_t j = 0, m;
+        int *a;
 
-        m = fdset_size(fds);
+        assert(ret);
 
-        if (m > 0) {
-                a = newa(int, m);
-                SET_FOREACH(e, MAKE_SET(fds))
-                        a[j++] = PTR_TO_FD(e);
+        m = fdset_size(fds);
+        if (m > INT_MAX) /* We want to be able to return an "int" */
+                return -ENOMEM;
+        if (m == 0) {
+                *ret = NULL; /* suppress array allocation if empty */
+                return 0;
         }
 
+        a = new(int, m);
+        if (!a)
+                return -ENOMEM;
+
+        SET_FOREACH(e, MAKE_SET(fds))
+                a[j++] = PTR_TO_FD(e);
+
         assert(j == m);
 
-        return close_all_fds(a, j);
+        *ret = TAKE_PTR(a);
+        return (int) m;
+}
+
+int fdset_close_others(FDSet *fds) {
+        _cleanup_free_ int *a = NULL;
+        int n;
+
+        n = fdset_to_array(fds, &a);
+        if (n < 0)
+                return n;
+
+        return close_all_fds(a, n);
 }
 
 unsigned fdset_size(FDSet *fds) {
index 39d15ee4aaf076b14312221a054deb8b90d535ba..9700cdbbca8b6d468b3e85aa5c80e6bf53cd275c 100644 (file)
@@ -13,6 +13,7 @@ FDSet* fdset_new(void);
 FDSet* fdset_free(FDSet *s);
 
 int fdset_put(FDSet *s, int fd);
+int fdset_consume(FDSet *s, int fd);
 int fdset_put_dup(FDSet *s, int fd);
 
 bool fdset_contains(FDSet *s, int fd);
@@ -24,6 +25,8 @@ int fdset_new_listen_fds(FDSet **ret, bool unset);
 
 int fdset_cloexec(FDSet *fds, bool b);
 
+int fdset_to_array(FDSet *fds, int **ret);
+
 int fdset_close_others(FDSet *fds);
 
 unsigned fdset_size(FDSet *fds);
index d03b0548ec9265df6dfbfa999c4049e88876a943..572b8f62ad681576d61f2dac83893c3740e955b5 100644 (file)
@@ -23,9 +23,15 @@ int write_string_file_atomic_label_ts(const char *fn, const char *line, struct t
 int create_shutdown_run_nologin_or_warn(void) {
         int r;
 
-        /* This is used twice: once in systemd-user-sessions.service, in order to block logins when we actually go
-         * down, and once in systemd-logind.service when shutdowns are scheduled, and logins are to be turned off a bit
-         * in advance. We use the same wording of the message in both cases. */
+        /* This is used twice: once in systemd-user-sessions.service, in order to block logins when we
+         * actually go down, and once in systemd-logind.service when shutdowns are scheduled, and logins are
+         * to be turned off a bit in advance. We use the same wording of the message in both cases.
+         *
+         * Traditionally, there was only /etc/nologin, and we managed that. Then, in PAM 1.1
+         * support for /run/nologin was added as alternative
+         * (https://github.com/linux-pam/linux-pam/commit/e9e593f6ddeaf975b7fe8446d184e6bc387d450b).
+         * 13 years later we stopped managing /etc/nologin, leaving it for the administrator to manage.
+         */
 
         r = write_string_file_atomic_label("/run/nologin",
                                            "System is going down. Unprivileged users are not permitted to log in anymore. "
index 084dd1c1e2a9483a33d2332cfca58667b327ed56..c4cf50851716a4cef3f2ba374368597718397ec2 100644 (file)
@@ -9,7 +9,7 @@
 #include "alloc-util.h"
 #include "blkid-util.h"
 #include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "device-util.h"
 #include "devnum-util.h"
 #include "env-util.h"
@@ -238,12 +238,13 @@ static int verify_esp_udev(
 }
 
 static int verify_fsroot_dir(
+                int dir_fd,
                 const char *path,
                 bool searching,
                 bool unprivileged_mode,
                 dev_t *ret_dev) {
 
-        _cleanup_close_ int fd = -EBADF;
+        _cleanup_free_ char *f = NULL;
         STRUCT_NEW_STATX_DEFINE(sxa);
         STRUCT_NEW_STATX_DEFINE(sxb);
         int r;
@@ -251,24 +252,22 @@ static int verify_fsroot_dir(
         /* Checks if the specified directory is at the root of its file system, and returns device
          * major/minor of the device, if it is. */
 
+        assert(dir_fd >= 0);
         assert(path);
 
-        /* We are using O_PATH here, since that way we can operate on directory inodes we cannot look into,
-         * which is quite likely if we run unprivileged */
-        fd = open(path, O_CLOEXEC|O_DIRECTORY|O_PATH);
-        if (fd < 0)
-                return log_full_errno((searching && errno == ENOENT) ||
-                                      (unprivileged_mode && ERRNO_IS_PRIVILEGE(errno)) ? LOG_DEBUG : LOG_ERR, errno,
-                                      "Failed to open directory \"%s\": %m", path);
+        /* We pass the full path from the root directory file descriptor so we can use it for logging, but
+         * dir_fd points to the parent directory of the final component of the given path, so we extract the
+         * filename and operate on that. */
 
-        /* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the
-         * directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here,
-         * before stat()ing */
-        (void) faccessat(fd, "trigger", F_OK, AT_SYMLINK_NOFOLLOW); /* Filename doesn't matter... */
+        r = path_extract_filename(path, &f);
+        if (r < 0 && r != -EADDRNOTAVAIL)
+                return log_error_errno(r, "Failed to extract filename of %s: %m", path);
 
-        r = statx_fallback(fd, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxa.sx);
+        r = statx_fallback(dir_fd, strempty(f), AT_SYMLINK_NOFOLLOW|(isempty(f) ? AT_EMPTY_PATH : 0),
+                           STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxa.sx);
         if (r < 0)
-                return log_full_errno((unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r,
+                return log_full_errno((searching && r == -ENOENT) ||
+                                      (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r,
                                       "Failed to determine block device node of \"%s\": %m", path);
 
         assert(S_ISDIR(sxa.sx.stx_mode)); /* We used O_DIRECTORY above, when opening, so this must hold */
@@ -288,29 +287,7 @@ static int verify_fsroot_dir(
         }
 
         /* Now let's look at the parent */
-        r = statx_fallback(fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb.sx);
-        if (r < 0 && ERRNO_IS_PRIVILEGE(r)) {
-                _cleanup_free_ char *parent = NULL;
-
-                /* If going via ".." didn't work due to EACCESS, then let's determine the parent path
-                 * directly instead. It's not as good, due to symlinks and such, but we can't do anything
-                 * better here.
-                 *
-                 * (In case you wonder where this fallback is useful: consider a classic Fedora setup with
-                 * /boot/ being an ext4 partition and /boot/efi/ being the VFAT ESP. The latter is mounted
-                 * inaccessible for regular users via the dmask= mount option. In that case as unprivileged
-                 * user we can stat() /boot/efi/, and we can stat()/enumerate /boot/. But we cannot look into
-                 * /boot/efi/, and in particular not use /boot/efi/../ – hence this work-around.) */
-
-                if (path_equal(path, "/"))
-                        goto success;
-
-                r = path_extract_directory(path, &parent);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to extract parent path from '%s': %m", path);
-
-                r = statx_fallback(AT_FDCWD, parent, AT_SYMLINK_NOFOLLOW, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb.sx);
-        }
+        r = statx_fallback(dir_fd, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb.sx);
         if (r < 0)
                 return log_full_errno(unprivileged_mode && ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_ERR, r,
                                       "Failed to determine block device node of parent of \"%s\": %m", path);
@@ -327,25 +304,17 @@ success:
         if (!ret_dev)
                 return 0;
 
-        if (sxa.sx.stx_dev_major == 0) { /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */
-                _cleanup_close_ int real_fd = -EBADF;
-
-                /* The statx() above we can execute on an O_PATH fd. But the btrfs ioctl we cannot. Hence
-                 * acquire a "real" fd first, without the O_PATH flag. */
-
-                real_fd = fd_reopen(fd, O_DIRECTORY|O_CLOEXEC);
-                if (real_fd < 0)
-                        return real_fd;
-
-                return btrfs_get_block_device_fd(real_fd, ret_dev);
-        }
+        if (sxa.sx.stx_dev_major == 0) /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */
+                return btrfs_get_block_device_at(dir_fd, strempty(f), ret_dev);
 
         *ret_dev = makedev(sxa.sx.stx_dev_major, sxa.sx.stx_dev_minor);
         return 0;
 }
 
 static int verify_esp(
-                const char *p,
+                int rfd,
+                const char *path,
+                char **ret_path,
                 uint32_t *ret_part,
                 uint64_t *ret_pstart,
                 uint64_t *ret_psize,
@@ -355,10 +324,13 @@ static int verify_esp(
 
         bool relax_checks, searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING),
              unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE);
+        _cleanup_free_ char *p = NULL;
+        _cleanup_close_ int pfd = -EBADF;
         dev_t devid = 0;
         int r;
 
-        assert(p);
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(path);
 
         /* This logs about all errors, except:
          *
@@ -374,13 +346,25 @@ static int verify_esp(
         /* Non-root user can only check the status, so if an error occurred in the following, it does not cause any
          * issues. Let's also, silence the error messages. */
 
+        r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT, &p, &pfd);
+        if (r < 0)
+                return log_full_errno((searching && r == -ENOENT) ||
+                                      (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR,
+                                      r, "Failed to open parent directory of \"%s\": %m", path);
+
         if (!relax_checks) {
+                _cleanup_free_ char *f = NULL;
                 struct statfs sfs;
 
-                if (statfs(p, &sfs) < 0)
+                r = path_extract_filename(p, &f);
+                if (r < 0 && r != -EADDRNOTAVAIL)
+                        return log_error_errno(r, "Failed to extract filename of %s: %m", p);
+
+                r = xstatfsat(pfd, strempty(f), &sfs);
+                if (r < 0)
                         /* If we are searching for the mount point, don't generate a log message if we can't find the path */
-                        return log_full_errno((searching && errno == ENOENT) ||
-                                              (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
+                        return log_full_errno((searching && r == -ENOENT) ||
+                                              (unprivileged_mode && r == -EACCES) ? LOG_DEBUG : LOG_ERR, r,
                                               "Failed to check file system type of \"%s\": %m", p);
 
                 if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
@@ -393,7 +377,7 @@ static int verify_esp(
                 relax_checks ||
                 detect_container() > 0;
 
-        r = verify_fsroot_dir(p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
+        r = verify_fsroot_dir(pfd, p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
         if (r < 0)
                 return r;
 
@@ -414,12 +398,16 @@ static int verify_esp(
         if (r < 0)
                 return r;
 
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
         if (ret_devid)
                 *ret_devid = devid;
 
         return 0;
 
 finish:
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
         if (ret_part)
                 *ret_part = 0;
         if (ret_pstart)
@@ -434,8 +422,8 @@ finish:
         return 0;
 }
 
-int find_esp_and_warn(
-                const char *root,
+int find_esp_and_warn_at(
+                int rfd,
                 const char *path,
                 bool unprivileged_mode,
                 char **ret_path,
@@ -445,9 +433,7 @@ int find_esp_and_warn(
                 sd_id128_t *ret_uuid,
                 dev_t *ret_devid) {
 
-        VerifyESPFlags flags = (unprivileged_mode ? VERIFY_ESP_UNPRIVILEGED_MODE : 0) |
-                               (root ? VERIFY_ESP_RELAX_CHECKS : 0);
-        _cleanup_free_ char *p = NULL;
+        VerifyESPFlags flags = (unprivileged_mode ? VERIFY_ESP_UNPRIVILEGED_MODE : 0);
         int r;
 
         /* This logs about all errors except:
@@ -456,48 +442,43 @@ int find_esp_and_warn(
          *   -EACCESS → when unprivileged_mode is true, and we can't access something
          */
 
-        if (path) {
-                r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
+        assert(rfd >= 0 || rfd == AT_FDCWD);
 
-                r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags);
-                if (r < 0)
-                        return r;
+        r = dir_fd_is_root_or_cwd(rfd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if directory file descriptor is root: %m");
+        if (r == 0)
+                flags |= VERIFY_ESP_RELAX_CHECKS;
 
-                goto found;
-        }
+        if (path)
+                return verify_esp(rfd, path, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags);
 
         path = getenv("SYSTEMD_ESP_PATH");
         if (path) {
+                _cleanup_free_ char *p = NULL;
+                _cleanup_close_ int fd = -EBADF;
                 struct stat st;
 
-                r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                if (!path_is_valid(p) || !path_is_absolute(p))
+                if (!path_is_valid(path) || !path_is_absolute(path))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
-                                               p);
+                                               "$SYSTEMD_ESP_PATH does not refer to an absolute path, refusing to use it: %s",
+                                               path);
+
+                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, &p, &fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to resolve path %s: %m", path);
 
                 /* Note: when the user explicitly configured things with an env var we won't validate the
                  * path beyond checking it refers to a directory. After all we want this to be useful for
                  * testing. */
 
-                if (stat(p, &st) < 0)
+                if (fstat(fd, &st) < 0)
                         return log_error_errno(errno, "Failed to stat '%s': %m", p);
                 if (!S_ISDIR(st.st_mode))
                         return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p);
 
+                if (ret_path)
+                        *ret_path = TAKE_PTR(p);
                 if (ret_part)
                         *ret_part = 0;
                 if (ret_pstart)
@@ -509,36 +490,70 @@ int find_esp_and_warn(
                 if (ret_devid)
                         *ret_devid = st.st_dev;
 
-                goto found;
+                return 0;
         }
 
         FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") {
-                r = chase_symlinks(dir, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r == -ENOENT)
-                        continue;
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               dir,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid,
+                r = verify_esp(rfd, dir, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid,
                                flags | VERIFY_ESP_SEARCHING);
                 if (r >= 0)
-                        goto found;
-                if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+                        return 0;
+                if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR, -ENOTTY)) /* This one is not it */
                         return r;
-
-                p = mfree(p);
         }
 
         /* No logging here */
         return -ENOKEY;
+}
 
-found:
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
+int find_esp_and_warn(
+                const char *root,
+                const char *path,
+                bool unprivileged_mode,
+                char **ret_path,
+                uint32_t *ret_part,
+                uint64_t *ret_pstart,
+                uint64_t *ret_psize,
+                sd_id128_t *ret_uuid,
+                dev_t *ret_devid) {
+
+        _cleanup_close_ int rfd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        uint32_t part;
+        uint64_t pstart, psize;
+        sd_id128_t uuid;
+        dev_t devid;
+        int r;
+
+        rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC);
+        if (rfd < 0)
+                return -errno;
+
+        r = find_esp_and_warn_at(rfd, path, unprivileged_mode,
+                                 ret_path ? &p : NULL,
+                                 ret_part ? &part : NULL,
+                                 ret_pstart ? &pstart : NULL,
+                                 ret_psize ? &psize : NULL,
+                                 ret_uuid ? &uuid : NULL,
+                                 ret_devid ? &devid : NULL);
+        if (r < 0)
+                return r;
+
+        if (ret_path) {
+                r = chaseat_prefix_root(p, root, ret_path);
+                if (r < 0)
+                        return r;
+        }
+        if (ret_part)
+                *ret_part = part;
+        if (ret_pstart)
+                *ret_pstart = pstart;
+        if (ret_psize)
+                *ret_psize = psize;
+        if (ret_uuid)
+                *ret_uuid = uuid;
+        if (ret_devid)
+                *ret_devid = devid;
 
         return 0;
 }
@@ -701,23 +716,34 @@ static int verify_xbootldr_udev(
 }
 
 static int verify_xbootldr(
-                const char *p,
+                int rfd,
+                const char *path,
                 bool searching,
                 bool unprivileged_mode,
+                char **ret_path,
                 sd_id128_t *ret_uuid,
                 dev_t *ret_devid) {
 
+        _cleanup_free_ char *p = NULL;
+        _cleanup_close_ int pfd = -EBADF;
         bool relax_checks;
         dev_t devid = 0;
         int r;
 
-        assert(p);
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+        assert(path);
+
+        r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT, &p, &pfd);
+        if (r < 0)
+                return log_full_errno((searching && r == -ENOENT) ||
+                                      (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR,
+                                      r, "Failed to open parent directory of \"%s\": %m", path);
 
         relax_checks =
                 getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0 ||
                 detect_container() > 0;
 
-        r = verify_fsroot_dir(p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
+        r = verify_fsroot_dir(pfd, p, searching, unprivileged_mode, relax_checks ? NULL : &devid);
         if (r < 0)
                 return r;
 
@@ -731,12 +757,16 @@ static int verify_xbootldr(
         if (r < 0)
                 return r;
 
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
         if (ret_devid)
                 *ret_devid = devid;
 
         return 0;
 
 finish:
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
         if (ret_uuid)
                 *ret_uuid = SD_ID128_NULL;
         if (ret_devid)
@@ -745,85 +775,98 @@ finish:
         return 0;
 }
 
-int find_xbootldr_and_warn(
-                const char *root,
+int find_xbootldr_and_warn_at(
+                int rfd,
                 const char *path,
                 bool unprivileged_mode,
                 char **ret_path,
                 sd_id128_t *ret_uuid,
                 dev_t *ret_devid) {
 
-        _cleanup_free_ char *p = NULL;
         int r;
 
         /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
 
-        if (path) {
-                r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
+        assert(rfd >= 0 || rfd == AT_FDCWD);
 
-                r = verify_xbootldr(p, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid);
-                if (r < 0)
-                        return r;
-
-                goto found;
-        }
+        if (path)
+                return verify_xbootldr(rfd, path, /* searching= */ false, unprivileged_mode, ret_path, ret_uuid, ret_devid);
 
         path = getenv("SYSTEMD_XBOOTLDR_PATH");
         if (path) {
+                _cleanup_free_ char *p = NULL;
+                _cleanup_close_ int fd = -EBADF;
                 struct stat st;
 
-                r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL);
-                if (r < 0)
-                        return log_error_errno(r,
-                                               "Failed to resolve path %s%s%s: %m",
-                                               path,
-                                               root ? " under directory " : "",
-                                               strempty(root));
-
-                if (!path_is_valid(p) || !path_is_absolute(p))
+                if (!path_is_valid(path) || !path_is_absolute(path))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
-                                               p);
+                                               "$SYSTEMD_XBOOTLDR_PATH does not refer to an absolute path, refusing to use it: %s",
+                                               path);
+
+                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, &p, &fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to resolve path %s: %m", p);
 
-                if (stat(p, &st) < 0)
+                if (fstat(fd, &st) < 0)
                         return log_error_errno(errno, "Failed to stat '%s': %m", p);
                 if (!S_ISDIR(st.st_mode))
                         return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p);
 
+                if (ret_path)
+                        *ret_path = TAKE_PTR(p);
                 if (ret_uuid)
                         *ret_uuid = SD_ID128_NULL;
                 if (ret_devid)
                         *ret_devid = st.st_dev;
 
-                goto found;
+                return 0;
         }
 
-        r = chase_symlinks("/boot", root, CHASE_PREFIX_ROOT, &p, NULL);
-        if (r == -ENOENT)
+        r = verify_xbootldr(rfd, "/boot", /* searching= */ true, unprivileged_mode, ret_path, ret_uuid, ret_devid);
+        if (r < 0) {
+                if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
+                        return r;
+
                 return -ENOKEY;
-        if (r < 0)
-                return log_error_errno(r,
-                                       "Failed to resolve path /boot%s%s: %m",
-                                       root ? " under directory " : "",
-                                       strempty(root));
+        }
 
-        r = verify_xbootldr(p, /* searching= */ true, unprivileged_mode, ret_uuid, ret_devid);
-        if (r >= 0)
-                goto found;
-        if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR)) /* This one is not it */
-                return r;
+        return 0;
+}
 
-        return -ENOKEY;
+int find_xbootldr_and_warn(
+        const char *root,
+        const char *path,
+        bool unprivileged_mode,
+        char **ret_path,
+        sd_id128_t *ret_uuid,
+        dev_t *ret_devid) {
+
+        _cleanup_close_ int rfd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        sd_id128_t uuid;
+        dev_t devid;
+        int r;
 
-found:
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
+        rfd = open(empty_to_root(root), O_PATH|O_DIRECTORY|O_CLOEXEC);
+        if (rfd < 0)
+                return -errno;
+
+        r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode,
+                                      ret_path ? &p : NULL,
+                                      ret_uuid ? &uuid : NULL,
+                                      ret_devid ? &devid : NULL);
+        if (r < 0)
+                return r;
+
+        if (ret_path) {
+                r = chaseat_prefix_root(p, root, ret_path);
+                if (r < 0)
+                        return r;
+        }
+        if (ret_uuid)
+                *ret_uuid = uuid;
+        if (ret_devid)
+                *ret_devid = devid;
 
         return 0;
 }
index 78d7f4551ed80127764df4db46e082319ee61532..94f320195bf0154fd451534ec28f447e33ce3f65 100644 (file)
@@ -8,5 +8,8 @@
 
 #include "sd-id128.h"
 
+int find_esp_and_warn_at(int rfd, const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
 int find_esp_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
+
+int find_xbootldr_and_warn_at(int rfd, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid);
 int find_xbootldr_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid);
index 351a5ede118445f9cd1eb40726f73b08f954afad..204e8b68b6d5903c9b066862e60b68990816e819 100644 (file)
@@ -7,6 +7,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "devnum-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
@@ -24,6 +25,7 @@
 #include "process-util.h"
 #include "signal-util.h"
 #include "sort-util.h"
+#include "stat-util.h"
 #include "string-util.h"
 #include "strxcpyx.h"
 #include "terminal-util.h"
@@ -107,6 +109,7 @@ typedef struct TableData {
                 gid_t gid;
                 pid_t pid;
                 mode_t mode;
+                dev_t devnum;
                 /* … add more here as we start supporting more cell data types … */
         };
 } TableData;
@@ -348,8 +351,12 @@ static size_t table_data_size(TableDataType type, const void *data) {
                 return sizeof(pid_t);
 
         case TABLE_MODE:
+        case TABLE_MODE_INODE_TYPE:
                 return sizeof(mode_t);
 
+        case TABLE_DEVNUM:
+                return sizeof(dev_t);
+
         default:
                 assert_not_reached();
         }
@@ -867,6 +874,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         gid_t gid;
                         pid_t pid;
                         mode_t mode;
+                        dev_t devnum;
                 } buffer;
 
                 switch (type) {
@@ -1022,10 +1030,16 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         break;
 
                 case TABLE_MODE:
+                case TABLE_MODE_INODE_TYPE:
                         buffer.mode = va_arg(ap, mode_t);
                         data = &buffer.mode;
                         break;
 
+                case TABLE_DEVNUM:
+                        buffer.devnum = va_arg(ap, dev_t);
+                        data = &buffer.devnum;
+                        break;
+
                 case TABLE_SET_MINIMUM_WIDTH: {
                         size_t w = va_arg(ap, size_t);
 
@@ -1273,6 +1287,8 @@ int table_hide_column_from_display_internal(Table *t, ...) {
 }
 
 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
+        int r;
+
         assert(a);
         assert(b);
 
@@ -1377,8 +1393,16 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                         return CMP(a->pid, b->pid);
 
                 case TABLE_MODE:
+                case TABLE_MODE_INODE_TYPE:
                         return CMP(a->mode, b->mode);
 
+                case TABLE_DEVNUM:
+                        r = CMP(major(a->devnum), major(b->devnum));
+                        if (r != 0)
+                                return r;
+
+                        return CMP(minor(a->devnum), minor(b->devnum));
+
                 default:
                         ;
                 }
@@ -1882,6 +1906,22 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
                 break;
         }
 
+        case TABLE_MODE_INODE_TYPE:
+
+                if (d->mode == MODE_INVALID)
+                        return table_ersatz_string(t);
+
+                return inode_type_to_string(d->mode);
+
+        case TABLE_DEVNUM:
+                if (devnum_is_zero(d->devnum))
+                        return table_ersatz_string(t);
+
+                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
+                        return NULL;
+
+                break;
+
         default:
                 assert_not_reached();
         }
@@ -2696,11 +2736,20 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
                 return json_variant_new_integer(ret, d->int_val);
 
         case TABLE_MODE:
+        case TABLE_MODE_INODE_TYPE:
                 if (d->mode == MODE_INVALID)
                         return json_variant_new_null(ret);
 
                 return json_variant_new_unsigned(ret, d->mode);
 
+        case TABLE_DEVNUM:
+                if (devnum_is_zero(d->devnum))
+                        return json_variant_new_null(ret);
+
+                return json_build(ret, JSON_BUILD_ARRAY(
+                                                  JSON_BUILD_UNSIGNED(major(d->devnum)),
+                                                  JSON_BUILD_UNSIGNED(minor(d->devnum))));
+
         default:
                 return -EINVAL;
         }
index 5a2b366b59c9b9e9f8c3ae59e1df660c3bb80680..148418c70f6143a1ad0d3db7d6086ba8185ffc6b 100644 (file)
@@ -51,7 +51,9 @@ typedef enum TableDataType {
         TABLE_GID,
         TABLE_PID,
         TABLE_SIGNAL,
-        TABLE_MODE,     /* as in UNIX file mode (mode_t), in typical octal output */
+        TABLE_MODE,            /* as in UNIX file mode (mode_t), in typical octal output */
+        TABLE_MODE_INODE_TYPE, /* also mode_t, but displays only the inode type as string */
+        TABLE_DEVNUM,          /* a dev_t, displayed in the usual major:minor way */
         _TABLE_DATA_TYPE_MAX,
 
         /* The following are not really data types, but commands for table_add_cell_many() to make changes to
diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c
new file mode 100644 (file)
index 0000000..bccd554
--- /dev/null
@@ -0,0 +1,762 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "image-policy.h"
+#include "logarithm.h"
+#include "sort-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+/* Rationale for the chosen syntax:
+ *
+ * → one line, so that it can be reasonably added to a shell command line, for example via `systemd-dissect
+ *   --image-policy=…` or to the kernel command line via `systemd.image_policy=`.
+ *
+ * → no use of "," or ";" as separators, so that it can be included in mount/fstab-style option strings and
+ *   doesn't require escaping. Instead, separators are ":", "=", "+" which should be fine both in shell
+ *   command lines and in mount/fstab style option strings.
+ */
+
+static int partition_policy_compare(const PartitionPolicy *a, const PartitionPolicy *b) {
+        return CMP(ASSERT_PTR(a)->designator, ASSERT_PTR(b)->designator);
+}
+
+static PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
+        if (!policy)
+                return NULL;
+
+        return typesafe_bsearch(
+                        &(PartitionPolicy) { .designator = designator },
+                        ASSERT_PTR(policy)->policies,
+                        ASSERT_PTR(policy)->n_policies,
+                        partition_policy_compare);
+}
+
+static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
+        PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
+
+        /* This normalizes the per-partition policy flags. This means if the user left some things
+         * unspecified, we'll fill in the appropriate "dontcare" policy instead. We'll also mask out bits
+         * that do not make any sense for specific partition types. */
+
+        /* If no protection flag is set, then this means all are set */
+        if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
+                flags |= PARTITION_POLICY_OPEN;
+
+        /* If this is a verity or verity signature designator, then mask off all protection bits, this after
+         * all needs no protection, because it *is* the protection */
+        if (partition_verity_to_data(policy->designator) >= 0 ||
+            partition_verity_sig_to_data(policy->designator) >= 0)
+                flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED);
+
+        /* if this designator has no verity concept, then mask off verity protection flags */
+        if (partition_verity_of(policy->designator) < 0)
+                flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);
+
+        if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
+                /* If the partition must be absent, then the gpt flags don't matter */
+                flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);
+        else {
+                /* If the gpt flags bits are not specified, set both options for each */
+                if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
+                        flags |= PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_READ_ONLY_OFF;
+                if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
+                        flags |= PARTITION_POLICY_GROWFS_ON|PARTITION_POLICY_GROWFS_OFF;
+        }
+
+        return flags;
+}
+
+PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
+        PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
+        PartitionPolicy *pp;
+
+        /* No policy means: everything may be used in any mode */
+        if (!policy)
+                return partition_policy_normalized_flags(
+                                &(const PartitionPolicy) {
+                                        .flags = PARTITION_POLICY_OPEN,
+                                        .designator = designator,
+                                });
+
+        pp = image_policy_bsearch(policy, designator);
+        if (pp)
+                return partition_policy_normalized_flags(pp);
+
+        /* Hmm, so this didn't work, then let's see if we can derive some policy from the underlying data
+         * partition in case of verity/signature partitions */
+
+        data_designator = partition_verity_to_data(designator);
+        if (data_designator >= 0) {
+                PartitionPolicyFlags data_flags;
+
+                /* So we are asked for the policy for a verity partition, and there's no explicit policy for
+                 * that case. Let's synthesize a policy from the protection setting for the underlying data
+                 * partition. */
+
+                data_flags = image_policy_get(policy, data_designator);
+                if (data_flags < 0)
+                        return data_flags;
+
+                /* We need verity if verity or verity with sig is requested */
+                if (!(data_flags & (PARTITION_POLICY_SIGNED|PARTITION_POLICY_VERITY)))
+                        return _PARTITION_POLICY_FLAGS_INVALID;
+
+                /* If the data partition may be unused or absent, then the verity partition may too. Also, inherit the partition flags policy */
+                return partition_policy_normalized_flags(
+                                &(const PartitionPolicy) {
+                                        .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
+                                                 (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
+                                        .designator = designator,
+                                });
+        }
+
+        data_designator = partition_verity_sig_to_data(designator);
+        if (data_designator >= 0) {
+                PartitionPolicyFlags data_flags;
+
+                /* Similar case as for verity partitions, but slightly more strict rules */
+
+                data_flags = image_policy_get(policy, data_designator);
+                if (data_flags < 0)
+                        return data_flags;
+
+                if (!(data_flags & PARTITION_POLICY_SIGNED))
+                        return _PARTITION_POLICY_FLAGS_INVALID;
+
+                return partition_policy_normalized_flags(
+                                &(const PartitionPolicy) {
+                                        .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
+                                                 (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
+                                        .designator = designator,
+                                });
+        }
+
+        return _PARTITION_POLICY_FLAGS_INVALID; /* got nothing */
+}
+
+PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator) {
+        PartitionPolicyFlags flags;
+
+        /* This is just like image_policy_get() but whenever there is no policy for a specific designator, we
+         * return the default policy. */
+
+        flags = image_policy_get(policy, designator);
+        if (flags < 0)
+                return partition_policy_normalized_flags(
+                                &(const PartitionPolicy) {
+                                        .flags = image_policy_default(policy),
+                                        .designator = designator,
+                                });
+
+        return flags;
+}
+
+static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
+        assert(s);
+
+        /* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */
+
+        if (streq(s, "verity"))
+                return PARTITION_POLICY_VERITY;
+        if (streq(s, "signed"))
+                return PARTITION_POLICY_SIGNED;
+        if (streq(s, "encrypted"))
+                return PARTITION_POLICY_ENCRYPTED;
+        if (streq(s, "unprotected"))
+                return PARTITION_POLICY_UNPROTECTED;
+        if (streq(s, "unused"))
+                return PARTITION_POLICY_UNUSED;
+        if (streq(s, "absent"))
+                return PARTITION_POLICY_ABSENT;
+        if (streq(s, "open")) /* shortcut alias */
+                return PARTITION_POLICY_OPEN;
+        if (streq(s, "ignore")) /* ditto */
+                return PARTITION_POLICY_IGNORE;
+        if (streq(s, "read-only-on"))
+                return PARTITION_POLICY_READ_ONLY_ON;
+        if (streq(s, "read-only-off"))
+                return PARTITION_POLICY_READ_ONLY_OFF;
+        if (streq(s, "growfs-on"))
+                return PARTITION_POLICY_GROWFS_ON;
+        if (streq(s, "growfs-off"))
+                return PARTITION_POLICY_GROWFS_OFF;
+
+        return _PARTITION_POLICY_FLAGS_INVALID;
+}
+
+PartitionPolicyFlags partition_policy_flags_from_string(const char *s) {
+        PartitionPolicyFlags flags = 0;
+        int r;
+
+        assert(s);
+
+        if (empty_or_dash(s))
+                return 0;
+
+        for (;;) {
+                _cleanup_free_ char *f = NULL;
+                PartitionPolicyFlags ff;
+
+                r = extract_first_word(&s, &f, "+", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                ff = policy_flag_from_string_one(strstrip(f));
+                if (ff < 0)
+                        return -EBADRQC; /* recognizable error */
+
+                flags |= ff;
+        }
+
+        return flags;
+}
+
+static ImagePolicy* image_policy_new(size_t n_policies) {
+        ImagePolicy *p;
+
+        if (n_policies > (SIZE_MAX - offsetof(ImagePolicy, policies)) / sizeof(PartitionPolicy)) /* overflow check */
+                return NULL;
+
+        p = malloc(offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * n_policies);
+        if (!p)
+                return NULL;
+
+        *p = (ImagePolicy) {
+                .default_flags = PARTITION_POLICY_IGNORE,
+        };
+        return p;
+}
+
+int image_policy_from_string(const char *s, ImagePolicy **ret) {
+        _cleanup_free_ ImagePolicy *p = NULL;
+        uint64_t dmask = 0;
+        ImagePolicy *t;
+        PartitionPolicyFlags symbolic_policy;
+        int r;
+
+        assert(s);
+        assert_cc(sizeof(dmask) * 8 >= _PARTITION_DESIGNATOR_MAX);
+
+        /* Recognizable errors:
+         *
+         *     ENOTUNIQ → Two or more rules for the same partition
+         *     EBADSLT  → Unknown partition designator
+         *     EBADRQC  → Unknown policy flags
+         */
+
+        /* First, let's handle "symbolic" policies, i.e. "-", "*", "~" */
+        if (empty_or_dash(s))
+                /* ignore policy: everything may exist, but nothing used */
+                symbolic_policy = PARTITION_POLICY_IGNORE;
+        else if (streq(s, "*"))
+                /* allow policy: everything is allowed */
+                symbolic_policy = PARTITION_POLICY_OPEN;
+        else if (streq(s, "~"))
+                /* deny policy: nothing may exist */
+                symbolic_policy = PARTITION_POLICY_ABSENT;
+        else
+                symbolic_policy = _PARTITION_POLICY_FLAGS_INVALID;
+
+        if (symbolic_policy >= 0) {
+                if (!ret)
+                        return 0;
+
+                p = image_policy_new(0);
+                if (!p)
+                        return -ENOMEM;
+
+                p->default_flags = symbolic_policy;
+                *ret = TAKE_PTR(p);
+                return 0;
+        }
+
+        /* Allocate the policy at maximum size, i.e. for all designators. We might overshoot a bit, but the
+         * items are cheap, and we can return unused space to libc once we know we don't need it */
+        p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
+        if (!p)
+                return -ENOMEM;
+
+        const char *q = s;
+        bool default_specified = false;
+        for (;;) {
+                _cleanup_free_ char *e = NULL, *d = NULL;
+                PartitionDesignator designator;
+                PartitionPolicyFlags flags;
+                char *f, *ds, *fs;
+
+                r = extract_first_word(&q, &e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                f = e;
+                r = extract_first_word((const char**) &f, &d, "=", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected designator name followed by '='; got instead: %s", e);
+                if (!f) /* no separator? */
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in policy expression: %s", e);
+
+                ds = strstrip(d);
+                if (isempty(ds)) {
+                        /* Not partition name? then it's the default policy */
+                        if (default_specified)
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Default partition policy flags specified more than once.");
+
+                        designator = _PARTITION_DESIGNATOR_INVALID;
+                        default_specified = true;
+                } else {
+                        designator = partition_designator_from_string(ds);
+                        if (designator < 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Unknown partition designator: %s", ds); /* recognizable error */
+                        if (dmask & (UINT64_C(1) << designator))
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition designator specified more than once: %s", ds);
+                        dmask |= UINT64_C(1) << designator;
+                }
+
+                fs = strstrip(f);
+                flags = partition_policy_flags_from_string(fs);
+                if (flags == -EBADRQC)
+                        return log_debug_errno(flags, "Unknown partition policy flag: %s", fs);
+                if (flags < 0)
+                        return log_debug_errno(flags, "Failed to parse partition policy flags '%s': %m", fs);
+
+                if (designator < 0)
+                        p->default_flags = flags;
+                else {
+                        p->policies[p->n_policies++] = (PartitionPolicy) {
+                                .designator = designator,
+                                .flags = flags,
+                        };
+                }
+        };
+
+        assert(p->n_policies <= _PARTITION_DESIGNATOR_MAX);
+
+        /* Return unused space to libc */
+        t = realloc(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies);
+        if (t)
+                p = t;
+
+        typesafe_qsort(p->policies, p->n_policies, partition_policy_compare);
+
+        if (ret)
+                *ret = TAKE_PTR(p);
+
+        return 0;
+}
+
+int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret) {
+        _cleanup_free_ char *buf = NULL;
+        const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK) + 1]; /* one string per known flag at most */
+        size_t m = 0;
+
+        assert(ret);
+
+        if (flags < 0)
+                return -EINVAL;
+
+        /* If 'simplify' is false we'll output the precise value of every single flag.
+         *
+         * If 'simplify' is true we'll try to make the output shorter, by doing the following:
+         *
+         *     → we'll spell the long form "verity+signed+encrypted+unprotected+unused+absent" via its
+         *       equivalent shortcut form "open" (which we happily parse btw, see above)
+         *
+         *     → we'll spell the long form "unused+absent" via its shortcut "ignore" (which we are also happy
+         *       to parse)
+         *
+         *     → if the read-only/growfs policy flags are both set, we suppress them. this thus removes the
+         *       distinction between "user explicitly declared don't care" and "we implied don't care because
+         *       user didn't say anything".
+         *
+         * net result: the resulting string is shorter, but the effective policy declared that way will have
+         * the same results as the long form. */
+
+        if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_OPEN)
+                l[m++] = "open";
+        else if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE)
+                l[m++] = "ignore";
+        else {
+                if (flags & PARTITION_POLICY_VERITY)
+                        l[m++] = "verity";
+                if (flags & PARTITION_POLICY_SIGNED)
+                        l[m++] = "signed";
+                if (flags & PARTITION_POLICY_ENCRYPTED)
+                        l[m++] = "encrypted";
+                if (flags & PARTITION_POLICY_UNPROTECTED)
+                        l[m++] = "unprotected";
+                if (flags & PARTITION_POLICY_UNUSED)
+                        l[m++] = "unused";
+                if (flags & PARTITION_POLICY_ABSENT)
+                        l[m++] = "absent";
+        }
+
+        if (!simplify || (!(flags & PARTITION_POLICY_READ_ONLY_ON) != !(flags & PARTITION_POLICY_READ_ONLY_OFF))) {
+                if (flags & PARTITION_POLICY_READ_ONLY_ON)
+                        l[m++] = "read-only-on";
+                if (flags & PARTITION_POLICY_READ_ONLY_OFF)
+                        l[m++] = "read-only-off";
+        }
+
+        if (!simplify || (!(flags & PARTITION_POLICY_GROWFS_ON) != !(flags & PARTITION_POLICY_GROWFS_OFF))) {
+                if (flags & PARTITION_POLICY_GROWFS_OFF)
+                        l[m++] = "growfs-off";
+                if (flags & PARTITION_POLICY_GROWFS_ON)
+                        l[m++] = "growfs-on";
+        }
+
+        if (m == 0)
+                buf = strdup("-");
+        else {
+                assert(m+1 < ELEMENTSOF(l));
+                l[m] = NULL;
+
+                buf = strv_join((char**) l, "+");
+        }
+        if (!buf)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(buf);
+        return 0;
+}
+
+static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {
+
+        if (expected < 0)
+                return -EINVAL;
+
+        if (image_policy_default(policy) != expected)
+                return false;
+
+        for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+                PartitionPolicyFlags f, w;
+
+                f = image_policy_get_exhaustively(policy, d);
+                if (f < 0)
+                        return f;
+
+                w = partition_policy_normalized_flags(
+                                &(const PartitionPolicy) {
+                                        .flags = expected,
+                                        .designator = d,
+                                });
+                if (w < 0)
+                        return w;
+                if (f != w)
+                        return false;
+        }
+
+        return true;
+}
+
+bool image_policy_equiv_ignore(const ImagePolicy *policy) {
+        /* Checks if this is the ignore policy (or equivalent to it), i.e. everything is ignored, aka '-', aka '' */
+        return image_policy_flags_all_match(policy, PARTITION_POLICY_IGNORE);
+}
+
+bool image_policy_equiv_allow(const ImagePolicy *policy) {
+        /* Checks if this is the allow policy (or equivalent to it), i.e. everything is allowed, aka '*' */
+        return image_policy_flags_all_match(policy, PARTITION_POLICY_OPEN);
+}
+
+bool image_policy_equiv_deny(const ImagePolicy *policy) {
+        /* Checks if this is the deny policy (or equivalent to it), i.e. everything must be absent, aka '~' */
+        return image_policy_flags_all_match(policy, PARTITION_POLICY_ABSENT);
+}
+
+int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret) {
+        _cleanup_free_ char *s = NULL;
+        int r;
+
+        assert(ret);
+
+        if (simplify) {
+                const char *fixed;
+
+                if (image_policy_equiv_allow(policy))
+                        fixed = "*";
+                else if (image_policy_equiv_ignore(policy))
+                        fixed = "-";
+                else if (image_policy_equiv_deny(policy))
+                        fixed = "~";
+                else
+                        fixed = NULL;
+
+                if (fixed) {
+                        s = strdup(fixed);
+                        if (!s)
+                                return -ENOMEM;
+
+                        *ret = TAKE_PTR(s);
+                        return 0;
+                }
+        }
+
+        for (size_t i = 0; i < image_policy_n_entries(policy); i++) {
+                const PartitionPolicy *p = policy->policies + i;
+                _cleanup_free_ char *f = NULL;
+                const char *t;
+
+                assert(i == 0 || p->designator > policy->policies[i-1].designator); /* Validate perfect ordering */
+
+                assert_se(t = partition_designator_to_string(p->designator));
+
+                if (simplify) {
+                        /* Skip policy entries that match the default anyway */
+                        PartitionPolicyFlags df;
+
+                        df = partition_policy_normalized_flags(
+                                        &(const PartitionPolicy) {
+                                                .flags = image_policy_default(policy),
+                                                .designator = p->designator,
+                                        });
+                        if (df < 0)
+                                return df;
+
+                        if (df == p->flags)
+                                continue;
+                }
+
+                r = partition_policy_flags_to_string(p->flags, simplify, &f);
+                if (r < 0)
+                        return r;
+
+                if (!strextend(&s, isempty(s) ? "" : ":", t, "=", f))
+                        return -ENOMEM;
+        }
+
+        if (!simplify || image_policy_default(policy) != PARTITION_POLICY_IGNORE) {
+                _cleanup_free_ char *df = NULL;
+
+                r = partition_policy_flags_to_string(image_policy_default(policy), simplify, &df);
+                if (r < 0)
+                        return r;
+
+                if (!strextend(&s, isempty(s) ? "" : ":", "=", df))
+                        return -ENOMEM;
+        }
+
+        if (isempty(s)) { /* no rule and default policy? then let's return "-" */
+                s = strdup("-");
+                if (!s)
+                        return -ENOMEM;
+        }
+
+        *ret = TAKE_PTR(s);
+        return 0;
+}
+
+bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b) {
+        if (a == b)
+                return true;
+        if (image_policy_n_entries(a) != image_policy_n_entries(b))
+                return false;
+        if (image_policy_default(a) != image_policy_default(b))
+                return false;
+        for (size_t i = 0; i < image_policy_n_entries(a); i++) {
+                if (a->policies[i].designator != b->policies[i].designator)
+                        return false;
+                if (a->policies[i].flags != b->policies[i].flags)
+                        return false;
+        }
+
+        return true;
+}
+
+int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b) {
+
+        /* The image_policy_equal() function checks if the policy is defined the exact same way. This
+         * function here instead looks at the outcome of the two policies instead. Where does this come to
+         * different results you ask? We imply some logic regarding Verity/Encryption: when no rule is
+         * defined for a verity partition we can synthesize it from the protection level of the data
+         * partition it protects. Or: any per-partition rule that is identical to the default rule is
+         * redundant, and will be recognized as such by image_policy_equivalent() but not by
+         * image_policy_equal()- */
+
+        if (image_policy_default(a) != image_policy_default(b))
+                return false;
+
+        for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+                PartitionPolicyFlags f, w;
+
+                f = image_policy_get_exhaustively(a, d);
+                if (f < 0)
+                        return f;
+
+                w = image_policy_get_exhaustively(b, d);
+                if (w < 0)
+                        return w;
+
+                if (f != w)
+                        return false;
+        }
+
+        return true;
+}
+
+int config_parse_image_policy(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(image_policy_freep) ImagePolicy *np = NULL;
+        ImagePolicy **p = ASSERT_PTR(data);
+        int r;
+
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                *p = image_policy_free(*p);
+                return 0;
+        }
+
+        r = image_policy_from_string(rvalue, &np);
+        if (r == -ENOTUNIQ)
+                return log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate rule in image policy, refusing: %s", rvalue);
+        if (r == -EBADSLT)
+                return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition type in image policy, refusing: %s", rvalue);
+        if (r == -EBADRQC)
+                return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition policy flag in image policy, refusing: %s", rvalue);
+        if (r < 0)
+                return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse image policy, refusing: %s", rvalue);
+
+        return free_and_replace_full(*p, np, image_policy_free);
+}
+
+int parse_image_policy_argument(const char *s, ImagePolicy **policy) {
+        _cleanup_(image_policy_freep) ImagePolicy *np = NULL;
+        int r;
+
+        assert(s);
+        assert(policy);
+
+        /*
+         * This function is intended to be used in command line parsers.
+         *
+         * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON SUCCESS!
+         * Hence, do not pass in uninitialized pointers.
+         */
+
+        r = image_policy_from_string(s, &np);
+        if (r == -ENOTUNIQ)
+                return log_error_errno(r, "Duplicate rule in image policy: %s", s);
+        if (r == -EBADSLT)
+                return log_error_errno(r, "Unknown partition type in image policy: %s", s);
+        if (r == -EBADRQC)
+                return log_error_errno(r, "Unknown partition policy flag in image policy: %s", s);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse image policy: %s", s);
+
+        return free_and_replace_full(*policy, np, image_policy_free);
+}
+
+const ImagePolicy image_policy_allow = {
+        /* Allow policy */
+        .n_policies = 0,
+        .default_flags = PARTITION_POLICY_OPEN,
+};
+
+const ImagePolicy image_policy_deny = {
+        /* Allow policy */
+        .n_policies = 0,
+        .default_flags = PARTITION_POLICY_ABSENT,
+};
+
+const ImagePolicy image_policy_ignore = {
+        /* Allow policy */
+        .n_policies = 0,
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_sysext = {
+        /* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
+         * we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
+         * be. */
+        .n_policies = 2,
+        .policies = {
+                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+        },
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_sysext_strict = {
+        /* For system extensions, requiring signing */
+        .n_policies = 2,
+        .policies = {
+                { PARTITION_ROOT,     PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+                { PARTITION_USR,      PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+        },
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_confext = {
+        /* For configuration extensions, honour root file system, and ignore everything else. After all, we
+         * are only interested in the /etc/ tree anyway, and that's really the only place it can be. */
+        .n_policies = 1,
+        .policies = {
+                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+        },
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_container = {
+        /* For systemd-nspawn containers we use all partitions, with the exception of swap */
+        .n_policies = 8,
+        .policies = {
+                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_HOME,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_SRV,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_TMP,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_VAR,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+        },
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_host = {
+        /* For the host policy we basically use everything */
+        .n_policies = 9,
+        .policies = {
+                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_HOME,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_SRV,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_ESP,      PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_SWAP,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_TMP,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_VAR,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+        },
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_service = {
+        /* For RootImage= in services we skip ESP/XBOOTLDR and swap */
+        .n_policies = 6,
+        .policies = {
+                { PARTITION_ROOT,     PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_USR,      PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_HOME,     PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_SRV,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_TMP,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+                { PARTITION_VAR,      PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+        },
+        .default_flags = PARTITION_POLICY_IGNORE,
+};
diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h
new file mode 100644 (file)
index 0000000..1b3d068
--- /dev/null
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct ImagePolicy ImagePolicy;
+
+#include "conf-parser.h"
+#include "dissect-image.h"
+#include "errno-list.h"
+
+typedef enum PartitionPolicyFlags {
+        /* Not all policy flags really make sense on all partition types, see comments. But even if they
+         * don't make sense we'll parse them anyway, because maybe one day we'll add them for more partition
+         * types, too. Moreover, we allow configuring a "default" policy for all partition types for which no
+         * explicit policy is specified. It's useful if we can use policy flags in there and apply this
+         * default policy gracefully even to partition types where they don't really make too much sense
+         * on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/
+         * partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for
+         * /home/), and so on. */
+        PARTITION_POLICY_VERITY               = 1 << 0, /* must exist, activate with verity                 (only applies to root/usr partitions) */
+        PARTITION_POLICY_SIGNED               = 1 << 1, /* must exist, activate with signed verity          (only applies to root/usr partitions) */
+        PARTITION_POLICY_ENCRYPTED            = 1 << 2, /* must exist, activate with LUKS encryption        (applies to any data partition, but not to verity/signature partitions */
+        PARTITION_POLICY_UNPROTECTED          = 1 << 3, /* must exist, activate without encryption/verity */
+        PARTITION_POLICY_UNUSED               = 1 << 4, /* must exist, don't use */
+        PARTITION_POLICY_ABSENT               = 1 << 5, /* must not exist */
+        PARTITION_POLICY_OPEN                 = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|
+                                                PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
+        PARTITION_POLICY_IGNORE               = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
+        _PARTITION_POLICY_USE_MASK            = PARTITION_POLICY_OPEN,
+
+        PARTITION_POLICY_READ_ONLY_OFF        = 1 << 6, /* State of GPT partition flag "read-only" must be on */
+        PARTITION_POLICY_READ_ONLY_ON         = 1 << 7,
+        _PARTITION_POLICY_READ_ONLY_MASK      = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON,
+        PARTITION_POLICY_GROWFS_OFF           = 1 << 8, /* State of GPT partition flag "growfs" must be on */
+        PARTITION_POLICY_GROWFS_ON            = 1 << 9,
+        _PARTITION_POLICY_GROWFS_MASK         = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON,
+        _PARTITION_POLICY_PFLAGS_MASK         = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
+
+        _PARTITION_POLICY_MASK                = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
+
+        _PARTITION_POLICY_FLAGS_INVALID       = -EINVAL,
+        _PARTITION_POLICY_FLAGS_ERRNO_MAX     = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
+} PartitionPolicyFlags;
+
+assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */
+
+typedef struct PartitionPolicy {
+        PartitionDesignator designator;
+        PartitionPolicyFlags flags;
+} PartitionPolicy;
+
+struct ImagePolicy {
+        PartitionPolicyFlags default_flags;  /* for any designator not listed in the list below */
+        size_t n_policies;
+        PartitionPolicy policies[];          /* sorted by designator, hence suitable for binary search */
+};
+
+/* Default policies for various usecases */
+extern const ImagePolicy image_policy_allow;
+extern const ImagePolicy image_policy_deny;
+extern const ImagePolicy image_policy_ignore;
+extern const ImagePolicy image_policy_sysext;        /* No verity required */
+extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
+extern const ImagePolicy image_policy_confext;       /* No verity required */
+extern const ImagePolicy image_policy_container;
+extern const ImagePolicy image_policy_service;
+extern const ImagePolicy image_policy_host;
+
+PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator);
+PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator);
+
+/* We want that the NULL image policy means "everything" allowed, hence use these simple accessors to make
+ * NULL policies work reasonably */
+static inline PartitionPolicyFlags image_policy_default(const ImagePolicy *policy) {
+        return policy ? policy->default_flags : PARTITION_POLICY_OPEN;
+}
+
+static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
+        return policy ? policy->n_policies : 0;
+}
+
+PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
+int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
+
+int image_policy_from_string(const char *s, ImagePolicy **ret);
+int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret);
+
+/* Recognizes three special policies by equivalence */
+bool image_policy_equiv_ignore(const ImagePolicy *policy);
+bool image_policy_equiv_allow(const ImagePolicy *policy);
+bool image_policy_equiv_deny(const ImagePolicy *policy);
+
+bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b);       /* checks if defined the same way, i.e. has literally the same ruleset */
+int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b);   /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
+
+static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
+        return mfree(p);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_image_policy);
+int parse_image_policy_argument(const char *s, ImagePolicy **policy);
index 8060dcc251439e49d48a726171e58e284a6cef02..152e517ebc155fa4ef64ab898f5d22e5953442a5 100644 (file)
@@ -10,7 +10,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "conf-parser.h"
 #include "constants.h"
@@ -52,18 +52,22 @@ typedef struct {
         OrderedHashmap *have_processed;
 } InstallContext;
 
-typedef enum {
-        PRESET_UNKNOWN,
-        PRESET_ENABLE,
-        PRESET_DISABLE,
-} PresetAction;
-
 struct UnitFilePresetRule {
         char *pattern;
         PresetAction action;
         char **instances;
 };
 
+/* NB! strings use past tense. */
+static const char *const preset_action_past_tense_table[_PRESET_ACTION_MAX] = {
+        [PRESET_UNKNOWN] = "unknown",
+        [PRESET_ENABLE]  = "enabled",
+        [PRESET_DISABLE] = "disabled",
+        [PRESET_IGNORE]  = "ignored",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(preset_action_past_tense, PresetAction);
+
 static bool install_info_has_rules(const InstallInfo *i) {
         assert(i);
 
@@ -78,14 +82,19 @@ static bool install_info_has_also(const InstallInfo *i) {
         return !strv_isempty(i->also);
 }
 
-void unit_file_presets_freep(UnitFilePresets *p) {
+static void unit_file_preset_rule_done(UnitFilePresetRule *rule) {
+        assert(rule);
+
+        free(rule->pattern);
+        strv_free(rule->instances);
+}
+
+void unit_file_presets_done(UnitFilePresets *p) {
         if (!p)
                 return;
 
-        for (size_t i = 0; i < p->n_rules; i++) {
-                free(p->rules[i].pattern);
-                strv_free(p->rules[i].instances);
-        }
+        FOREACH_ARRAY(rule, p->rules, p->n_rules)
+                unit_file_preset_rule_done(rule);
 
         free(p->rules);
         p->n_rules = 0;
@@ -686,7 +695,7 @@ static int remove_marked_symlinks_fd(
                         if (!found) {
                                 _cleanup_free_ char *dest = NULL;
 
-                                q = chase_symlinks(p, lp->root_dir, CHASE_NONEXISTENT, &dest, NULL);
+                                q = chase(p, lp->root_dir, CHASE_NONEXISTENT, &dest, NULL);
                                 if (q == -ENOENT)
                                         continue;
                                 if (q < 0) {
@@ -1379,7 +1388,7 @@ static int unit_file_load(
                 if (!(flags & SEARCH_LOAD))
                         return 0;
 
-                fd = chase_symlinks_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+                fd = chase_and_open(path, root_dir, 0, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
                 if (fd < 0)
                         return fd;
         }
@@ -1918,7 +1927,7 @@ static int install_info_symlink_alias(
                 if (!alias_path)
                         return -ENOMEM;
 
-                q = chase_symlinks(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL);
+                q = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL);
                 if (q < 0 && q != -ENOENT) {
                         r = r < 0 ? r : q;
                         continue;
@@ -3215,7 +3224,7 @@ static int presets_find_config(RuntimeScope scope, const char *root_dir, char **
 }
 
 static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePresets *presets) {
-        _cleanup_(unit_file_presets_freep) UnitFilePresets ps = {};
+        _cleanup_(unit_file_presets_done) UnitFilePresets ps = {};
         _cleanup_strv_free_ char **files = NULL;
         int r;
 
@@ -3241,7 +3250,7 @@ static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePreset
 
                 for (;;) {
                         _cleanup_free_ char *line = NULL;
-                        UnitFilePresetRule rule = {};
+                        _cleanup_(unit_file_preset_rule_done) UnitFilePresetRule rule = {};
                         const char *parameter;
                         char *l;
 
@@ -3292,11 +3301,25 @@ static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePreset
                                 };
                         }
 
+                        parameter = first_word(l, "ignore");
+                        if (parameter) {
+                                char *pattern;
+
+                                pattern = strdup(parameter);
+                                if (!pattern)
+                                        return -ENOMEM;
+
+                                rule = (UnitFilePresetRule) {
+                                        .pattern = pattern,
+                                        .action = PRESET_IGNORE,
+                                };
+                        }
+
                         if (rule.action) {
                                 if (!GREEDY_REALLOC(ps.rules, ps.n_rules + 1))
                                         return -ENOMEM;
 
-                                ps.rules[ps.n_rules++] = rule;
+                                ps.rules[ps.n_rules++] = TAKE_STRUCT(rule);
                                 continue;
                         }
 
@@ -3305,8 +3328,7 @@ static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePreset
         }
 
         ps.initialized = true;
-        *presets = ps;
-        ps = (UnitFilePresets){};
+        *presets = TAKE_STRUCT(ps);
 
         return 0;
 }
@@ -3378,24 +3400,27 @@ static int query_presets(const char *name, const UnitFilePresets *presets, char
         switch (action) {
         case PRESET_UNKNOWN:
                 log_debug("Preset files don't specify rule for %s. Enabling.", name);
-                return 1;
+                return PRESET_ENABLE;
         case PRESET_ENABLE:
                 if (instance_name_list && *instance_name_list)
                         STRV_FOREACH(s, *instance_name_list)
                                 log_debug("Preset files say enable %s.", *s);
                 else
                         log_debug("Preset files say enable %s.", name);
-                return 1;
+                return PRESET_ENABLE;
         case PRESET_DISABLE:
                 log_debug("Preset files say disable %s.", name);
-                return 0;
+                return PRESET_DISABLE;
+        case PRESET_IGNORE:
+                log_debug("Preset files say ignore %s.", name);
+                return PRESET_IGNORE;
         default:
                 assert_not_reached();
         }
 }
 
-int unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
-        _cleanup_(unit_file_presets_freep) UnitFilePresets tmp = {};
+PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
+        _cleanup_(unit_file_presets_done) UnitFilePresets tmp = {};
         int r;
 
         if (!cached)
@@ -3488,7 +3513,7 @@ static int preset_prepare_one(
         if (r < 0)
                 return r;
 
-        if (r > 0) {
+        if (r == PRESET_ENABLE) {
                 if (instance_name_list)
                         STRV_FOREACH(s, instance_name_list) {
                                 r = install_info_discover_and_check(plus, lp, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
@@ -3503,7 +3528,7 @@ static int preset_prepare_one(
                                 return r;
                 }
 
-        } else
+        } else if (r == PRESET_DISABLE)
                 r = install_info_discover(minus, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
                                           &info, changes, n_changes);
 
@@ -3521,7 +3546,7 @@ int unit_file_preset(
 
         _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
         _cleanup_(lookup_paths_free) LookupPaths lp = {};
-        _cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
+        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
         const char *config_path;
         int r;
 
@@ -3560,7 +3585,7 @@ int unit_file_preset_all(
 
         _cleanup_(install_context_done) InstallContext plus = {}, minus = {};
         _cleanup_(lookup_paths_free) LookupPaths lp = {};
-        _cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
+        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
         const char *config_path = NULL;
         int r;
 
@@ -3611,7 +3636,7 @@ int unit_file_preset_all(
         return execute_preset(file_flags, &plus, &minus, &lp, config_path, NULL, mode, changes, n_changes);
 }
 
-static UnitFileList* unit_file_list_free_one(UnitFileList *f) {
+static UnitFileList* unit_file_list_free(UnitFileList *f) {
         if (!f)
                 return NULL;
 
@@ -3619,11 +3644,15 @@ static UnitFileList* unit_file_list_free_one(UnitFileList *f) {
         return mfree(f);
 }
 
-Hashmap* unit_file_list_free(Hashmap *h) {
-        return hashmap_free_with_destructor(h, unit_file_list_free_one);
-}
+DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free);
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+        unit_file_list_hash_ops_free,
+        char,
+        string_hash_func,
+        string_compare_func,
+        UnitFileList,
+        unit_file_list_free);
 
 int unit_file_get_list(
                 RuntimeScope scope,
@@ -3659,7 +3688,7 @@ int unit_file_get_list(
                 }
 
                 FOREACH_DIRENT(de, d, return -errno) {
-                        _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL;
+                        _cleanup_(unit_file_list_freep) UnitFileList *f = NULL;
 
                         if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
                                 continue;
index 0f9724f99935f3f3d3871ef92d27f972fe00abb8..30b07a725fe028ce5be9cfcffca11110a6473e58 100644 (file)
@@ -195,7 +195,8 @@ int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *fi
 int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name);
 
 int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
-Hashmap* unit_file_list_free(Hashmap *h);
+
+extern const struct hash_ops unit_file_list_hash_ops_free;
 
 InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source);
 void install_changes_free(InstallChange *changes, size_t n_changes);
@@ -216,8 +217,20 @@ typedef struct {
         bool initialized;
 } UnitFilePresets;
 
-void unit_file_presets_freep(UnitFilePresets *p);
-int unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached);
+typedef enum PresetAction {
+        PRESET_UNKNOWN,
+        PRESET_ENABLE,
+        PRESET_DISABLE,
+        PRESET_IGNORE,
+        _PRESET_ACTION_MAX,
+        _PRESET_ACTION_INVALID = -EINVAL,
+        _PRESET_ACTION_ERRNO_MAX = -ERRNO_MAX, /* Ensure this type covers the whole negative errno range */
+} PresetAction;
+
+const char *preset_action_past_tense_to_string(PresetAction action);
+
+void unit_file_presets_done(UnitFilePresets *p);
+PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached);
 
 const char *unit_file_state_to_string(UnitFileState s) _const_;
 UnitFileState unit_file_state_from_string(const char *s) _pure_;
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 6ed9a6890f5e73cba0bfc1a2f01f15350147198e..d372e096c494afd3f8d606307909183852e02d31 100644 (file)
@@ -93,14 +93,9 @@ static int url_from_catalog(sd_journal *j, char **ret) {
         if (r < 0)
                 return log_error_errno(r, "Failed to find catalog entry: %m");
 
-        weblink = startswith(t, "Documentation:");
-        if (!weblink) {
-                weblink = strstr(t + 1, "\nDocumentation:");
-                if (!weblink)
-                        goto notfound;
-
-                weblink += 15;
-        }
+        weblink = find_line_startswith(t, "Documentation:");
+        if (!weblink)
+                goto notfound;
 
         /* Skip whitespace to value */
         weblink += strspn(weblink, " \t");
index d6b8d086ffbe87d7737986a9ec0bf61bc1972ad6..a85cff09142b2dcdbd758c8b02f3283a6d5c89d8 100644 (file)
@@ -24,6 +24,7 @@
 #include "env-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
+#include "fs-util.h"
 #include "fileio.h"
 #include "loop-util.h"
 #include "missing_loop.h"
@@ -46,9 +47,7 @@ static void cleanup_clear_loop_close(int *fd) {
 static int loop_is_bound(int fd) {
         struct loop_info64 info;
 
-        assert(fd >= 0);
-
-        if (ioctl(fd, LOOP_GET_STATUS64, &info) < 0) {
+        if (ioctl(ASSERT_FD(fd), LOOP_GET_STATUS64, &info) < 0) {
                 if (errno == ENXIO)
                         return false; /* not bound! */
 
@@ -76,10 +75,9 @@ static int get_current_uevent_seqnum(uint64_t *ret) {
 static int open_lock_fd(int primary_fd, int operation) {
         _cleanup_close_ int lock_fd = -EBADF;
 
-        assert(primary_fd >= 0);
         assert(IN_SET(operation & ~LOCK_NB, LOCK_SH, LOCK_EX));
 
-        lock_fd = fd_reopen(primary_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+        lock_fd = fd_reopen(ASSERT_FD(primary_fd), O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
         if (lock_fd < 0)
                 return lock_fd;
 
@@ -439,11 +437,10 @@ static int loop_device_make_internal(
         int r, f_flags;
         struct stat st;
 
-        assert(fd >= 0);
         assert(ret);
         assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
 
-        if (fstat(fd, &st) < 0)
+        if (fstat(ASSERT_FD(fd), &st) < 0)
                 return -errno;
 
         if (S_ISBLK(st.st_mode)) {
@@ -650,7 +647,8 @@ int loop_device_make(
                         ret);
 }
 
-int loop_device_make_by_path(
+int loop_device_make_by_path_at(
+                int dir_fd,
                 const char *path,
                 int open_flags,
                 uint32_t sector_size,
@@ -662,6 +660,7 @@ int loop_device_make_by_path(
         _cleanup_close_ int fd = -EBADF;
         bool direct = false;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(path);
         assert(ret);
         assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY));
@@ -678,9 +677,9 @@ int loop_device_make_by_path(
         direct_flags = FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO) ? O_DIRECT : 0;
         rdwr_flags = open_flags >= 0 ? open_flags : O_RDWR;
 
-        fd = open(path, basic_flags|direct_flags|rdwr_flags);
+        fd = xopenat(dir_fd, path, basic_flags|direct_flags|rdwr_flags, 0);
         if (fd < 0 && direct_flags != 0) /* If we had O_DIRECT on, and things failed with that, let's immediately try again without */
-                fd = open(path, basic_flags|rdwr_flags);
+                fd = xopenat(dir_fd, path, basic_flags|rdwr_flags, 0);
         else
                 direct = direct_flags != 0;
         if (fd < 0) {
@@ -690,9 +689,9 @@ int loop_device_make_by_path(
                 if (open_flags >= 0 || !(ERRNO_IS_PRIVILEGE(r) || r == -EROFS))
                         return r;
 
-                fd = open(path, basic_flags|direct_flags|O_RDONLY);
+                fd = xopenat(dir_fd, path, basic_flags|direct_flags|O_RDONLY, 0);
                 if (fd < 0 && direct_flags != 0) /* as above */
-                        fd = open(path, basic_flags|O_RDONLY);
+                        fd = xopenat(dir_fd, path, basic_flags|O_RDONLY, 0);
                 else
                         direct = direct_flags != 0;
                 if (fd < 0)
@@ -709,7 +708,16 @@ int loop_device_make_by_path(
                   direct ? "enabled" : "disabled",
                   direct != (direct_flags != 0) ? " (O_DIRECT was requested but not supported)" : "");
 
-        return loop_device_make_internal(path, fd, open_flags, 0, 0, sector_size, loop_flags, lock_op, ret);
+        return loop_device_make_internal(
+                        dir_fd == AT_FDCWD ? path : NULL,
+                        fd,
+                        open_flags,
+                        /* offset = */ 0,
+                        /* size = */ 0,
+                        sector_size,
+                        loop_flags,
+                        lock_op,
+                        ret);
 }
 
 int loop_device_make_by_path_memory(
@@ -949,9 +957,7 @@ int loop_device_open_from_fd(
         _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
         int r;
 
-        assert(fd >= 0);
-
-        r = block_device_new_from_fd(fd, 0, &dev);
+        r = block_device_new_from_fd(ASSERT_FD(fd), 0, &dev);
         if (r < 0)
                 return r;
 
@@ -985,13 +991,11 @@ static int resize_partition(int partition_fd, uint64_t offset, uint64_t size) {
         dev_t devno;
         int r;
 
-        assert(partition_fd >= 0);
-
         /* Resizes the partition the loopback device refer to (assuming it refers to one instead of an actual
          * loopback device), and changes the offset, if needed. This is a fancy wrapper around
          * BLKPG_RESIZE_PARTITION. */
 
-        if (fstat(partition_fd, &st) < 0)
+        if (fstat(ASSERT_FD(partition_fd), &st) < 0)
                 return -errno;
 
         assert(S_ISBLK(st.st_mode));
@@ -1096,9 +1100,7 @@ int loop_device_flock(LoopDevice *d, int operation) {
 
         /* If we had no lock fd so far, create one and lock it right-away */
         if (d->lock_fd < 0) {
-                assert(d->fd >= 0);
-
-                d->lock_fd = open_lock_fd(d->fd, operation);
+                d->lock_fd = open_lock_fd(ASSERT_FD(d->fd), operation);
                 if (d->lock_fd < 0)
                         return d->lock_fd;
 
@@ -1111,12 +1113,11 @@ int loop_device_flock(LoopDevice *d, int operation) {
 
 int loop_device_sync(LoopDevice *d) {
         assert(d);
-        assert(d->fd >= 0);
 
         /* We also do this implicitly in loop_device_unref(). Doing this explicitly here has the benefit that
          * we can check the return value though. */
 
-        return RET_NERRNO(fsync(d->fd));
+        return RET_NERRNO(fsync(ASSERT_FD(d->fd)));
 }
 
 int loop_device_set_autoclear(LoopDevice *d, bool autoclear) {
@@ -1124,7 +1125,7 @@ int loop_device_set_autoclear(LoopDevice *d, bool autoclear) {
 
         assert(d);
 
-        if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0)
+        if (ioctl(ASSERT_FD(d->fd), LOOP_GET_STATUS64, &info) < 0)
                 return -errno;
 
         if (autoclear == FLAGS_SET(info.lo_flags, LO_FLAGS_AUTOCLEAR))
@@ -1155,7 +1156,7 @@ int loop_device_set_filename(LoopDevice *d, const char *name) {
         if (name && strlen(name) >= sizeof(info.lo_file_name))
                 return -ENOBUFS;
 
-        if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0)
+        if (ioctl(ASSERT_FD(d->fd), LOOP_GET_STATUS64, &info) < 0)
                 return -errno;
 
         if (strneq((char*) info.lo_file_name, strempty(name), sizeof(info.lo_file_name)))
index dda14ec4f01c6a142891c10f52d8543938cd60c9..d77c314af83530b997ddcf0a69f5c3b6219335da 100644 (file)
@@ -1,6 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
+
 #include "sd-device.h"
 
 #include "macro.h"
@@ -32,7 +34,10 @@ struct LoopDevice {
 #define LOOP_DEVICE_IS_FOREIGN(d) ((d)->nr < 0)
 
 int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
-int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+int loop_device_make_by_path_at(int dir_fd, const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
+static inline int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) {
+        return loop_device_make_by_path_at(AT_FDCWD, path, open_flags, sector_size, loop_flags, lock_op, ret);
+}
 int loop_device_make_by_path_memory(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret);
 int loop_device_open(sd_device *dev, int open_flags, int lock_op, LoopDevice **ret);
 int loop_device_open_from_fd(int fd, int open_flags, int lock_op, LoopDevice **ret);
diff --git a/src/shared/lsm-util.c b/src/shared/lsm-util.c
new file mode 100644 (file)
index 0000000..7b6d419
--- /dev/null
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "fileio.h"
+#include "lsm-util.h"
+#include "string-util.h"
+
+int lsm_supported(const char *name) {
+        _cleanup_free_ char *lsm_list = NULL;
+        int r;
+
+        assert(name);
+
+        r = read_one_line_file("/sys/kernel/security/lsm", &lsm_list);
+        if (r == -ENOENT) /* LSM support not available at all? */
+                return false;
+        if (r < 0)
+                return log_debug_errno(r, "Failed to read /sys/kernel/security/lsm: %m");
+
+        for (const char *p = lsm_list;;) {
+                _cleanup_free_ char *word = NULL;
+
+                r = extract_first_word(&p, &word, ",", 0);
+                if (r == 0)
+                        return false;
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to parse /sys/kernel/security/lsm: %m");
+
+                if (streq(word, name))
+                        return true;
+        }
+}
diff --git a/src/shared/lsm-util.h b/src/shared/lsm-util.h
new file mode 100644 (file)
index 0000000..c4d9027
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int lsm_supported(const char *name);
index 4b4309037b47f946084bd733d482da533d8a01c9..f27c3d768bfcd5b7cd0b4102d3f0a6299d55cc91 100644 (file)
@@ -8,6 +8,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "id128-util.h"
 #include "io-util.h"
 #include "virt.h"
 
 static int generate_machine_id(const char *root, sd_id128_t *ret) {
-        const char *dbus_machine_id;
         _cleanup_close_ int fd = -EBADF;
         int r;
 
         assert(ret);
 
         /* First, try reading the D-Bus machine id, unless it is a symlink */
-        dbus_machine_id = prefix_roota(root, "/var/lib/dbus/machine-id");
-        fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
-        if (fd >= 0) {
-                if (id128_read_fd(fd, ID128_FORMAT_PLAIN, ret) >= 0) {
-                        log_info("Initializing machine ID from D-Bus machine ID.");
-                        return 0;
-                }
-
-                fd = safe_close(fd);
+        fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT | CHASE_NOFOLLOW, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd >= 0 && id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret) >= 0) {
+                log_info("Initializing machine ID from D-Bus machine ID.");
+                return 0;
         }
 
         if (isempty(root) && running_in_chroot() <= 0) {
index 3dc49922821a57260e1a000370bdd30bc658a362..3f6b6a849d2d2f35d6b79fb9ae1d2db0384b6e40 100644 (file)
@@ -21,6 +21,7 @@
                 r = impl;                                               \
                 if (r < 0)                                              \
                         (void) sd_notifyf(0, "ERRNO=%i", -r);           \
+                (void) sd_notifyf(0, "EXIT_STATUS=%i", ret);            \
                 ask_password_agent_close();                             \
                 polkit_agent_close();                                   \
                 pager_close();                                          \
index 9342d25273c407da424013e474ff3cefd8cd6acc..021ba517f8aba2bf7a0d272c5c6053c8cce9417f 100644 (file)
@@ -10,6 +10,7 @@ shared_sources = files(
         'bitmap.c',
         'blockdev-util.c',
         'bond-util.c',
+        'boot-entry.c',
         'boot-timestamps.c',
         'bootspec.c',
         'bpf-dlopen.c',
@@ -63,7 +64,7 @@ shared_sources = files(
         'ethtool-util.c',
         'exec-util.c',
         'exit-status.c',
-        'extension-release.c',
+        'extension-util.c',
         'fdset.c',
         'fileio-label.c',
         'find-esp.c',
@@ -80,6 +81,7 @@ shared_sources = files(
         'id128-print.c',
         'idn-util.c',
         'ima-util.c',
+        'image-policy.c',
         'import-util.c',
         'in-addr-prefix-util.c',
         'install-file.c',
@@ -103,6 +105,7 @@ shared_sources = files(
         'logs-show.c',
         'loop-util.c',
         'loopback-setup.c',
+        'lsm-util.c',
         'machine-id-setup.c',
         'machine-pool.c',
         'macvlan-util.c',
@@ -223,7 +226,7 @@ if conf.get('ENABLE_NSCD') == 1
         shared_sources += files('nscd-flush.c')
 endif
 
-if conf.get('HAVE_LIBFIDO2') == 1
+if conf.get('HAVE_LIBFIDO2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1
         shared_sources += files('cryptsetup-fido2.c')
 endif
 
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 e583261f456fc4e35900e97accf3bd36cc9aacc6..f30b5f1a7fcc174b65cc2f0b6cc2f599faf5adbc 100644 (file)
@@ -12,7 +12,7 @@
 #endif
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dissect-image.h"
 #include "exec-util.h"
 #include "extract-word.h"
@@ -805,6 +805,7 @@ static int mount_in_namespace(
                 bool read_only,
                 bool make_file_or_directory,
                 const MountOptions *options,
+                const ImagePolicy *image_policy,
                 bool is_image) {
 
         _cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF;
@@ -844,7 +845,7 @@ static int mount_in_namespace(
         if (r < 0)
                 return log_debug_errno(r == -ENOENT ? SYNTHETIC_ERRNO(EOPNOTSUPP) : r, "Target does not allow propagation of mount points");
 
-        r = chase_symlinks(src, NULL, 0, &chased_src_path, &chased_src_fd);
+        r = chase(src, NULL, 0, &chased_src_path, &chased_src_fd);
         if (r < 0)
                 return log_debug_errno(r, "Failed to resolve source path of %s: %m", src);
         log_debug("Chased source path of %s to %s", src, chased_src_path);
@@ -892,7 +893,7 @@ static int mount_in_namespace(
         mount_tmp_created = true;
 
         if (is_image)
-                r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, NULL, NULL, NULL, NULL);
+                r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, image_policy, NULL, NULL, NULL, NULL);
         else
                 r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL);
         if (r < 0)
@@ -1042,7 +1043,7 @@ int bind_mount_in_namespace(
                 bool read_only,
                 bool make_file_or_directory) {
 
-        return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, NULL, false);
+        return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, /* options= */ NULL, /* image_policy= */ NULL, /* is_image= */ false);
 }
 
 int mount_image_in_namespace(
@@ -1053,9 +1054,10 @@ int mount_image_in_namespace(
                 const char *dest,
                 bool read_only,
                 bool make_file_or_directory,
-                const MountOptions *options) {
+                const MountOptions *options,
+                const ImagePolicy *image_policy) {
 
-        return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, true);
+        return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, image_policy, /* is_image=*/ true);
 }
 
 int make_mount_point(const char *path) {
@@ -1078,13 +1080,16 @@ int make_mount_point(const char *path) {
         return 1;
 }
 
-static int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
+int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
         _cleanup_close_ int userns_fd = -EBADF;
         _cleanup_free_ char *line = NULL;
 
         /* Allocates a userns file descriptor with the mapping we need. For this we'll fork off a child
          * process whose only purpose is to give us a new user namespace. It's killed when we got it. */
 
+        if (!userns_shift_range_valid(uid_shift, uid_range))
+                return -EINVAL;
+
         if (IN_SET(idmapping, REMOUNT_IDMAPPING_NONE, REMOUNT_IDMAPPING_HOST_ROOT)) {
                 if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0u, uid_shift, uid_range) < 0)
                         return log_oom_debug();
@@ -1123,31 +1128,21 @@ static int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdm
         return TAKE_FD(userns_fd);
 }
 
-int remount_idmap(
+int remount_idmap_fd(
                 const char *p,
-                uid_t uid_shift,
-                uid_t uid_range,
-                uid_t owner,
-                RemountIdmapping idmapping) {
+                int userns_fd) {
 
-        _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF;
+        _cleanup_close_ int mount_fd = -EBADF;
         int r;
 
         assert(p);
-
-        if (!userns_shift_range_valid(uid_shift, uid_range))
-                return -EINVAL;
+        assert(userns_fd >= 0);
 
         /* Clone the mount point */
         mount_fd = open_tree(-1, p, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
         if (mount_fd < 0)
                 return log_debug_errno(errno, "Failed to open tree of mounted filesystem '%s': %m", p);
 
-        /* Create a user namespace mapping */
-        userns_fd = make_userns(uid_shift, uid_range, owner, idmapping);
-        if (userns_fd < 0)
-                return userns_fd;
-
         /* Set the user namespace mapping attribute on the cloned mount point */
         if (mount_setattr(mount_fd, "", AT_EMPTY_PATH | AT_RECURSIVE,
                           &(struct mount_attr) {
@@ -1168,6 +1163,16 @@ int remount_idmap(
         return 0;
 }
 
+int remount_idmap(const char *p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
+        _cleanup_close_ int userns_fd = -EBADF;
+
+        userns_fd = make_userns(uid_shift, uid_range, owner, idmapping);
+        if (userns_fd < 0)
+                return userns_fd;
+
+        return remount_idmap_fd(p, userns_fd);
+}
+
 typedef struct SubMount {
         char *path;
         int mount_fd;
index 84ea4b6392773f388e132b7f0c5f877ccd1c9043..d1defcd8be482fb20d335e9951305f1dfcb4250d 100644 (file)
@@ -81,7 +81,7 @@ static inline char* umount_and_rmdir_and_free(char *p) {
 DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
 
 int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);
-int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options);
+int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options, const ImagePolicy *image_policy);
 
 int make_mount_point(const char *path);
 
@@ -103,6 +103,8 @@ typedef enum RemountIdmapping {
         _REMOUNT_IDMAPPING_INVALID = -EINVAL,
 } RemountIdmapping;
 
+int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping);
+int remount_idmap_fd(const char *p, int userns_fd);
 int remount_idmap(const char *p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping);
 
 int remount_and_move_sub_mounts(
index 4f757f0b55d8feb9c6f6257daca4bef30ff9f1ca..a954ea349ee065c633395ee7536f84972cacfd01 100644 (file)
@@ -120,8 +120,7 @@ int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret) {
                         return r;
         }
 
-        *ret = s;
-        s = (CPUSet) {};
+        *ret = TAKE_STRUCT(s);
 
         return 0;
 }
index 9d74e08a2be23a2c6f825f9d9ef32223fff1d4e4..4d864af717546875c6db75e6dd6f50f7c051f038 100644 (file)
@@ -50,18 +50,35 @@ int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char
 }
 
 static void cleanup_system_bus(pam_handle_t *handle, void *data, int error_status) {
+        /* The PAM_DATA_SILENT flag is the way that pam_end() communicates to the module stack that this
+         * invocation of pam_end() is not the final one, but in the process that is going to directly exec
+         * the child. This means we are being called after a fork(), and we do not want to try and clean
+         * up the sd-bus object, as it would affect the parent too and we'll hit an assertion. */
+        if (error_status & PAM_DATA_SILENT)
+                return (void) pam_syslog_pam_error(
+                                handle,
+                                LOG_ERR,
+                                SYNTHETIC_ERRNO(EUCLEAN),
+                                "Attempted to close sd-bus after fork, this should not happen.");
+
         sd_bus_flush_close_unref(data);
 }
 
-int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret) {
+int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret) {
         _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+        _cleanup_free_ char *cache_id = NULL;
         int r;
 
         assert(handle);
+        assert(module_name);
         assert(ret);
 
+        cache_id = strjoin("system-bus-", module_name);
+        if (!cache_id)
+                return pam_log_oom(handle);
+
         /* We cache the bus connection so that we can share it between the session and the authentication hooks */
-        r = pam_get_data(handle, "systemd-system-bus", (const void**) &bus);
+        r = pam_get_data(handle, cache_id, (const void**) &bus);
         if (r == PAM_SUCCESS && bus) {
                 *ret = sd_bus_ref(TAKE_PTR(bus)); /* Increase the reference counter, so that the PAM data stays valid */
                 return PAM_SUCCESS;
@@ -73,7 +90,7 @@ int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret) {
         if (r < 0)
                 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to connect to system bus: %m");
 
-        r = pam_set_data(handle, "systemd-system-bus", bus, cleanup_system_bus);
+        r = pam_set_data(handle, cache_id, bus, cleanup_system_bus);
         if (r != PAM_SUCCESS)
                 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@");
 
@@ -83,10 +100,17 @@ int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret) {
         return PAM_SUCCESS;
 }
 
-int pam_release_bus_connection(pam_handle_t *handle) {
+int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) {
+        _cleanup_free_ char *cache_id = NULL;
         int r;
 
-        r = pam_set_data(handle, "systemd-system-bus", NULL, NULL);
+        assert(module_name);
+
+        cache_id = strjoin("system-bus-", module_name);
+        if (!cache_id)
+                return pam_log_oom(handle);
+
+        r = pam_set_data(handle, cache_id, NULL, NULL);
         if (r != PAM_SUCCESS)
                 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to release PAM user record data: @PAMERR@");
 
index 1a17ea18c5c9a5be0d4f832e1859ae6fe6f3a049..d9c906aaad05d6e799fed0c82011f437e1016cde 100644 (file)
@@ -24,7 +24,9 @@ static inline int pam_bus_log_parse_error(pam_handle_t *handle, int r) {
         return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse bus message: %m");
 }
 
-int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret);
-int pam_release_bus_connection(pam_handle_t *handle);
+/* Use a different module name per different PAM module. They are all loaded in the same namespace, and this
+ * helps avoid a clash in the internal data structures of sd-bus. It will be used as key for cache items. */
+int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret);
+int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
 
 void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
index 6cc4265737b3c4b13b9b0f7839a8cbe07ccd3242..e99c321418df3f613d79dd2210e3cb1ab19675ed 100644 (file)
@@ -11,6 +11,7 @@
 #include "cgroup-util.h"
 #include "dirent-util.h"
 #include "fd-util.h"
+#include "fs-util.h"
 #include "log.h"
 #include "macro.h"
 #include "mountpoint-util.h"
@@ -28,9 +29,11 @@ static bool is_physical_fs(const struct statfs *sfs) {
 
 static int patch_dirfd_mode(
                 int dfd,
+                bool refuse_already_set,
                 mode_t *ret_old_mode) {
 
         struct stat st;
+        int r;
 
         assert(dfd >= 0);
         assert(ret_old_mode);
@@ -39,16 +42,24 @@ static int patch_dirfd_mode(
                 return -errno;
         if (!S_ISDIR(st.st_mode))
                 return -ENOTDIR;
-        if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */
-                return -EACCES; /* original error */
+
+        if (FLAGS_SET(st.st_mode, 0700)) { /* Already set? */
+                if (refuse_already_set)
+                        return -EACCES; /* original error */
+
+                *ret_old_mode = st.st_mode;
+                return 0;
+        }
+
         if (st.st_uid != geteuid())  /* this only works if the UID matches ours */
                 return -EACCES;
 
-        if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
-                return -errno;
+        r = fchmod_opath(dfd, (st.st_mode | 0700) & 07777);
+        if (r < 0)
+                return r;
 
         *ret_old_mode = st.st_mode;
-        return 0;
+        return 1;
 }
 
 int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
@@ -64,18 +75,18 @@ int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags
         if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
                 return -errno;
 
-        r = patch_dirfd_mode(dfd, &old_mode);
+        r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
         if (r < 0)
                 return r;
 
         if (unlinkat(dfd, filename, unlink_flags) < 0) {
                 r = -errno;
                 /* Try to restore the original access mode if this didn't work */
-                (void) fchmod(dfd, old_mode);
+                (void) fchmod(dfd, old_mode & 07777);
                 return r;
         }
 
-        if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
+        if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
                 return -errno;
 
         /* If this worked, we won't reset the old mode by default, since we'll need it for other entries too,
@@ -99,22 +110,84 @@ int fstatat_harder(int dfd,
         if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
                 return -errno;
 
-        r = patch_dirfd_mode(dfd, &old_mode);
+        r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
         if (r < 0)
                 return r;
 
         if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
                 r = -errno;
-                (void) fchmod(dfd, old_mode);
+                (void) fchmod(dfd, old_mode & 07777);
                 return r;
         }
 
-        if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode) < 0)
+        if (FLAGS_SET(remove_flags, REMOVE_CHMOD_RESTORE) && fchmod(dfd, old_mode & 07777) < 0)
                 return -errno;
 
         return 0;
 }
 
+static int openat_harder(int dfd, const char *path, int open_flags, RemoveFlags remove_flags, mode_t *ret_old_mode) {
+        _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
+        bool chmod_done = false;
+        mode_t old_mode;
+        int r;
+
+        assert(dfd >= 0 || dfd == AT_FDCWD);
+        assert(path);
+
+        /* Unlike unlink_harder() and fstatat_harder(), this chmod the specified path. */
+
+        if (FLAGS_SET(open_flags, O_PATH) ||
+            !FLAGS_SET(open_flags, O_DIRECTORY) ||
+            !FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
+
+                fd = RET_NERRNO(openat(dfd, path, open_flags));
+                if (fd < 0)
+                        return fd;
+
+                if (ret_old_mode) {
+                        struct stat st;
+
+                        if (fstat(fd, &st) < 0)
+                                return -errno;
+
+                        *ret_old_mode = st.st_mode;
+                }
+
+                return TAKE_FD(fd);
+        }
+
+        pfd = RET_NERRNO(openat(dfd, path, (open_flags & (O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW)) | O_PATH));
+        if (pfd < 0)
+                return pfd;
+
+        if (FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
+                r = patch_dirfd_mode(pfd, /* refuse_already_set = */ false, &old_mode);
+                if (r < 0)
+                        return r;
+
+                chmod_done = r;
+        }
+
+        fd = fd_reopen(pfd, open_flags & ~O_NOFOLLOW);
+        if (fd < 0) {
+                if (chmod_done)
+                        (void) fchmod_opath(pfd, old_mode & 07777);
+                return fd;
+        }
+
+        if (ret_old_mode)
+                *ret_old_mode = old_mode;
+
+        return TAKE_FD(fd);
+}
+
+static int rm_rf_children_impl(
+                int fd,
+                RemoveFlags flags,
+                const struct stat *root_dev,
+                mode_t old_mode);
+
 static int rm_rf_inner_child(
                 int fd,
                 const char *fname,
@@ -169,13 +242,16 @@ static int rm_rf_inner_child(
                 if (!allow_recursion)
                         return -EISDIR;
 
-                int subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+                mode_t old_mode;
+                int subdir_fd = openat_harder(fd, fname,
+                                              O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
+                                              flags, &old_mode);
                 if (subdir_fd < 0)
-                        return -errno;
+                        return subdir_fd;
 
                 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
                  * again for each directory */
-                q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
+                q = rm_rf_children_impl(subdir_fd, flags | REMOVE_PHYSICAL, root_dev, old_mode);
 
         } else if (flags & REMOVE_ONLY_DIRECTORIES)
                 return 0;
@@ -191,6 +267,7 @@ static int rm_rf_inner_child(
 typedef struct TodoEntry {
         DIR *dir;         /* A directory that we were operating on. */
         char *dirname;    /* The filename of that directory itself. */
+        mode_t old_mode;  /* The original file mode. */
 } TodoEntry;
 
 static void free_todo_entries(TodoEntry **todos) {
@@ -207,6 +284,22 @@ int rm_rf_children(
                 RemoveFlags flags,
                 const struct stat *root_dev) {
 
+        struct stat st;
+
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return rm_rf_children_impl(fd, flags, root_dev, st.st_mode);
+}
+
+static int rm_rf_children_impl(
+                int fd,
+                RemoveFlags flags,
+                const struct stat *root_dev,
+                mode_t old_mode) {
+
         _cleanup_(free_todo_entries) TodoEntry *todos = NULL;
         size_t n_todo = 0;
         _cleanup_free_ char *dirname = NULL; /* Set when we are recursing and want to delete ourselves */
@@ -223,14 +316,20 @@ int rm_rf_children(
                          * We need to remove the inner directory we were operating on. */
                         assert(dirname);
                         r = unlinkat_harder(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR, flags);
-                        if (r < 0 && r != -ENOENT && ret == 0)
-                                ret = r;
+                        if (r < 0 && r != -ENOENT) {
+                                if (ret == 0)
+                                        ret = r;
+
+                                if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE))
+                                        (void) fchmodat(dirfd(todos[n_todo-1].dir), dirname, old_mode & 07777, 0);
+                        }
                         dirname = mfree(dirname);
 
                         /* And now let's back out one level up */
                         n_todo --;
                         d = TAKE_PTR(todos[n_todo].dir);
                         dirname = TAKE_PTR(todos[n_todo].dirname);
+                        old_mode = todos[n_todo].old_mode;
 
                         assert(d);
                         fd = dirfd(d); /* Retrieve the file descriptor from the DIR object */
@@ -287,17 +386,25 @@ int rm_rf_children(
                                  if (!newdirname)
                                          return log_oom();
 
-                                 int newfd = openat(fd, de->d_name,
-                                                    O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+                                 mode_t mode;
+                                 int newfd = openat_harder(fd, de->d_name,
+                                                           O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
+                                                           flags, &mode);
                                  if (newfd >= 0) {
-                                         todos[n_todo++] = (TodoEntry) { TAKE_PTR(d), TAKE_PTR(dirname) };
+                                         todos[n_todo++] = (TodoEntry) {
+                                                 .dir = TAKE_PTR(d),
+                                                 .dirname = TAKE_PTR(dirname),
+                                                 .old_mode = old_mode
+                                         };
+
                                          fd = newfd;
                                          dirname = TAKE_PTR(newdirname);
+                                         old_mode = mode;
 
                                          goto next_fd;
 
-                                 } else if (errno != -ENOENT && ret == 0)
-                                         ret = -errno;
+                                 } else if (newfd != -ENOENT && ret == 0)
+                                         ret = newfd;
 
                         } else if (r < 0 && r != -ENOENT && ret == 0)
                                 ret = r;
@@ -306,14 +413,20 @@ int rm_rf_children(
                 if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0)
                         ret = -errno;
 
-                if (n_todo == 0)
+                if (n_todo == 0) {
+                        if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE) &&
+                            fchmod(fd, old_mode & 07777) < 0 && ret >= 0)
+                                ret = -errno;
+
                         break;
+                }
         }
 
         return ret;
 }
 
 int rm_rf(const char *path, RemoveFlags flags) {
+        mode_t old_mode;
         int fd, r, q = 0;
 
         assert(path);
@@ -345,19 +458,20 @@ int rm_rf(const char *path, RemoveFlags flags) {
                 /* Not btrfs or not a subvolume */
         }
 
-        fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+        fd = openat_harder(AT_FDCWD, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode);
         if (fd >= 0) {
                 /* We have a dir */
-                r = rm_rf_children(fd, flags, NULL);
+                r = rm_rf_children_impl(fd, flags, NULL, old_mode);
 
                 if (FLAGS_SET(flags, REMOVE_ROOT))
                         q = RET_NERRNO(rmdir(path));
         } else {
-                if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
+                r = fd;
+                if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
                         return 0;
 
-                if (!IN_SET(errno, ENOTDIR, ELOOP))
-                        return -errno;
+                if (!IN_SET(r, -ENOTDIR, -ELOOP))
+                        return r;
 
                 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT))
                         return 0;
index 8ece7f163f1be53370957aff2e2cfc61bce90814..3e9f89c8905182fec698e3d21fa1a5181799c871 100644 (file)
@@ -322,6 +322,7 @@ const SyscallFilterSet syscall_filter_sets[_SYSCALL_FILTER_SET_MAX] = {
                 "exit_group\0"
                 "futex\0"
                 "futex_time64\0"
+                "futex_waitv\0"
                 "get_robust_list\0"
                 "get_thread_area\0"
                 "getegid\0"
@@ -719,6 +720,7 @@ const SyscallFilterSet syscall_filter_sets[_SYSCALL_FILTER_SET_MAX] = {
                 "open_by_handle_at\0"
                 "pivot_root\0"
                 "quotactl\0"
+                "quotactl_fd\0"
                 "setdomainname\0"
                 "setfsuid\0"
                 "setfsuid32\0"
@@ -797,9 +799,19 @@ const SyscallFilterSet syscall_filter_sets[_SYSCALL_FILTER_SET_MAX] = {
                 "sched_setparam\0"
                 "sched_setscheduler\0"
                 "set_mempolicy\0"
+                "set_mempolicy_home_node\0"
                 "setpriority\0"
                 "setrlimit\0"
         },
+        [SYSCALL_FILTER_SET_SANDBOX] = {
+                .name = "@sandbox",
+                .help = "Sandbox functionality",
+                .value =
+                "landlock_add_rule\0"
+                "landlock_create_ruleset\0"
+                "landlock_restrict_self\0"
+                "seccomp\0"
+        },
         [SYSCALL_FILTER_SET_SETUID] = {
                 .name = "@setuid",
                 .help = "Operations for changing user/group credentials",
index 3f1a993e11cdc93cfbc3a41ff303fd37de0b1c65..5fd135d5972fba1538eb692ee8aa789c77d839cc 100644 (file)
@@ -49,6 +49,7 @@ enum {
         SYSCALL_FILTER_SET_RAW_IO,
         SYSCALL_FILTER_SET_REBOOT,
         SYSCALL_FILTER_SET_RESOURCES,
+        SYSCALL_FILTER_SET_SANDBOX,
         SYSCALL_FILTER_SET_SETUID,
         SYSCALL_FILTER_SET_SIGNAL,
         SYSCALL_FILTER_SET_SWAP,
@@ -56,9 +57,12 @@ enum {
         SYSCALL_FILTER_SET_SYSTEM_SERVICE,
         SYSCALL_FILTER_SET_TIMER,
         SYSCALL_FILTER_SET_KNOWN,
-        _SYSCALL_FILTER_SET_MAX
+        _SYSCALL_FILTER_SET_MAX,
 };
 
+assert_cc(SYSCALL_FILTER_SET_DEFAULT == 0);
+assert_cc(SYSCALL_FILTER_SET_KNOWN == _SYSCALL_FILTER_SET_MAX-1);
+
 extern const SyscallFilterSet syscall_filter_sets[];
 
 const SyscallFilterSet *syscall_filter_set_find(const char *name);
index f2e65cfcb09e3eee161e736a546135f8c0aedc3a..caf8e6d59316948db88c5e5f53d2c101d2caa277 100644 (file)
@@ -1,6 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <stdbool.h>
+
 #include "missing_securebits.h"
 
 int secure_bits_to_string_alloc(int i, char **s);
index 200e4ad9b86e217cadee8566ac6967659b450a7a..a4a4017f061939980f856b1e77c3ed8f5551ac93 100644 (file)
@@ -10,7 +10,7 @@
 
 #include "alloc-util.h"
 #include "architecture.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "format-util.h"
 #include "fs-util.h"
@@ -134,7 +134,7 @@ int specifier_real_path(char specifier, const void *data, const char *root, cons
         if (!path)
                 return -ENOENT;
 
-        return chase_symlinks(path, root, 0, ret, NULL);
+        return chase(path, root, 0, ret, NULL);
 }
 
 int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
@@ -187,19 +187,9 @@ int specifier_machine_id(char specifier, const void *data, const char *root, con
 
         assert(ret);
 
-        if (root) {
-                _cleanup_close_ int fd = -EBADF;
-
-                fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
-                if (fd < 0)
-                        /* Translate error for missing os-release file to EUNATCH. */
-                        return fd == -ENOENT ? -EUNATCH : fd;
-
-                r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &id);
-        } else
-                r = sd_id128_get_machine(&id);
-        if (r < 0)
-                return r;
+        r = id128_get_machine(root, &id);
+        if (r < 0) /* Translate error for missing /etc/machine-id file to EUNATCH. */
+                return r == -ENOENT ? -EUNATCH : r;
 
         return specifier_id128(specifier, &id, root, userdata, ret);
 }
index 2aa30bd9f0213a8485e43be685a18f27e5407704..8dc93c28229937f20496217e5520dea96ec222a0 100644 (file)
@@ -9,7 +9,7 @@
 #include <unistd.h>
 
 #include "base-filesystem.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "initrd-util.h"
 #include "log.h"
@@ -53,7 +53,7 @@ int switch_root(const char *new_root,
                 old_root_fd = safe_close(old_root_fd);
 
         /* Determine where we shall place the old root after the transition */
-        r = chase_symlinks(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL);
+        r = chase(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after, NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, old_root_after);
         if (r == 0) /* Doesn't exist yet. Let's create it */
@@ -69,7 +69,7 @@ int switch_root(const char *new_root,
         FOREACH_STRING(path, "/sys", "/dev", "/run", "/proc") {
                 _cleanup_free_ char *chased = NULL;
 
-                r = chase_symlinks(path, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL);
+                r = chase(path, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, path);
                 if (r > 0) {
index 28d743a5767830e4d823dbf7974b63ea06971164..3ccbbbcaf9081e5074ea59d702d6548231b7039f 100644 (file)
 #include "fs-util.h"
 #include "hexdecoct.h"
 #include "hmac.h"
+#include "lock-util.h"
+#include "log.h"
+#include "logarithm.h"
 #include "memory-util.h"
 #include "openssl-util.h"
 #include "parse-util.h"
 #include "random-util.h"
 #include "sha256.h"
 #include "stat-util.h"
+#include "string-table.h"
 #include "time-util.h"
 #include "tpm2-util.h"
 #include "virt.h"
@@ -30,6 +34,7 @@ static void *libtss2_mu_dl = NULL;
 
 TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
 TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
+TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle);
 void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
 TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
 void (*sym_Esys_Free)(void *ptr) = NULL;
@@ -44,10 +49,14 @@ TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySes
 TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
 TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
 TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
+TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName);
 TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
 TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
 TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask);
 TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name);
+TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle);
+TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object);
+TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size);
 TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL;
 TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
 TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation);
@@ -66,6 +75,7 @@ int dlopen_tpm2(void) {
                         &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG,
                         DLSYM_ARG(Esys_Create),
                         DLSYM_ARG(Esys_CreatePrimary),
+                        DLSYM_ARG(Esys_EvictControl),
                         DLSYM_ARG(Esys_Finalize),
                         DLSYM_ARG(Esys_FlushContext),
                         DLSYM_ARG(Esys_Free),
@@ -80,10 +90,14 @@ int dlopen_tpm2(void) {
                         DLSYM_ARG(Esys_PolicyAuthValue),
                         DLSYM_ARG(Esys_PolicyGetDigest),
                         DLSYM_ARG(Esys_PolicyPCR),
+                        DLSYM_ARG(Esys_ReadPublic),
                         DLSYM_ARG(Esys_StartAuthSession),
                         DLSYM_ARG(Esys_Startup),
                         DLSYM_ARG(Esys_TRSess_SetAttributes),
+                        DLSYM_ARG(Esys_TR_FromTPMPublic),
                         DLSYM_ARG(Esys_TR_GetName),
+                        DLSYM_ARG(Esys_TR_Deserialize),
+                        DLSYM_ARG(Esys_TR_Serialize),
                         DLSYM_ARG(Esys_TR_SetAuth),
                         DLSYM_ARG(Esys_Unseal),
                         DLSYM_ARG(Esys_VerifySignature));
@@ -249,7 +263,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) {
                 return NULL;
 
         _cleanup_tpm2_context_ Tpm2Context *context = (Tpm2Context*)handle->tpm2_context;
-        if (context)
+        if (context && !handle->keep)
                 tpm2_handle_flush(context->esys_context, handle->esys_handle);
 
         return mfree(handle);
@@ -334,55 +348,200 @@ static int tpm2_credit_random(Tpm2Context *c) {
         return 0;
 }
 
-static int tpm2_make_primary(
-                Tpm2Context *c,
-                Tpm2Handle **ret_primary,
-                TPMI_ALG_PUBLIC alg,
-                TPMI_ALG_PUBLIC *ret_alg) {
+const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) {
 
-        static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
-        static const TPM2B_PUBLIC primary_template_ecc = {
-                .size = sizeof(TPMT_PUBLIC),
-                .publicArea = {
-                        .type = TPM2_ALG_ECC,
-                        .nameAlg = TPM2_ALG_SHA256,
-                        .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
-                        .parameters.eccDetail = {
-                                .symmetric = {
-                                        .algorithm = TPM2_ALG_AES,
-                                        .keyBits.aes = 128,
-                                        .mode.aes = TPM2_ALG_CFB,
+        /*
+         * Set up array so flags can be used directly as an input.
+         *
+         * Templates for SRK come from the spec:
+         *   - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
+         *
+         * However, note their is some lore here. On Linux, the SRK has it's unique field set to size 0 and
+         * on Windows the SRK has their unique data set to keyLen in bytes of zeros.
+         */
+        assert(flags >= 0);
+        assert(flags <= _TPM2_SRK_TEMPLATE_MAX);
+
+        static const TPM2B_PUBLIC templ[_TPM2_SRK_TEMPLATE_MAX + 1] = {
+                /* index 0 RSA old */
+                [0] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_RSA,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+                                .parameters.rsaDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .keyBits = 2048,
                                 },
-                                .scheme.scheme = TPM2_ALG_NULL,
-                                .curveID = TPM2_ECC_NIST_P256,
-                                .kdf.scheme = TPM2_ALG_NULL,
                         },
                 },
-        };
-        static const TPM2B_PUBLIC primary_template_rsa = {
-                .size = sizeof(TPMT_PUBLIC),
-                .publicArea = {
-                        .type = TPM2_ALG_RSA,
-                        .nameAlg = TPM2_ALG_SHA256,
-                        .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
-                        .parameters.rsaDetail = {
-                                .symmetric = {
-                                        .algorithm = TPM2_ALG_AES,
-                                        .keyBits.aes = 128,
-                                        .mode.aes = TPM2_ALG_CFB,
+                [TPM2_SRK_TEMPLATE_ECC] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_ECC,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+                                .parameters.eccDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .curveID = TPM2_ECC_NIST_P256,
+                                        .kdf.scheme = TPM2_ALG_NULL,
+                                },
+                        },
+                },
+                [TPM2_SRK_TEMPLATE_NEW_STYLE] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_RSA,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+                                .parameters.rsaDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .keyBits = 2048,
+                                },
+                        },
+                },
+                [TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_ECC,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+                                .parameters.eccDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .curveID = TPM2_ECC_NIST_P256,
+                                        .kdf.scheme = TPM2_ALG_NULL,
                                 },
-                                .scheme.scheme = TPM2_ALG_NULL,
-                                .keyBits = 2048,
                         },
                 },
         };
 
+        return &templ[flags];
+}
+
+/*
+ * Why and what is an SRK?
+ * TL;DR provides a working space for those without owner auth. The user enrolling
+ * the disk may not have access to the TPMs owner hierarchy auth, so they need a
+ * working space. This working space is at the defined address of 0x81000001.
+ * Details can be found here:
+ *   - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
+ */
+#define SRK_HANDLE UINT32_C(0x81000001)
+
+/*
+ * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present
+ * and < 0 on error
+ */
+static int tpm2_get_srk(
+                Tpm2Context *c,
+                TPMI_ALG_PUBLIC *ret_alg,
+                Tpm2Handle *ret_primary) {
+
+        TPMI_YES_NO more_data;
+        ESYS_TR primary_tr = ESYS_TR_NONE;
+        _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL;
+
+        assert(c);
+        assert(ret_primary);
+
+        TSS2_RC rc = sym_Esys_GetCapability(c->esys_context,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        TPM2_CAP_HANDLES,
+                        SRK_HANDLE,
+                        1,
+                        &more_data,
+                        &cap_data);
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to enumerate handles searching for SRK: %s",
+                                       sym_Tss2_RC_Decode(rc));
+
+        /* Did Not find SRK, indicate this by returning 0 */
+        if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) {
+                ret_primary->esys_handle = ESYS_TR_NONE;
+
+                if (ret_alg)
+                        *ret_alg = 0;
+                return 0;
+        }
+
+        log_debug("Found SRK on TPM.");
+
+        /* convert the raw handle to an ESYS_TR */
+        TPM2_HANDLE handle = cap_data->data.handles.handle[0];
+        rc = sym_Esys_TR_FromTPMPublic(c->esys_context,
+                        handle,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        &primary_tr);
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                        "Failed to convert ray handle to ESYS_TR for SRK: %s",
+                                        sym_Tss2_RC_Decode(rc));
+
+        /* Get the algorithm if the caller wants it */
+        _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL;
+        if (ret_alg) {
+                rc = sym_Esys_ReadPublic(
+                                c->esys_context,
+                                primary_tr,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                &out_public,
+                                NULL,
+                                NULL);
+                if (rc != TSS2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                                "Failed to convert ray handle to ESYS_TR for SRK: %s",
+                                                sym_Tss2_RC_Decode(rc));
+        }
+
+        ret_primary->esys_handle = primary_tr;
+
+        if (ret_alg)
+                 *ret_alg = out_public->publicArea.type;
+
+        return 1;
+}
+
+static int tpm2_make_primary(
+                Tpm2Context *c,
+                TPMI_ALG_PUBLIC alg,
+                bool use_srk_model,
+                TPMI_ALG_PUBLIC *ret_alg,
+                Tpm2Handle **ret_primary) {
+
+        static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
         static const TPML_PCR_SELECTION creation_pcr = {};
+        const TPM2B_PUBLIC *primary_template = NULL;
+        Tpm2SRKTemplateFlags base_flags = use_srk_model ? TPM2_SRK_TEMPLATE_NEW_STYLE : 0;
+        _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT;
         TSS2_RC rc;
         usec_t ts;
         int r;
 
-        log_debug("Creating primary key on TPM.");
+        log_debug("Creating %s on TPM.", use_srk_model ? "SRK" : "Transient Primary Key");
 
         /* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much
          * faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's
@@ -395,7 +554,42 @@ static int tpm2_make_primary(
         if (r < 0)
                 return r;
 
+        /* we only need the SRK lock when making the SRK since its not atomic, transient
+         * primary creations don't even matter if they stomp on each other, the TPM will
+         * keep kicking back the same key.
+         */
+        if (use_srk_model) {
+                r = make_lock_file("/run/systemd/tpm2-srk-init", LOCK_EX, &srk_lock);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to take TPM SRK lock: %m");
+        }
+
+        /* Find existing SRK and use it if present */
+        if (use_srk_model) {
+                TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL;
+                r = tpm2_get_srk(c, &got_alg, primary);
+                if (r < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to establish if SRK is present");
+                if (r == 1) {
+                        log_debug("Discovered existing SRK");
+
+                        if (alg != 0 && alg != got_alg)
+                                log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring",
+                                            alg, got_alg);
+
+                        if (ret_alg)
+                                *ret_alg = alg;
+                        if (ret_primary)
+                                *ret_primary = TAKE_PTR(primary);
+                        return 0;
+                }
+                log_debug("Did not find SRK, generating...");
+        }
+
         if (IN_SET(alg, 0, TPM2_ALG_ECC)) {
+                primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC);
+
                 rc = sym_Esys_CreatePrimary(
                                 c->esys_context,
                                 ESYS_TR_RH_OWNER,
@@ -403,7 +597,7 @@ static int tpm2_make_primary(
                                 ESYS_TR_NONE,
                                 ESYS_TR_NONE,
                                 &primary_sensitive,
-                                &primary_template_ecc,
+                                primary_template,
                                 NULL,
                                 &creation_pcr,
                                 &primary->esys_handle,
@@ -425,6 +619,8 @@ static int tpm2_make_primary(
         }
 
         if (IN_SET(alg, 0, TPM2_ALG_RSA)) {
+                primary_template = tpm2_get_primary_template(base_flags);
+
                 rc = sym_Esys_CreatePrimary(
                                 c->esys_context,
                                 ESYS_TR_RH_OWNER,
@@ -432,7 +628,7 @@ static int tpm2_make_primary(
                                 ESYS_TR_NONE,
                                 ESYS_TR_NONE,
                                 &primary_sensitive,
-                                &primary_template_rsa,
+                                primary_template,
                                 NULL,
                                 &creation_pcr,
                                 &primary->esys_handle,
@@ -452,7 +648,17 @@ static int tpm2_make_primary(
                 log_debug("Successfully created RSA primary key on TPM.");
         }
 
-        log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC));
+        log_debug("Generating %s on the TPM2 took %s.", use_srk_model ? "SRK" : "Transient Primary Key",
+                        FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC));
+
+        if (use_srk_model) {
+                rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle,
+                                ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, SRK_HANDLE, &primary->esys_handle);
+                if (rc != TSS2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc));
+                primary->keep = true;
+        }
 
         if (ret_primary)
                 *ret_primary = TAKE_PTR(primary);
@@ -570,7 +776,7 @@ size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s) {
 
         uint32_t mask;
         tpm2_tpms_pcr_selection_to_mask(s, &mask);
-        return (size_t)__builtin_popcount(mask);
+        return popcount(mask);
 }
 
 /* Utility functions for TPML_PCR_SELECTION. */
@@ -1752,10 +1958,13 @@ int tpm2_seal(const char *device,
               void **ret_pcr_hash,
               size_t *ret_pcr_hash_size,
               uint16_t *ret_pcr_bank,
-              uint16_t *ret_primary_alg) {
+              uint16_t *ret_primary_alg,
+              void **ret_srk_buf,
+              size_t *ret_srk_buf_size) {
 
         _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
         _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
+        _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL;
         static const TPML_PCR_SELECTION creation_pcr = {};
         _cleanup_(erase_and_freep) void *secret = NULL;
         _cleanup_free_ void *hash = NULL;
@@ -1764,6 +1973,7 @@ int tpm2_seal(const char *device,
         TPM2B_PUBLIC hmac_template;
         usec_t start;
         TSS2_RC rc;
+        size_t srk_buf_size;
         int r;
 
         assert(pubkey || pubkey_size == 0);
@@ -1805,7 +2015,7 @@ int tpm2_seal(const char *device,
                 return r;
 
         _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL;
-        r = tpm2_make_primary(c, &primary, 0, &primary_alg);
+        r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary);
         if (r < 0)
                 return r;
 
@@ -1914,9 +2124,35 @@ int tpm2_seal(const char *device,
         if (!hash)
                 return log_oom();
 
+        /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both
+         * the raw TPM handle as well as the object name. The object name is used to verify that
+         * the key we use later is the key we expect to establish the session with.
+         */
+        if (ret_srk_buf) {
+                log_debug("Serializing SRK ESYS_TR reference");
+                rc = sym_Esys_TR_Serialize(c->esys_context, primary->esys_handle, &srk_buf, &srk_buf_size);
+                if (rc != TSS2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                            "Failed to serialize primary key: %s", sym_Tss2_RC_Decode(rc));
+        }
+
         if (DEBUG_LOGGING)
                 log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
 
+        if (ret_srk_buf) {
+                /*
+                 * make a copy since we don't want the caller to understand that
+                 * ESYS allocated the pointer. It would make tracking what deallocator
+                 * to use for srk_buf in which context a PITA.
+                 */
+                void *tmp = memdup(srk_buf, srk_buf_size);
+                if (!tmp)
+                        return log_oom();
+
+                *ret_srk_buf = TAKE_PTR(tmp);
+                *ret_srk_buf_size = srk_buf_size;
+        }
+
         *ret_secret = TAKE_PTR(secret);
         *ret_secret_size = hmac_sensitive.sensitive.data.size;
         *ret_blob = TAKE_PTR(blob);
@@ -1944,6 +2180,8 @@ int tpm2_unseal(const char *device,
                 size_t blob_size,
                 const void *known_policy_hash,
                 size_t known_policy_hash_size,
+                const void *srk_buf,
+                size_t srk_buf_size,
                 void **ret_secret,
                 size_t *ret_secret_size) {
 
@@ -1999,18 +2237,39 @@ int tpm2_unseal(const char *device,
         if (r < 0)
                 return r;
 
+        /* If their is a primary key we trust, like an SRK, use it */
         _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL;
-        r = tpm2_make_primary(c, &primary, primary_alg, NULL);
-        if (r < 0)
-                return r;
+        if (srk_buf) {
+
+                r = tpm2_handle_new(c, &primary);
+                if (r < 0)
+                        return r;
+
+                primary->keep = true;
+
+                log_debug("Found existing SRK key to use, deserializing ESYS_TR");
+                rc = sym_Esys_TR_Deserialize(
+                                c->esys_context,
+                                srk_buf,
+                                srk_buf_size,
+                                &primary->esys_handle);
+                if (rc != TSS2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc));
+        /* old callers without an SRK still need to create a key */
+        } else {
+                r = tpm2_make_primary(c, primary_alg, false, NULL, &primary);
+                if (r < 0)
+                        return r;
+        }
 
         log_debug("Loading HMAC key into TPM.");
 
         /*
          * Nothing sensitive on the bus, no need for encryption. Even if an attacker
-         * gives you back a different key, the session initiation will fail if a pin
-         * is provided. If an attacker gives back a bad key, we already lost since
-         * primary key is not verified and they could attack there as well.
+         * gives you back a different key, the session initiation will fail. In the
+         * SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey
+         * provides protections.
          */
         _cleanup_tpm2_handle_ Tpm2Handle *hmac_key = NULL;
         r = tpm2_handle_new(c, &hmac_key);
@@ -2354,14 +2613,10 @@ int tpm2_pcr_mask_from_string(const char *arg, uint32_t *ret_mask) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to parse PCR list: %s", arg);
 
-                r = safe_atou(pcr, &n);
+                r = pcr_index_from_string(pcr);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to parse PCR number: %s", pcr);
-                if (n >= TPM2_PCRS_MAX)
-                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
-                                               "PCR number out of range (valid range 0…%u): %u",
-                                               TPM2_PCRS_MAX - 1, n);
-
+                        return log_error_errno(r, "Failed to parse specified PCR or specified PCR is out of range: %s", pcr);
+                n = r;
                 SET_BIT(mask, n);;
         }
 
@@ -2439,6 +2694,8 @@ int tpm2_make_luks2_json(
                 size_t policy_hash_size,
                 const void *salt,
                 size_t salt_size,
+                const void *srk_buf,
+                size_t srk_buf_size,
                 TPM2Flags flags,
                 JsonVariant **ret) {
 
@@ -2479,7 +2736,8 @@ int tpm2_make_luks2_json(
                                        JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
                                        JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
                                        JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
-                                       JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size))));
+                                       JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
+                                       JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size))));
         if (r < 0)
                 return r;
 
@@ -2504,10 +2762,12 @@ int tpm2_parse_luks2_json(
                 size_t *ret_policy_hash_size,
                 void **ret_salt,
                 size_t *ret_salt_size,
+                void **ret_srk_buf,
+                size_t *ret_srk_buf_size,
                 TPM2Flags *ret_flags) {
 
-        _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
-        size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0;
+        _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
+        size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
         uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
         uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
         uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
@@ -2614,6 +2874,13 @@ int tpm2_parse_luks2_json(
         } else if (pubkey_pcr_mask != 0)
                 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing.");
 
+        w = json_variant_by_key(v, "tpm2_srk");
+        if (w) {
+                r = json_variant_unbase64(w, &srk_buf, &srk_buf_size);
+                if (r < 0)
+                        return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
+        }
+
         if (ret_keyslot)
                 *ret_keyslot = keyslot;
         if (ret_hash_pcr_mask)
@@ -2642,6 +2909,10 @@ int tpm2_parse_luks2_json(
                 *ret_salt_size = salt_size;
         if (ret_flags)
                 *ret_flags = flags;
+        if (ret_srk_buf)
+                *ret_srk_buf = TAKE_PTR(srk_buf);
+        if (ret_srk_buf_size)
+                *ret_srk_buf_size = srk_buf_size;
 
         return 0;
 }
@@ -2843,3 +3114,24 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass,
 
         return 0;
 }
+
+static const char* const pcr_index_table[_PCR_INDEX_MAX_DEFINED] = {
+        [PCR_PLATFORM_CODE]       = "platform-code",
+        [PCR_PLATFORM_CONFIG]     = "platform-config",
+        [PCR_EXTERNAL_CODE]       = "external-code",
+        [PCR_EXTERNAL_CONFIG]     = "external-config",
+        [PCR_BOOT_LOADER_CODE]    = "boot-loader-code",
+        [PCR_BOOT_LOADER_CONFIG]  = "boot-loader-config",
+        [PCR_SECURE_BOOT_POLICY]  = "secure-boot-policy",
+        [PCR_KERNEL_INITRD]       = "kernel-initrd",
+        [PCR_IMA]                 = "ima",
+        [PCR_KERNEL_BOOT]         = "kernel-boot",
+        [PCR_KERNEL_CONFIG]       = "kernel-config",
+        [PCR_SYSEXTS]             = "sysexts",
+        [PCR_SHIM_POLICY]         = "shim-policy",
+        [PCR_SYSTEM_IDENTITY]     = "system-identity",
+        [PCR_DEBUG]               = "debug",
+        [PCR_APPLICATION_SUPPORT] = "application-support",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(pcr_index, int, TPM2_PCRS_MAX);
index c2532c61c23a9916fd099d49b3694369de484fef..6634e2d2421e413dfd14c313459640ab77039df4 100644 (file)
@@ -13,6 +13,12 @@ typedef enum TPM2Flags {
 } TPM2Flags;
 
 
+typedef enum Tpm2SRKTemplateFlags {
+        TPM2_SRK_TEMPLATE_ECC       = 1 << 0,
+        TPM2_SRK_TEMPLATE_NEW_STYLE = 1 << 1,
+        _TPM2_SRK_TEMPLATE_MAX      = TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC,
+} Tpm2SRKTemplateFlags;
+
 /* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
  * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */
 #define TPM2_PCRS_MAX 24U
@@ -65,8 +71,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
 
 int dlopen_tpm2(void);
 
-int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
-int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
+int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
 
 typedef struct {
         unsigned n_ref;
@@ -85,6 +91,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref);
 typedef struct {
         Tpm2Context *tpm2_context;
         ESYS_TR esys_handle;
+        bool keep;
 } Tpm2Handle;
 
 #define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), }
@@ -124,6 +131,8 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l);
 size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l);
 #define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0)
 
+const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags);
+
 #else /* HAVE_TPM2 */
 typedef struct {} Tpm2Context;
 typedef struct {} Tpm2Handle;
@@ -135,8 +144,8 @@ int tpm2_find_device_auto(int log_level, char **ret);
 int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
 int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
 
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret);
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags);
 
 /* Default to PCR 7 only */
 #define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)
@@ -193,6 +202,31 @@ typedef enum Tpm2Support {
         TPM2_SUPPORT_FULL      = TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM,
 } Tpm2Support;
 
+typedef enum PcrIndex {
+/* The following names for PCRs 0…7 are based on the names in the "TCG PC Client Specific Platform Firmware Profile Specification" (https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/) */
+   PCR_PLATFORM_CODE       = 0,
+        PCR_PLATFORM_CONFIG     = 1,
+        PCR_EXTERNAL_CODE       = 2,
+        PCR_EXTERNAL_CONFIG     = 3,
+        PCR_BOOT_LOADER_CODE    = 4,
+        PCR_BOOT_LOADER_CONFIG  = 5,
+        PCR_SECURE_BOOT_POLICY  = 7,
+/* The following names for PCRs 9…15 are based on the "Linux TPM PCR Registry"
+(https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/) */
+        PCR_KERNEL_INITRD       = 9,
+        PCR_IMA                 = 10,
+        PCR_KERNEL_BOOT         = 11,
+        PCR_KERNEL_CONFIG       = 12,
+        PCR_SYSEXTS             = 13,
+        PCR_SHIM_POLICY         = 14,
+        PCR_SYSTEM_IDENTITY     = 15,
+/* As per "TCG PC Client Specific Platform Firmware Profile Specification" again, see above */
+        PCR_DEBUG               = 16,
+        PCR_APPLICATION_SUPPORT = 23,
+        _PCR_INDEX_MAX_DEFINED  = TPM2_PCRS_MAX,
+        _PCR_INDEX_INVALID      = -EINVAL,
+} PcrIndex;
+
 Tpm2Support tpm2_support(void);
 
 int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask);
@@ -205,3 +239,5 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass,
                     const void *salt,
                     size_t saltlen,
                     uint8_t res[static SHA256_DIGEST_SIZE]);
+
+int pcr_index_from_string(const char *s);
index 195311942250965ac4b95db835f1e58a95c82cf0..fe7f158c41e4d8cde42ff083679d17589389a19a 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "missing_threads.h"
@@ -40,7 +40,7 @@ int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char
         if (!path)
                 path = "/etc/login.defs";
 
-        r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", NULL, &f);
+        r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", NULL, &f);
         if (r == -ENOENT)
                 goto defaults;
         if (r < 0)
index 88b8fc2f8f5f8f43a6f1c660926fce659efd0f74..ea1bc9faac89321ed371fd691582dc90c06ab508 100644 (file)
@@ -44,7 +44,6 @@ int nss_passwd_to_user_record(
         int r;
 
         assert(pwd);
-        assert(ret);
 
         if (isempty(pwd->pw_name))
                 return -EINVAL;
@@ -161,7 +160,8 @@ int nss_passwd_to_user_record(
         hr->mask = USER_RECORD_REGULAR |
                 (!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
 
-        *ret = TAKE_PTR(hr);
+        if (ret)
+                *ret = TAKE_PTR(hr);
         return 0;
 }
 
@@ -216,7 +216,6 @@ int nss_user_record_by_name(
         int r;
 
         assert(name);
-        assert(ret);
 
         for (;;) {
                 buf = malloc(buflen);
@@ -257,7 +256,8 @@ int nss_user_record_by_name(
         if (r < 0)
                 return r;
 
-        (*ret)->incomplete = incomplete;
+        if (ret)
+                (*ret)->incomplete = incomplete;
         return 0;
 }
 
@@ -273,8 +273,6 @@ int nss_user_record_by_uid(
         struct spwd spwd, *sresult = NULL;
         int r;
 
-        assert(ret);
-
         for (;;) {
                 buf = malloc(buflen);
                 if (!buf)
@@ -313,7 +311,8 @@ int nss_user_record_by_uid(
         if (r < 0)
                 return r;
 
-        (*ret)->incomplete = incomplete;
+        if (ret)
+                (*ret)->incomplete = incomplete;
         return 0;
 }
 
@@ -326,7 +325,6 @@ int nss_group_to_group_record(
         int r;
 
         assert(grp);
-        assert(ret);
 
         if (isempty(grp->gr_name))
                 return -EINVAL;
@@ -376,7 +374,8 @@ int nss_group_to_group_record(
         g->mask = USER_RECORD_REGULAR |
                 (!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
 
-        *ret = TAKE_PTR(g);
+        if (ret)
+                *ret = TAKE_PTR(g);
         return 0;
 }
 
@@ -431,7 +430,6 @@ int nss_group_record_by_name(
         int r;
 
         assert(name);
-        assert(ret);
 
         for (;;) {
                 buf = malloc(buflen);
@@ -471,7 +469,8 @@ int nss_group_record_by_name(
         if (r < 0)
                 return r;
 
-        (*ret)->incomplete = incomplete;
+        if (ret)
+                (*ret)->incomplete = incomplete;
         return 0;
 }
 
@@ -487,8 +486,6 @@ int nss_group_record_by_gid(
         struct sgrp sgrp, *sresult = NULL;
         int r;
 
-        assert(ret);
-
         for (;;) {
                 buf = malloc(buflen);
                 if (!buf)
@@ -526,6 +523,7 @@ int nss_group_record_by_gid(
         if (r < 0)
                 return r;
 
-        (*ret)->incomplete = incomplete;
+        if (ret)
+                (*ret)->incomplete = incomplete;
         return 0;
 }
index a77eff4407a49c7c12d316328d6d4749788619f3..a7896297ae3afa303db6d60e0ce688350bc088bf 100644 (file)
@@ -142,10 +142,20 @@ struct user_group_data {
         bool incomplete;
 };
 
-static void user_group_data_release(struct user_group_data *d) {
+static void user_group_data_done(struct user_group_data *d) {
         json_variant_unref(d->record);
 }
 
+struct membership_data {
+        char *user_name;
+        char *group_name;
+};
+
+static void membership_data_done(struct membership_data *d) {
+        free(d->user_name);
+        free(d->group_name);
+}
+
 static int userdb_on_query_reply(
                 Varlink *link,
                 JsonVariant *parameters,
@@ -178,7 +188,7 @@ static int userdb_on_query_reply(
         switch (iterator->what) {
 
         case LOOKUP_USER: {
-                _cleanup_(user_group_data_release) struct user_group_data user_data = {};
+                _cleanup_(user_group_data_done) struct user_group_data user_data = {};
 
                 static const JsonDispatch dispatch_table[] = {
                         { "record",     _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
@@ -235,7 +245,7 @@ static int userdb_on_query_reply(
         }
 
         case LOOKUP_GROUP: {
-                _cleanup_(user_group_data_release) struct user_group_data group_data = {};
+                _cleanup_(user_group_data_done) struct user_group_data group_data = {};
 
                 static const JsonDispatch dispatch_table[] = {
                         { "record",     _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
@@ -288,10 +298,7 @@ static int userdb_on_query_reply(
         }
 
         case LOOKUP_MEMBERSHIP: {
-                struct membership_data {
-                        const char *user_name;
-                        const char *group_name;
-                } membership_data = {};
+                _cleanup_(membership_data_done) struct membership_data membership_data = {};
 
                 static const JsonDispatch dispatch_table[] = {
                         { "userName",  JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, user_name),  JSON_RELAX },
@@ -306,21 +313,8 @@ static int userdb_on_query_reply(
                 if (r < 0)
                         goto finish;
 
-                iterator->found_user_name = mfree(iterator->found_user_name);
-                iterator->found_group_name = mfree(iterator->found_group_name);
-
-                iterator->found_user_name = strdup(membership_data.user_name);
-                if (!iterator->found_user_name) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
-
-                iterator->found_group_name = strdup(membership_data.group_name);
-                if (!iterator->found_group_name) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
-
+                iterator->found_user_name = TAKE_PTR(membership_data.user_name);
+                iterator->found_group_name = TAKE_PTR(membership_data.group_name);
                 iterator->n_found++;
 
                 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
index 8244f599a9a0a86c66160fb2e1637f69ee13aa5f..6b985a4c9b32fcdb3f08ea4ed07160efc51b16a9 100644 (file)
@@ -81,6 +81,18 @@ typedef enum VarlinkState {
                VARLINK_PENDING_METHOD,                  \
                VARLINK_PENDING_METHOD_MORE)
 
+typedef struct VarlinkJsonQueueItem VarlinkJsonQueueItem;
+
+/* A queued message we shall write into the socket, along with the file descriptors to send at the same
+ * time. This queue item binds them together so that message/fd boundaries are maintained throughout the
+ * whole pipeline. */
+struct VarlinkJsonQueueItem {
+        LIST_FIELDS(VarlinkJsonQueueItem, queue);
+        JsonVariant *data;
+        size_t n_fds;
+        int fds[];
+};
+
 struct Varlink {
         unsigned n_ref;
 
@@ -125,10 +137,28 @@ struct Varlink {
         size_t output_buffer_index;
         size_t output_buffer_size;
 
+        int *input_fds; /* file descriptors associated with the data in input_buffer (for fd passing) */
+        size_t n_input_fds;
+
+        int *output_fds; /* file descriptors associated with the data in output_buffer (for fd passing) */
+        size_t n_output_fds;
+
+        /* Further messages to output not yet formatted into text, and thus not included in output_buffer
+         * yet. We keep them separate from output_buffer, to not violate fd message boundaries: we want that
+         * each fd that is sent is associated with its fds, and that fds cannot be accidentally associated
+         * with preceding or following messages. */
+        LIST_HEAD(VarlinkJsonQueueItem, output_queue);
+        VarlinkJsonQueueItem *output_queue_tail;
+
+        /* The fds to associate with the next message that is about to be enqueued. The user first pushes the
+         * fds it intends to send via varlink_push_fd() into this queue, and then once the message data is
+         * submitted we'll combine the fds and the message data into one. */
+        int *pushed_fds;
+        size_t n_pushed_fds;
+
         VarlinkReply reply_callback;
 
         JsonVariant *current;
-        JsonVariant *reply;
 
         struct ucred ucred;
         bool ucred_acquired:1;
@@ -138,6 +168,13 @@ struct Varlink {
         bool prefer_read_write:1;
         bool got_pollhup:1;
 
+        bool allow_fd_passing_input:1;
+        bool allow_fd_passing_output:1;
+
+        bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */
+
+        int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */
+
         usec_t timestamp;
         usec_t timeout;
 
@@ -223,6 +260,8 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(varlink_state, VarlinkState);
 #define varlink_server_log(s, fmt, ...) \
         log_debug("%s: " fmt, varlink_server_description(s), ##__VA_ARGS__)
 
+static int varlink_format_queue(Varlink *v);
+
 static inline const char *varlink_description(Varlink *v) {
         return (v ? v->description : NULL) ?: "varlink";
 }
@@ -231,6 +270,36 @@ static inline const char *varlink_server_description(VarlinkServer *s) {
         return (s ? s->description : NULL) ?: "varlink";
 }
 
+static VarlinkJsonQueueItem *varlink_json_queue_item_free(VarlinkJsonQueueItem *q) {
+        if (!q)
+                return NULL;
+
+        json_variant_unref(q->data);
+        close_many(q->fds, q->n_fds);
+
+        return mfree(q);
+}
+
+static VarlinkJsonQueueItem *varlink_json_queue_item_new(JsonVariant *m, const int fds[], size_t n_fds) {
+        VarlinkJsonQueueItem *q;
+
+        assert(m);
+        assert(fds || n_fds == 0);
+
+        q = malloc(offsetof(VarlinkJsonQueueItem, fds) + sizeof(int) * n_fds);
+        if (!q)
+                return NULL;
+
+        *q = (VarlinkJsonQueueItem) {
+                .data = json_variant_ref(m),
+                .n_fds = n_fds,
+        };
+
+        memcpy_safe(q->fds, fds, n_fds * sizeof(int));
+
+        return TAKE_PTR(q);
+}
+
 static void varlink_set_state(Varlink *v, VarlinkState state) {
         assert(v);
         assert(state >= 0 && state < _VARLINK_STATE_MAX);
@@ -265,7 +334,9 @@ static int varlink_new(Varlink **ret) {
                 .ucred = UCRED_INVALID,
 
                 .timestamp = USEC_INFINITY,
-                .timeout = VARLINK_DEFAULT_TIMEOUT_USEC
+                .timeout = VARLINK_DEFAULT_TIMEOUT_USEC,
+
+                .af = -1,
         };
 
         *ret = v;
@@ -289,6 +360,7 @@ int varlink_connect_address(Varlink **ret, const char *address) {
                 return log_debug_errno(errno, "Failed to create AF_UNIX socket: %m");
 
         v->fd = fd_move_above_stdio(v->fd);
+        v->af = AF_UNIX;
 
         r = sockaddr_un_set_path(&sockaddr.un, address);
         if (r < 0) {
@@ -339,6 +411,7 @@ int varlink_connect_fd(Varlink **ret, int fd) {
                 return log_debug_errno(r, "Failed to create varlink object: %m");
 
         v->fd = fd;
+        v->af = -1,
         varlink_set_state(v, VARLINK_IDLE_CLIENT);
 
         /* Note that if this function is called we assume the passed socket (if it is one) is already
@@ -361,6 +434,17 @@ static void varlink_detach_event_sources(Varlink *v) {
         v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source);
 }
 
+static void varlink_clear_current(Varlink *v) {
+        assert(v);
+
+        /* Clears the currently processed incoming message */
+        v->current = json_variant_unref(v->current);
+
+        close_many(v->input_fds, v->n_input_fds);
+        v->input_fds = mfree(v->input_fds);
+        v->n_input_fds = 0;
+}
+
 static void varlink_clear(Varlink *v) {
         assert(v);
 
@@ -368,11 +452,28 @@ static void varlink_clear(Varlink *v) {
 
         v->fd = safe_close(v->fd);
 
+        varlink_clear_current(v);
+
         v->input_buffer = mfree(v->input_buffer);
-        v->output_buffer = mfree(v->output_buffer);
+        v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer);
 
-        v->current = json_variant_unref(v->current);
-        v->reply = json_variant_unref(v->reply);
+        varlink_clear_current(v);
+
+        close_many(v->output_fds, v->n_output_fds);
+        v->output_fds = mfree(v->output_fds);
+        v->n_output_fds = 0;
+
+        close_many(v->pushed_fds, v->n_pushed_fds);
+        v->pushed_fds = mfree(v->pushed_fds);
+        v->n_pushed_fds = 0;
+
+        while (v->output_queue) {
+                VarlinkJsonQueueItem *q = v->output_queue;
+
+                LIST_REMOVE(queue, v->output_queue, q);
+                varlink_json_queue_item_free(q);
+        }
+        v->output_queue_tail = NULL;
 
         v->event = sd_event_unref(v->event);
 }
@@ -441,6 +542,7 @@ disconnect:
 
 static int varlink_write(Varlink *v) {
         ssize_t n;
+        int r;
 
         assert(v);
 
@@ -449,25 +551,53 @@ static int varlink_write(Varlink *v) {
         if (v->connecting) /* Writing while we are still wait for a non-blocking connect() to complete will
                             * result in ENOTCONN, hence exit early here */
                 return 0;
-        if (v->output_buffer_size == 0)
-                return 0;
         if (v->write_disconnected)
                 return 0;
 
+        /* If needed let's convert some output queue json variants into text form */
+        r = varlink_format_queue(v);
+        if (r < 0)
+                return r;
+
+        if (v->output_buffer_size == 0)
+                return 0;
+
         assert(v->fd >= 0);
 
-        /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible
-         * with non-socket IO, hence fall back automatically.
-         *
-         * Use a local variable to help gcc figure out that we set 'n' in all cases. */
-        bool prefer_write = v->prefer_read_write;
-        if (!prefer_write) {
-                n = send(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL);
-                if (n < 0 && errno == ENOTSOCK)
-                        prefer_write = v->prefer_read_write = true;
+        if (v->n_output_fds > 0) { /* If we shall send fds along, we must use sendmsg() */
+                struct iovec iov = {
+                        .iov_base = v->output_buffer + v->output_buffer_index,
+                        .iov_len = v->output_buffer_size,
+                };
+                struct msghdr mh = {
+                        .msg_iov = &iov,
+                        .msg_iovlen = 1,
+                        .msg_controllen = CMSG_SPACE(sizeof(int) * v->n_output_fds),
+                };
+
+                mh.msg_control = alloca0(mh.msg_controllen);
+
+                struct cmsghdr *control = CMSG_FIRSTHDR(&mh);
+                control->cmsg_len = CMSG_LEN(sizeof(int) * v->n_output_fds);
+                control->cmsg_level = SOL_SOCKET;
+                control->cmsg_type = SCM_RIGHTS;
+                memcpy(CMSG_DATA(control), v->output_fds, sizeof(int) * v->n_output_fds);
+
+                n = sendmsg(v->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+        } else {
+                /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible
+                 * with non-socket IO, hence fall back automatically.
+                 *
+                 * Use a local variable to help gcc figure out that we set 'n' in all cases. */
+                bool prefer_write = v->prefer_read_write;
+                if (!prefer_write) {
+                        n = send(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL);
+                        if (n < 0 && errno == ENOTSOCK)
+                                prefer_write = v->prefer_read_write = true;
+                }
+                if (prefer_write)
+                        n = write(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size);
         }
-        if (prefer_write)
-                n = write(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size);
         if (n < 0) {
                 if (errno == EAGAIN)
                         return 0;
@@ -482,20 +612,33 @@ static int varlink_write(Varlink *v) {
                 return -errno;
         }
 
+        if (v->output_buffer_sensitive)
+                explicit_bzero_safe(v->output_buffer + v->output_buffer_index, n);
+
         v->output_buffer_size -= n;
 
-        if (v->output_buffer_size == 0)
+        if (v->output_buffer_size == 0) {
                 v->output_buffer_index = 0;
-        else
+                v->output_buffer_sensitive = false; /* We can reset the sensitive flag once the buffer is empty */
+        } else
                 v->output_buffer_index += n;
 
+        close_many(v->output_fds, v->n_output_fds);
+        v->n_output_fds = 0;
+
         v->timestamp = now(CLOCK_MONOTONIC);
         return 1;
 }
 
+#define VARLINK_FDS_MAX (16U*1024U)
+
 static int varlink_read(Varlink *v) {
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * VARLINK_FDS_MAX)) control;
+        struct iovec iov;
+        struct msghdr mh;
         size_t rs;
         ssize_t n;
+        void *p;
 
         assert(v);
 
@@ -539,16 +682,31 @@ static int varlink_read(Varlink *v) {
                 }
         }
 
+        p = v->input_buffer + v->input_buffer_index + v->input_buffer_size;
         rs = MALLOC_SIZEOF_SAFE(v->input_buffer) - (v->input_buffer_index + v->input_buffer_size);
 
-        bool prefer_read = v->prefer_read_write;
-        if (!prefer_read) {
-                n = recv(v->fd, v->input_buffer + v->input_buffer_index + v->input_buffer_size, rs, MSG_DONTWAIT);
-                if (n < 0 && errno == ENOTSOCK)
-                        prefer_read = v->prefer_read_write = true;
+        if (v->allow_fd_passing_input) {
+                iov = (struct iovec) {
+                        .iov_base = p,
+                        .iov_len = rs,
+                };
+                mh = (struct msghdr) {
+                        .msg_iov = &iov,
+                        .msg_iovlen = 1,
+                        .msg_control = &control,
+                        .msg_controllen = sizeof(control),
+                };
+                n = recvmsg_safe(v->fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+        } else {
+                bool prefer_read = v->prefer_read_write;
+                if (!prefer_read) {
+                        n = recv(v->fd, p, rs, MSG_DONTWAIT);
+                        if (n < 0 && errno == ENOTSOCK)
+                                prefer_read = v->prefer_read_write = true;
+                }
+                if (prefer_read)
+                        n = read(v->fd, p, rs);
         }
-        if (prefer_read)
-                n = read(v->fd, v->input_buffer + v->input_buffer_index + v->input_buffer_size, rs);
         if (n < 0) {
                 if (errno == EAGAIN)
                         return 0;
@@ -561,10 +719,44 @@ static int varlink_read(Varlink *v) {
                 return -errno;
         }
         if (n == 0) { /* EOF */
+
+                if (v->allow_fd_passing_input)
+                        cmsg_close_all(&mh);
+
                 v->read_disconnected = true;
                 return 1;
         }
 
+        if (v->allow_fd_passing_input) {
+                struct cmsghdr* cmsg;
+
+                cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1);
+                if (cmsg) {
+                        size_t add;
+
+                        /* We only allow file descriptors to be passed along with the first byte of a
+                         * message. If they are passed with any other byte this is a protocol violation. */
+                        if (v->input_buffer_size != 0) {
+                                cmsg_close_all(&mh);
+                                return -EPROTO;
+                        }
+
+                        add = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+                        if (add > INT_MAX - v->n_input_fds) {
+                                cmsg_close_all(&mh);
+                                return -EBADF;
+                        }
+
+                        if (!GREEDY_REALLOC(v->input_fds, v->n_input_fds + add)) {
+                                cmsg_close_all(&mh);
+                                return -ENOMEM;
+                        }
+
+                        memcpy_safe(v->input_fds + v->n_input_fds, CMSG_TYPED_DATA(cmsg, int), add * sizeof(int));
+                        v->n_input_fds += add;
+                }
+        }
+
         v->input_buffer_size += n;
         v->input_buffer_unscanned += n;
 
@@ -762,7 +954,7 @@ static int varlink_dispatch_reply(Varlink *v) {
                                 log_debug_errno(r, "Reply callback returned error, ignoring: %m");
                 }
 
-                v->current = json_variant_unref(v->current);
+                varlink_clear_current(v);
 
                 if (v->state == VARLINK_PROCESSING_REPLY) {
 
@@ -902,7 +1094,7 @@ static int varlink_dispatch_method(Varlink *v) {
 
         case VARLINK_PROCESSED_METHOD: /* Method call is fully processed */
         case VARLINK_PROCESSING_METHOD_ONEWAY: /* ditto */
-                v->current = json_variant_unref(v->current);
+                varlink_clear_current(v);
                 varlink_set_state(v, VARLINK_IDLE_SERVER);
                 break;
 
@@ -1250,8 +1442,8 @@ Varlink* varlink_flush_close_unref(Varlink *v) {
         return varlink_close_unref(v);
 }
 
-static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
-        _cleanup_free_ char *text = NULL;
+static int varlink_format_json(Varlink *v, JsonVariant *m) {
+        _cleanup_(erase_and_freep) char *text = NULL;
         int r;
 
         assert(v);
@@ -1281,7 +1473,6 @@ static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
 
                 memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
                 v->output_buffer_size += r + 1;
-
         } else {
                 char *n;
                 const size_t new_size = v->output_buffer_size + r + 1;
@@ -1297,6 +1488,75 @@ static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
                 v->output_buffer_index = 0;
         }
 
+        if (json_variant_is_sensitive(m))
+                v->output_buffer_sensitive = true; /* Propagate sensitive flag */
+        else
+                text = mfree(text); /* No point in the erase_and_free() destructor declared above */
+
+        return 0;
+}
+
+static int varlink_enqueue_json(Varlink *v, JsonVariant *m) {
+        VarlinkJsonQueueItem *q;
+
+        assert(v);
+        assert(m);
+
+        /* If there are no file descriptors to be queued and no queue entries yet we can shortcut things and
+         * append this entry directly to the output buffer */
+        if (v->n_pushed_fds == 0 && !v->output_queue)
+                return varlink_format_json(v, m);
+
+        /* Otherwise add a queue entry for this */
+        q = varlink_json_queue_item_new(m, v->pushed_fds, v->n_pushed_fds);
+        if (!q)
+                return -ENOMEM;
+
+        v->n_pushed_fds = 0; /* fds now belong to the queue entry */
+
+        LIST_INSERT_AFTER(queue, v->output_queue, v->output_queue_tail, q);
+        v->output_queue_tail = q;
+        return 0;
+}
+
+static int varlink_format_queue(Varlink *v) {
+        int r;
+
+        assert(v);
+
+        /* Takes entries out of the output queue and formats them into the output buffer. But only if this
+         * would not corrupt our fd message boundaries */
+
+        while (v->output_queue) {
+                _cleanup_free_ int *array = NULL;
+                VarlinkJsonQueueItem *q = v->output_queue;
+
+                if (v->n_output_fds > 0) /* unwritten fds? if we'd add more we'd corrupt the fd message boundaries, hence wait */
+                        return 0;
+
+                if (q->n_fds > 0) {
+                        array = newdup(int, q->fds, q->n_fds);
+                        if (!array)
+                                return -ENOMEM;
+                }
+
+                r = varlink_format_json(v, q->data);
+                if (r < 0)
+                        return r;
+
+                /* Take possession of the queue element's fds */
+                free(v->output_fds);
+                v->output_fds = TAKE_PTR(array);
+                v->n_output_fds = q->n_fds;
+                q->n_fds = 0;
+
+                LIST_REMOVE(queue, v->output_queue, q);
+                if (!v->output_queue)
+                        v->output_queue_tail = NULL;
+
+                varlink_json_queue_item_free(q);
+        }
+
         return 0;
 }
 
@@ -1478,6 +1738,10 @@ int varlink_call(
 
         assert(v->n_pending == 0); /* n_pending can't be > 0 if we are in VARLINK_IDLE_CLIENT state */
 
+        /* If there was still a reply pinned from a previous call, now it's the time to get rid of it, so
+         * that we can assign a new reply shortly. */
+        varlink_clear_current(v);
+
         r = varlink_sanitize_parameters(&parameters);
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
@@ -1514,17 +1778,14 @@ int varlink_call(
         case VARLINK_CALLED:
                 assert(v->current);
 
-                json_variant_unref(v->reply);
-                v->reply = TAKE_PTR(v->current);
-
                 varlink_set_state(v, VARLINK_IDLE_CLIENT);
                 assert(v->n_pending == 1);
                 v->n_pending--;
 
                 if (ret_parameters)
-                        *ret_parameters = json_variant_by_key(v->reply, "parameters");
+                        *ret_parameters = json_variant_by_key(v->current, "parameters");
                 if (ret_error_id)
-                        *ret_error_id = json_variant_string(json_variant_by_key(v->reply, "error"));
+                        *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error"));
                 if (ret_flags)
                         *ret_flags = 0;
 
@@ -1594,7 +1855,7 @@ int varlink_reply(Varlink *v, JsonVariant *parameters) {
                 /* We just replied to a method call that was let hanging for a while (i.e. we were outside of
                  * the varlink_dispatch_method() stack frame), which means with this reply we are ready to
                  * process further messages. */
-                v->current = json_variant_unref(v->current);
+                varlink_clear_current(v);
                 varlink_set_state(v, VARLINK_IDLE_SERVER);
         } else
                 /* We replied to a method call from within the varlink_dispatch_method() stack frame), which
@@ -1635,6 +1896,13 @@ int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) {
                     VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE))
                 return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy.");
 
+        /* Reset the list of pushed file descriptors before sending an error reply. We do this here to
+         * simplify code that puts together a complex reply message with fds, and half-way something
+         * fails. In that case the pushed fds need to be flushed out again. Under the assumption that it
+         * never makes sense to send fds along with errors we simply flush them out here beforehand, so that
+         * the callers don't need to do this explicitly. */
+        varlink_reset_fds(v);
+
         r = varlink_sanitize_parameters(&parameters);
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
@@ -1650,7 +1918,7 @@ int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) {
                 return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
 
         if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) {
-                v->current = json_variant_unref(v->current);
+                varlink_clear_current(v);
                 varlink_set_state(v, VARLINK_IDLE_SERVER);
         } else
                 varlink_set_state(v, VARLINK_PROCESSED_METHOD);
@@ -2024,6 +2292,156 @@ sd_event *varlink_get_event(Varlink *v) {
         return v->event;
 }
 
+int varlink_push_fd(Varlink *v, int fd) {
+        int i;
+
+        assert_return(v, -EINVAL);
+        assert_return(fd >= 0, -EBADF);
+
+        /* Takes an fd to send along with the *next* varlink message sent via this varlink connection. This
+         * takes ownership of the specified fd. Use varlink_dup_fd() below to duplicate the fd first. */
+
+        if (!v->allow_fd_passing_output)
+                return -EPERM;
+
+        if (v->n_pushed_fds >= INT_MAX)
+                return -ENOMEM;
+
+        if (!GREEDY_REALLOC(v->pushed_fds, v->n_pushed_fds + 1))
+                return -ENOMEM;
+
+        i = (int) v->n_pushed_fds;
+        v->pushed_fds[v->n_pushed_fds++] = fd;
+        return i;
+}
+
+int varlink_dup_fd(Varlink *v, int fd) {
+        _cleanup_close_ int dp = -1;
+        int r;
+
+        assert_return(v, -EINVAL);
+        assert_return(fd >= 0, -EBADF);
+
+        /* Like varlink_push_fd() but duplicates the specified fd instead of taking possession of it */
+
+        dp = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+        if (dp < 0)
+                return -errno;
+
+        r = varlink_push_fd(v, dp);
+        if (r < 0)
+                return r;
+
+        TAKE_FD(dp);
+        return r;
+}
+
+int varlink_reset_fds(Varlink *v) {
+        assert_return(v, -EINVAL);
+
+        /* Closes all currently pending fds to send. This may be used whenever the caller is in the process
+         * of putting together a message with fds, and then eventually something fails and they need to
+         * rollback the fds. Note that this is implicitly called whenever an error reply is sent, see above. */
+
+        close_many(v->output_fds, v->n_output_fds);
+        v->n_output_fds = 0;
+        return 0;
+}
+
+int varlink_peek_fd(Varlink *v, size_t i) {
+        assert_return(v, -EINVAL);
+
+        /* Returns one of the file descriptors that were received along with the current message. This does
+         * not duplicate the fd nor invalidate it, it hence remains in our possession. */
+
+        if (!v->allow_fd_passing_input)
+                return -EPERM;
+
+        if (i >= v->n_input_fds)
+                return -ENXIO;
+
+        return v->input_fds[i];
+}
+
+int varlink_take_fd(Varlink *v, size_t i) {
+        assert_return(v, -EINVAL);
+
+        /* Similar to varlink_peek_fd() but the file descriptor's ownership is passed to the caller, and
+         * we'll invalidate the reference to it under our possession. If called twice in a row will return
+         * -EBADF */
+
+        if (!v->allow_fd_passing_input)
+                return -EPERM;
+
+        if (i >= v->n_input_fds)
+                return -ENXIO;
+
+        return TAKE_FD(v->input_fds[i]);
+}
+
+static int verify_unix_socket(Varlink *v) {
+        assert(v);
+
+        if (v->af < 0) {
+                struct stat st;
+
+                if (fstat(v->fd, &st) < 0)
+                        return -errno;
+                if (!S_ISSOCK(st.st_mode)) {
+                        v->af = AF_UNSPEC;
+                        return -ENOTSOCK;
+                }
+
+                v->af = socket_get_family(v->fd);
+                if (v->af < 0)
+                        return v->af;
+        }
+
+        return v->af == AF_UNIX ? 0 : -ENOMEDIUM;
+}
+
+int varlink_set_allow_fd_passing_input(Varlink *v, bool b) {
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        if (v->allow_fd_passing_input == b)
+                return 0;
+
+        if (!b) {
+                v->allow_fd_passing_input = false;
+                return 1;
+        }
+
+        r = verify_unix_socket(v);
+        if (r < 0)
+                return r;
+
+        v->allow_fd_passing_input = true;
+        return 0;
+}
+
+int varlink_set_allow_fd_passing_output(Varlink *v, bool b) {
+        int r;
+
+        assert_return(v, -EINVAL);
+
+        if (v->allow_fd_passing_output == b)
+                return 0;
+
+        if (!b) {
+                v->allow_fd_passing_output = false;
+                return 1;
+        }
+
+        r = verify_unix_socket(v);
+        if (r < 0)
+                return r;
+
+        v->allow_fd_passing_output = true;
+        return 0;
+}
+
 int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
         VarlinkServer *s;
 
index 9518cd909844f1d236255641f13565304b2d5850..a94f5a7729e4b2096423a948b622fbe02afab625 100644 (file)
@@ -107,6 +107,18 @@ int varlink_error_errno(Varlink *v, int error);
 int varlink_notify(Varlink *v, JsonVariant *parameters);
 int varlink_notifyb(Varlink *v, ...);
 
+/* Write outgoing fds into the socket (to be associated with the next enqueued message) */
+int varlink_push_fd(Varlink *v, int fd);
+int varlink_dup_fd(Varlink *v, int fd);
+int varlink_reset_fds(Varlink *v);
+
+/* Read incoming fds from the socket (associated with the currently handled message) */
+int varlink_peek_fd(Varlink *v, size_t i);
+int varlink_take_fd(Varlink *v, size_t i);
+
+int varlink_set_allow_fd_passing_input(Varlink *v, bool b);
+int varlink_set_allow_fd_passing_output(Varlink *v, bool b);
+
 /* Bind a disconnect, reply or timeout callback */
 int varlink_bind_reply(Varlink *v, VarlinkReply reply);
 
@@ -163,14 +175,18 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref);
 DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
 DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
 
+/* These are local errors that never cross the wire, and are our own invention */
 #define VARLINK_ERROR_DISCONNECTED "io.systemd.Disconnected"
 #define VARLINK_ERROR_TIMEOUT "io.systemd.TimedOut"
 #define VARLINK_ERROR_PROTOCOL "io.systemd.Protocol"
 #define VARLINK_ERROR_SYSTEM "io.systemd.System"
 
+/* These are errors defined in the Varlink spec */
 #define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
 #define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"
 #define VARLINK_ERROR_METHOD_NOT_IMPLEMENTED "org.varlink.service.MethodNotImplemented"
 #define VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter"
+
+/* These are errors we came up with and squatted the namespace with */
 #define VARLINK_ERROR_SUBSCRIPTION_TAKEN "org.varlink.service.SubscriptionTaken"
 #define VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied"
index df381d85b8b9f7eaef6b496d197cf3fea7385424..3b1fb41fef74b04721be82cd5206bfbe71af0e61 100644 (file)
@@ -87,26 +87,26 @@ int xml_tokenize(const char **p, char **name, void **state, unsigned *line) {
 
                         if (startswith(b, "!--")) {
                                 /* A comment */
-                                e = strstr(b + 3, "-->");
+                                e = strstrafter(b + 3, "-->");
                                 if (!e)
                                         return -EINVAL;
 
-                                inc_lines(line, b, e + 3 - b);
+                                inc_lines(line, b, e - b);
 
-                                c = e + 3;
+                                c = e;
                                 continue;
                         }
 
                         if (*b == '?') {
                                 /* Processing instruction */
 
-                                e = strstr(b + 1, "?>");
+                                e = strstrafter(b + 1, "?>");
                                 if (!e)
                                         return -EINVAL;
 
-                                inc_lines(line, b, e + 2 - b);
+                                inc_lines(line, b, e - b);
 
-                                c = e + 2;
+                                c = e;
                                 continue;
                         }
 
index 42111d27728892762baee4365de031988a004bc7..b1dac20c692da534794fd64682bbf202febf6e38 100644 (file)
@@ -14,6 +14,8 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "sd-daemon.h"
+
 #include "alloc-util.h"
 #include "async.h"
 #include "binfmt-util.h"
@@ -25,6 +27,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 +53,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 +68,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)
@@ -575,6 +572,10 @@ int main(int argc, char *argv[]) {
         if (!in_container)
                 sync_with_progress();
 
+        /* This is primarily useful when running systemd in a VM, as it provides the user running the VM with
+         * a mechanism to pick up systemd's exit status in the VM. */
+        (void) sd_notifyf(0, "EXIT_STATUS=%i", arg_exit_code);
+
         if (streq(arg_verb, "exit")) {
                 if (in_container) {
                         log_info("Exiting container.");
index bae4d9d023b0d5c058f4ddf24fa95d2cb55a743c..93e082d77eaa353528a71f757ec31779bfe2177d 100644 (file)
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "constants.h"
 #include "device-util.h"
+#include "devnum-util.h"
 #include "dirent-util.h"
 #include "escape.h"
 #include "fd-util.h"
@@ -788,7 +789,7 @@ static int mount_points_list_umount(MountPoint **head, bool *changed, bool last_
                  * /run/shutdown/mounts from there.
                  */
                 if (!resolved_mounts_path)
-                        (void) chase_symlinks("/run/shutdown/mounts", NULL, 0, &resolved_mounts_path, NULL);
+                        (void) chase("/run/shutdown/mounts", NULL, 0, &resolved_mounts_path, NULL);
                 if (!path_equal(dirname, resolved_mounts_path)) {
                         char newpath[STRLEN("/run/shutdown/mounts/") + 16 + 1];
 
@@ -890,7 +891,7 @@ static int dm_points_list_detach(MountPoint **head, bool *changed, bool last_try
                         continue;
                 }
 
-                log_info("Detaching DM %s (%u:%u).", m->path, major(m->devnum), minor(m->devnum));
+                log_info("Detaching DM %s (" DEVNUM_FORMAT_STR ").", m->path, DEVNUM_FORMAT_VAL(m->devnum));
                 r = delete_dm(m);
                 if (r < 0) {
                         log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not detach DM %s: %m", m->path);
@@ -920,7 +921,7 @@ static int md_points_list_detach(MountPoint **head, bool *changed, bool last_try
                         continue;
                 }
 
-                log_info("Stopping MD %s (%u:%u).", m->path, major(m->devnum), minor(m->devnum));
+                log_info("Stopping MD %s (" DEVNUM_FORMAT_STR ").", m->path, DEVNUM_FORMAT_VAL(m->devnum));
                 r = delete_md(m);
                 if (r < 0) {
                         log_full_errno(last_try ? LOG_ERR : LOG_INFO, r, "Could not stop MD %s: %m", m->path);
index f82a90dc1b20608370918be45cf357e8a375b92f..a8a451ee1a88339908f1119ffa81655a6c09dd39 100644 (file)
@@ -23,6 +23,7 @@
 #include "bus-locator.h"
 #include "bus-util.h"
 #include "constants.h"
+#include "devnum-util.h"
 #include "exec-util.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -45,13 +46,14 @@ static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID;
 
 static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
         char offset_str[DECIMAL_STR_MAX(uint64_t)];
-        char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")];
+        const char *resume_str;
         int r;
 
         assert(hibernate_location);
         assert(hibernate_location->swap);
 
-        xsprintf(resume_str, "%u:%u", major(hibernate_location->devno), minor(hibernate_location->devno));
+        resume_str = FORMAT_DEVNUM(hibernate_location->devno);
+
         r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER);
         if (r < 0)
                 return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m",
index dd80d6f7c906742dac384d15afc437839fe4a0fa..87eed541f06974aea0f70409c37d0f5059262205 100644 (file)
@@ -13,6 +13,7 @@
 #include "bus-error.h"
 #include "constants.h"
 #include "env-util.h"
+#include "initrd-util.h"
 #include "log.h"
 #include "main-func.h"
 #include "process-util.h"
@@ -43,12 +44,12 @@ static int reload_manager(sd_bus *bus) {
         return 0;
 }
 
-static int default_target_is_inactive(sd_bus *bus) {
+static int target_is_inactive(sd_bus *bus, const char *target) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_free_ char *path = NULL, *state = NULL;
         int r;
 
-        path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
+        path = unit_dbus_path_from_name(target);
         if (!path)
                 return log_oom();
 
@@ -65,11 +66,11 @@ static int default_target_is_inactive(sd_bus *bus) {
         return streq_ptr(state, "inactive");
 }
 
-static int start_default_target(sd_bus *bus) {
+static int start_target(sd_bus *bus, const char *target) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         int r;
 
-        log_info("Starting "SPECIAL_DEFAULT_TARGET);
+        log_info("Starting %s", target);
 
         /* Start this unit only if we can replace basic.target with it */
         r = bus_call_method(
@@ -78,10 +79,10 @@ static int start_default_target(sd_bus *bus) {
                         "StartUnit",
                         &error,
                         NULL,
-                        "ss", SPECIAL_DEFAULT_TARGET, "isolate");
+                        "ss", target, "isolate");
 
         if (r < 0)
-                return log_error_errno(r, "Failed to start "SPECIAL_DEFAULT_TARGET": %s", bus_error_message(&error, r));
+                return log_error_errno(r, "Failed to start %s: %s", target, bus_error_message(&error, r));
 
         return 0;
 }
@@ -105,8 +106,7 @@ static int fork_wait(const char* const cmdline[]) {
 
 static void print_mode(const char* mode) {
         printf("You are in %s mode. After logging in, type \"journalctl -xb\" to view\n"
-                "system logs, \"systemctl reboot\" to reboot, \"systemctl default\" or \"exit\"\n"
-                "to boot into default mode.\n", mode);
+               "system logs, \"systemctl reboot\" to reboot, or \"exit\"\n" "to continue bootup.\n", mode);
         fflush(stdout);
 }
 
@@ -140,15 +140,17 @@ static int run(int argc, char *argv[]) {
                 if (reload_manager(bus) < 0)
                         goto fallback;
 
-                r = default_target_is_inactive(bus);
+                const char *target = in_initrd() ? SPECIAL_INITRD_TARGET : SPECIAL_DEFAULT_TARGET;
+
+                r = target_is_inactive(bus, target);
                 if (r < 0)
                         goto fallback;
                 if (!r) {
-                        log_warning(SPECIAL_DEFAULT_TARGET" is not inactive. Please review the "SPECIAL_DEFAULT_TARGET" setting.\n");
+                        log_warning("%s is not inactive. Please review the %s setting.\n", target, target);
                         goto fallback;
                 }
 
-                if (start_default_target(bus) >= 0)
+                if (start_target(bus, target) >= 0)
                         break;
 
         fallback:
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 4553e90a505064bc03e6847d12599a8cc2346dca..f5b2248040462260273095a69757098593a31d47 100644 (file)
@@ -9,13 +9,13 @@
 
 #include "build.h"
 #include "capability-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "devnum-util.h"
 #include "discover-image.h"
 #include "dissect-image.h"
 #include "env-util.h"
 #include "escape.h"
-#include "extension-release.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
 #include "user-util.h"
 #include "verbs.h"
 
-static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default */
+static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */
 static char *arg_root = NULL;
 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
 static bool arg_force = false;
+static int arg_noexec = -1;
+static ImagePolicy *arg_image_policy = NULL;
+
+/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
+static ImageClass arg_image_class = IMAGE_SYSEXT;
 
 STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
+
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+        const char *dot_directory_name;
+        const char *directory_name;
+        const char *short_identifier;
+        const char *short_identifier_plural;
+        const char *level_env;
+        const char *scope_env;
+        const char *name_env;
+        const ImagePolicy *default_image_policy;
+        unsigned long default_mount_flags;
+} image_class_info[_IMAGE_CLASS_MAX] = {
+        [IMAGE_SYSEXT] = {
+                .dot_directory_name = ".systemd-sysext",
+                .directory_name = "systemd-sysext",
+                .short_identifier = "sysext",
+                .short_identifier_plural = "extensions",
+                .level_env = "SYSEXT_LEVEL",
+                .scope_env = "SYSEXT_SCOPE",
+                .name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
+                .default_image_policy = &image_policy_sysext,
+                .default_mount_flags = MS_RDONLY|MS_NODEV,
+        },
+        [IMAGE_CONFEXT] = {
+                .dot_directory_name = ".systemd-confext",
+                .directory_name = "systemd-confext",
+                .short_identifier = "confext",
+                .short_identifier_plural = "confexts",
+                .level_env = "CONFEXT_LEVEL",
+                .scope_env = "CONFEXT_SCOPE",
+                .name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
+                .default_image_policy = &image_policy_confext,
+                .default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
+        }
+};
 
 static int is_our_mount_point(const char *p) {
         _cleanup_free_ char *buf = NULL, *f = NULL;
@@ -70,26 +112,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);
@@ -131,7 +173,7 @@ static int unmerge(void) {
         STRV_FOREACH(p, arg_hierarchies) {
                 _cleanup_free_ char *resolved = NULL;
 
-                r = chase_symlinks(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
+                r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
                 if (r == -ENOENT) {
                         log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
                         continue;
@@ -179,7 +221,7 @@ static int verb_status(int argc, char **argv, void *userdata) {
                 _cleanup_strv_free_ char **l = NULL;
                 struct stat st;
 
-                r = chase_symlinks(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
+                r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
                 if (r == -ENOENT) {
                         log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
                         continue;
@@ -205,7 +247,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();
 
@@ -250,6 +292,7 @@ static int mount_overlayfs(
 
         _cleanup_free_ char *options = NULL;
         bool separator = false;
+        unsigned long flags;
         int r;
 
         assert(where);
@@ -271,8 +314,12 @@ static int mount_overlayfs(
                 separator = true;
         }
 
+        flags = image_class_info[arg_image_class].default_mount_flags;
+        if (arg_noexec >= 0)
+                SET_FLAG(flags, MS_NOEXEC, arg_noexec);
+
         /* Now mount the actual overlayfs */
-        r = mount_nofollow_verbose(LOG_ERR, "sysext", where, "overlay", MS_RDONLY, options);
+        r = mount_nofollow_verbose(LOG_ERR, image_class_info[arg_image_class].short_identifier, where, "overlay", flags, options);
         if (r < 0)
                 return r;
 
@@ -297,7 +344,7 @@ static int merge_hierarchy(
 
         /* Resolve the path of the host's version of the hierarchy, i.e. what we want to use as lowest layer
          * in the overlayfs stack. */
-        r = chase_symlinks(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
+        r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_hierarchy, NULL);
         if (r == -ENOENT)
                 log_debug_errno(r, "Hierarchy '%s' on host doesn't exist, not merging.", hierarchy);
         else if (r < 0)
@@ -315,7 +362,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();
 
@@ -336,7 +383,7 @@ static int merge_hierarchy(
         STRV_FOREACH(p, paths) {
                 _cleanup_free_ char *resolved = NULL;
 
-                r = chase_symlinks(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
+                r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL);
                 if (r == -ENOENT) {
                         log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p);
                         continue;
@@ -383,16 +430,16 @@ 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();
 
-        r = write_string_filef(f, WRITE_STRING_FILE_CREATE, "%u:%u", major(st.st_dev), minor(st.st_dev));
+        r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE);
         if (r < 0)
                 return log_error_errno(r, "Failed to write '%s': %m", f);
 
@@ -408,50 +455,27 @@ static int strverscmp_improvedp(char *const* a, char *const* b) {
         return strverscmp_improved(*a, *b);
 }
 
-static int validate_version(
-                const char *root,
-                const Image *img,
-                const char *host_os_release_id,
-                const char *host_os_release_version_id,
-                const char *host_os_release_sysext_level) {
-
-        int r;
-
-        assert(root);
+static const ImagePolicy *pick_image_policy(const Image *img) {
         assert(img);
+        assert(img->path);
 
-        if (arg_force) {
-                log_debug("Force mode enabled, skipping version validation.");
-                return 1;
-        }
+        /* Explicitly specified policy always wins */
+        if (arg_image_policy)
+                return arg_image_policy;
 
-        /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
-         * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
-         * merged.) */
-        r = chase_symlinks("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
-        if (r < 0) {
-                if (r != -ENOENT)
-                        return log_error_errno(r, "Failed to determine whether /usr/lib/os-release exists in the extension image: %m");
-        } else
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Extension image contains /usr/lib/os-release file, which is not allowed (it may carry /etc/os-release), refusing.");
-
-        r = extension_release_validate(
-                        img->name,
-                        host_os_release_id,
-                        host_os_release_version_id,
-                        host_os_release_sysext_level,
-                        in_initrd() ? "initrd" : "system",
-                        img->extension_release);
-        if (r < 0)
-                return log_error_errno(r, "Failed to validate extension release information: %m");
+        /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
+         * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
+         * other directories we assume the appropriate level of trust was already established already.  */
+
+        if (in_initrd() && path_startswith(img->path, "/.extra/sysext/"))
+                return &image_policy_sysext_strict;
 
-        return r;
+        return image_class_info[img->class].default_image_policy;
 }
 
 static int merge_subprocess(Hashmap *images, const char *workspace) {
         _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL,
-                *buf = NULL;
+            *host_os_release_confext_level = NULL, *buf = NULL;
         _cleanup_strv_free_ char **extensions = NULL, **paths = NULL;
         size_t n_extensions = 0;
         unsigned n_ignored = 0;
@@ -478,11 +502,13 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                 return r;
 
         /* Acquire host OS release info, so that we can compare it with the extension's data */
+        char **host_os_release_level = (arg_image_class == IMAGE_CONFEXT) ? &host_os_release_confext_level : &host_os_release_sysext_level;
         r = parse_os_release(
                         arg_root,
                         "ID", &host_os_release_id,
                         "VERSION_ID", &host_os_release_version_id,
-                        "SYSEXT_LEVEL", &host_os_release_sysext_level);
+                        image_class_info[arg_image_class].level_env,
+                        host_os_release_level);
         if (r < 0)
                 return log_error_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(arg_root));
         if (isempty(host_os_release_id))
@@ -494,7 +520,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         HASHMAP_FOREACH(img, images) {
                 _cleanup_free_ char *p = NULL;
 
-                p = path_join(workspace, "extensions", img->name);
+                p = path_join(workspace, image_class_info[arg_image_class].short_identifier_plural, img->name);
                 if (!p)
                         return log_oom();
 
@@ -505,6 +531,17 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                 switch (img->type) {
                 case IMAGE_DIRECTORY:
                 case IMAGE_SUBVOLUME:
+
+                        if (!arg_force) {
+                                r = extension_has_forbidden_content(p);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0) {
+                                        n_ignored++;
+                                        continue;
+                                }
+                        }
+
                         r = mount_nofollow_verbose(LOG_ERR, img->path, p, NULL, MS_BIND, NULL);
                         if (r < 0)
                                 return r;
@@ -537,6 +574,9 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         if (verity_settings.data_path)
                                 flags |= DISSECT_IMAGE_NO_PARTITION_TABLE;
 
+                        if (!arg_force)
+                                flags |= DISSECT_IMAGE_VALIDATE_OS_EXT;
+
                         r = loop_device_make_by_path(
                                         img->path,
                                         O_RDONLY,
@@ -550,7 +590,8 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         r = dissect_loop_device_and_warn(
                                         d,
                                         &verity_settings,
-                                        NULL,
+                                        /* mount_options= */ NULL,
+                                        pick_image_policy(img),
                                         flags,
                                         &m);
                         if (r < 0)
@@ -576,8 +617,12 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                                         UID_INVALID,
                                         UID_INVALID,
                                         flags);
-                        if (r < 0)
+                        if (r < 0 && r != -ENOMEDIUM)
                                 return r;
+                        if (r == -ENOMEDIUM && !arg_force) {
+                                n_ignored++;
+                                continue;
+                        }
 
                         r = dissected_image_relinquish(m);
                         if (r < 0)
@@ -588,17 +633,23 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         assert_not_reached();
                 }
 
-                r = validate_version(
-                                p,
-                                img,
-                                host_os_release_id,
-                                host_os_release_version_id,
-                                host_os_release_sysext_level);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        n_ignored++;
-                        continue;
+                if (arg_force)
+                        log_debug("Force mode enabled, skipping version validation.");
+                else {
+                        r = extension_release_validate(
+                                        img->name,
+                                        host_os_release_id,
+                                        host_os_release_version_id,
+                                        host_os_release_sysext_level,
+                                        in_initrd() ? "initrd" : "system",
+                                        img->extension_release,
+                                        arg_image_class);
+                        if (r < 0)
+                                return r;
+                        if (r == 0) {
+                                n_ignored++;
+                                continue;
+                        }
                 }
 
                 /* Nice! This one is an extension we want. */
@@ -612,7 +663,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         /* Nothing left? Then shortcut things */
         if (n_extensions == 0) {
                 if (n_ignored > 0)
-                        log_info("No suitable extensions found (%u ignored due to incompatible version).", n_ignored);
+                        log_info("No suitable extensions found (%u ignored due to incompatible image(s)).", n_ignored);
                 else
                         log_info("No extensions found.");
                 return 0;
@@ -637,7 +688,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();
 
@@ -649,7 +700,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
         STRV_FOREACH(h, arg_hierarchies) {
                 _cleanup_free_ char *resolved = NULL;
 
-                r = chase_symlinks(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+                r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
 
@@ -691,7 +742,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
                         continue;
                 }
 
-                r = chase_symlinks(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+                r = chase(*h, arg_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve hierarchy '%s%s': %m", strempty(arg_root), *h);
 
@@ -713,7 +764,7 @@ static int merge(Hashmap *images) {
         pid_t pid;
         int r;
 
-        r = safe_fork("(sd-sysext)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
+        r = safe_fork("(sd-merge)", FORK_DEATHSIG|FORK_LOG|FORK_NEW_MOUNTNS, &pid);
         if (r < 0)
                 return log_error_errno(r, "Failed to fork off child: %m");
         if (r == 0) {
@@ -729,7 +780,7 @@ static int merge(Hashmap *images) {
                 _exit(r > 0 ? EXIT_SUCCESS : 123); /* 123 means: didn't find any extensions */
         }
 
-        r = wait_for_terminate_and_check("(sd-sysext)", pid, WAIT_LOG_ABNORMAL);
+        r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
         if (r < 0)
                 return r;
 
@@ -747,12 +798,12 @@ 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);
+                r = image_read_metadata(img, &image_policy_sysext);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
         }
@@ -781,7 +832,7 @@ static int verb_merge(int argc, char **argv, void *userdata) {
         STRV_FOREACH(p, arg_hierarchies) {
                 _cleanup_free_ char *resolved = NULL;
 
-                r = chase_symlinks(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
+                r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL);
                 if (r == -ENOENT) {
                         log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p);
                         continue;
@@ -850,9 +901,9 @@ static int verb_list(int argc, char **argv, void *userdata) {
         if (!images)
                 return log_oom();
 
-        r = image_discover(IMAGE_EXTENSION, arg_root, images);
+        r = image_discover(arg_image_class, arg_root, images);
         if (r < 0)
-                return log_error_errno(r, "Failed to discover extension images: %m");
+                return log_error_errno(r, "Failed to discover images: %m");
 
         if ((arg_json_format_flags & JSON_FORMAT_OFF) && hashmap_isempty(images)) {
                 log_info("No OS extensions found.");
@@ -888,11 +939,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"
@@ -904,6 +955,9 @@ static int verb_help(int argc, char **argv, void *userdata) {
                "     --json=pretty|short|off\n"
                "                          Generate JSON output\n"
                "     --force              Ignore version incompatibilities\n"
+               "     --image-policy=POLICY\n"
+               "                          Specify disk image dissection policy\n"
+               "     --noexec=BOOL        Whether to mount extension overlay with noexec\n"
                "\nSee the %2$s for details.\n",
                program_invocation_short_name,
                link,
@@ -924,16 +978,20 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_ROOT,
                 ARG_JSON,
                 ARG_FORCE,
+                ARG_IMAGE_POLICY,
+                ARG_NOEXEC,
         };
 
         static const struct option options[] = {
-                { "help",      no_argument,       NULL, 'h'           },
-                { "version",   no_argument,       NULL, ARG_VERSION   },
-                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
-                { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
-                { "root",      required_argument, NULL, ARG_ROOT      },
-                { "json",      required_argument, NULL, ARG_JSON      },
-                { "force",     no_argument,       NULL, ARG_FORCE     },
+                { "help",         no_argument,       NULL, 'h'              },
+                { "version",      no_argument,       NULL, ARG_VERSION      },
+                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
+                { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND    },
+                { "root",         required_argument, NULL, ARG_ROOT         },
+                { "json",         required_argument, NULL, ARG_JSON         },
+                { "force",        no_argument,       NULL, ARG_FORCE        },
+                { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+                { "noexec",       required_argument, NULL, ARG_NOEXEC       },
                 {}
         };
 
@@ -977,6 +1035,20 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_force = true;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_NOEXEC:
+                        r = parse_boolean_argument("--noexec", optarg, NULL);
+                        if (r < 0)
+                                return r;
+
+                        arg_noexec = r;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1004,6 +1076,7 @@ static int sysext_main(int argc, char *argv[]) {
 
 static int run(int argc, char *argv[]) {
         int r;
+        arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
 
         log_setup();
 
@@ -1014,9 +1087,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 54cd943af7ab8af992946619394443d51951a395..ee5f8ec12ecf261cdfcc319429ec58603444149d 100644 (file)
@@ -6,6 +6,7 @@
 #include "fs-util.h"
 #include "generator.h"
 #include "log.h"
+#include "path-util.h"
 #include "proc-cmdline.h"
 #include "special.h"
 #include "string-util.h"
 static const char *arg_dest = NULL;
 
 static int generate_symlink(void) {
-        const char *p = NULL;
+        _cleanup_free_ char *j = NULL;
 
-        if (laccess("/system-update", F_OK) < 0) {
-                if (errno == ENOENT)
-                        return 0;
+        FOREACH_STRING(p, "/system-update", "/etc/system-update") {
+                if (laccess(p, F_OK) >= 0)
+                        goto link_found;
 
-                log_error_errno(errno, "Failed to check for system update: %m");
-                return -EINVAL;
+                if (errno != ENOENT)
+                        log_warning_errno(errno, "Failed to check if %s symlink exists, ignoring: %m", p);
         }
 
-        p = strjoina(arg_dest, "/" SPECIAL_DEFAULT_TARGET);
-        if (symlink(SYSTEM_DATA_UNIT_DIR "/system-update.target", p) < 0)
-                return log_error_errno(errno, "Failed to create symlink %s: %m", p);
+        return 0;
+
+link_found:
+        j = path_join(arg_dest, SPECIAL_DEFAULT_TARGET);
+        if (!j)
+                return log_oom();
+
+        if (symlink(SYSTEM_DATA_UNIT_DIR "/system-update.target", j) < 0)
+                return log_error_errno(errno, "Failed to create symlink %s: %m", j);
 
         return 1;
 }
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 5c15a9fba0749b0b74c1de7c90fef07bc0730e86..40d5f6d557a7beb18ff7eaa78e786e14a907ba4f 100644 (file)
@@ -21,7 +21,7 @@ int verb_clean_or_freeze(int argc, char *argv[], void *userdata) {
         polkit_agent_open_maybe();
 
         if (!arg_clean_what) {
-                arg_clean_what = strv_new("cache", "runtime");
+                arg_clean_what = strv_new("cache", "runtime", "fdstore");
                 if (!arg_clean_what)
                         return log_oom();
         }
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 bc4fc54c5ab2262645bb10eb063163bc341a0529..8d791deaf1c9922227a4c1b883450935a4ac7430 100644 (file)
@@ -14,6 +14,8 @@ static int show_installation_targets_client_side(const char *name) {
         char **p;
         int r;
 
+        CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
         p = STRV_MAKE(name);
         flags = UNIT_FILE_DRY_RUN |
                 (arg_runtime ? UNIT_FILE_RUNTIME : 0);
index 72b377cde6152a5c9580d19ab521037cb882e889..aad248fe1f56989b0a2406cf23cc2609b0534742 100644 (file)
@@ -49,9 +49,24 @@ static bool output_show_unit_file(const UnitFileList *u, char **states, char **p
         return true;
 }
 
+static const char* preset_action_to_color(PresetAction action, bool underline) {
+        assert(action >= 0);
+
+        switch (action) {
+        case PRESET_ENABLE:
+                return underline ? ansi_highlight_green_underline() : ansi_highlight_green();
+        case PRESET_DISABLE:
+                return underline ? ansi_highlight_red_underline() : ansi_highlight_red();
+        case PRESET_IGNORE:
+                return underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
+        default:
+                return NULL;
+        }
+}
+
 static int output_unit_file_list(const UnitFileList *units, unsigned c) {
         _cleanup_(table_unrefp) Table *table = NULL;
-        _cleanup_(unit_file_presets_freep) UnitFilePresets presets = {};
+        _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
         int r;
 
         table = table_new("unit file", "state", "preset");
@@ -98,22 +113,14 @@ static int output_unit_file_list(const UnitFileList *units, unsigned c) {
                         return table_log_add_error(r);
 
                 if (show_preset_for_state(u->state)) {
-                        const char *unit_preset_str, *on_preset_color;
+                        const char *on_preset_color = underline ? on_underline : ansi_normal();
 
                         r = unit_file_query_preset(arg_runtime_scope, arg_root, id, &presets);
-                        if (r < 0) {
-                                unit_preset_str = "n/a";
-                                on_preset_color = underline ? on_underline : ansi_normal();
-                        } else if (r == 0) {
-                                unit_preset_str = "disabled";
-                                on_preset_color = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
-                        } else {
-                                unit_preset_str = "enabled";
-                                on_preset_color = underline ? ansi_highlight_green_underline() : ansi_highlight_green();
-                        }
+                        if (r >= 0)
+                                on_preset_color = preset_action_to_color(r, underline);
 
                         r = table_add_many(table,
-                                           TABLE_STRING, unit_preset_str,
+                                           TABLE_STRING, strna(preset_action_past_tense_to_string(r)),
                                            TABLE_SET_BOTH_COLORS, strempty(on_preset_color));
                 } else
                         r = table_add_many(table,
@@ -136,6 +143,7 @@ static int output_unit_file_list(const UnitFileList *units, unsigned c) {
 int verb_list_unit_files(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_free_ UnitFileList *units = NULL;
+        _cleanup_(hashmap_freep) Hashmap *h = NULL;
         unsigned c = 0;
         const char *state;
         char *path;
@@ -143,40 +151,31 @@ int verb_list_unit_files(int argc, char *argv[], void *userdata) {
         bool fallback = false;
 
         if (install_client_side()) {
-                Hashmap *h;
                 UnitFileList *u;
                 unsigned n_units;
 
-                h = hashmap_new(&string_hash_ops);
+                h = hashmap_new(&unit_file_list_hash_ops_free);
                 if (!h)
                         return log_oom();
 
                 r = unit_file_get_list(arg_runtime_scope, arg_root, h, arg_states, strv_skip(argv, 1));
-                if (r < 0) {
-                        unit_file_list_free(h);
+                if (r < 0)
                         return log_error_errno(r, "Failed to get unit file list: %m");
-                }
 
                 n_units = hashmap_size(h);
 
-                units = new(UnitFileList, n_units ?: 1); /* avoid malloc(0) */
-                if (!units) {
-                        unit_file_list_free(h);
+                units = new(UnitFileList, n_units);
+                if (!units)
                         return log_oom();
-                }
 
                 HASHMAP_FOREACH(u, h) {
                         if (!output_show_unit_file(u, NULL, NULL))
                                 continue;
 
                         units[c++] = *u;
-                        free(u);
                 }
 
                 assert(c <= n_units);
-                hashmap_free(h);
-
-                r = 0;
         } else {
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -262,10 +261,6 @@ int verb_list_unit_files(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return r;
 
-        if (install_client_side())
-                for (UnitFileList *unit = units; unit < units + c; unit++)
-                        free(unit->path);
-
         if (c == 0)
                 return -ENOENT;
 
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 f3d09e57e65494cead15d062e175080f516569c8..6422550af48afa54409f5068630385eaafe2e365 100644 (file)
@@ -207,6 +207,9 @@ typedef struct UnitStatusInfo {
         bool running:1;
         int status_errno;
 
+        uint32_t fd_store_max;
+        uint32_t n_fd_store;
+
         usec_t start_timestamp;
         usec_t exit_timestamp;
 
@@ -513,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));
@@ -678,7 +681,7 @@ static void print_status_info(
         }
 
         if (i->status_text)
-                printf("     Status: \"%s\"\n", i->status_text);
+                printf("     Status: \"%s%s%s\"\n", ansi_highlight_cyan(), i->status_text, ansi_normal());
         if (i->status_errno > 0) {
                 errno = i->status_errno;
                 printf("      Error: %i (%m)\n", i->status_errno);
@@ -703,6 +706,9 @@ static void print_status_info(
                         printf("\n");
         }
 
+        if (i->n_fd_store > 0 || i->fd_store_max > 0)
+                printf("   FD Store: %u%s (limit: %u)%s\n", i->n_fd_store, ansi_grey(), i->fd_store_max, ansi_normal());
+
         if (i->memory_current != UINT64_MAX) {
                 printf("     Memory: %s", FORMAT_BYTES(i->memory_current));
 
@@ -1991,6 +1997,8 @@ static int show_one(
                 { "StatusText",                     "s",               NULL,           offsetof(UnitStatusInfo, status_text)                       },
                 { "PIDFile",                        "s",               NULL,           offsetof(UnitStatusInfo, pid_file)                          },
                 { "StatusErrno",                    "i",               NULL,           offsetof(UnitStatusInfo, status_errno)                      },
+                { "FileDescriptorStoreMax",         "u",               NULL,           offsetof(UnitStatusInfo, fd_store_max)                      },
+                { "NFileDescriptorStore",           "u",               NULL,           offsetof(UnitStatusInfo, n_fd_store)                        },
                 { "ExecMainStartTimestamp",         "t",               NULL,           offsetof(UnitStatusInfo, start_timestamp)                   },
                 { "ExecMainExitTimestamp",          "t",               NULL,           offsetof(UnitStatusInfo, exit_timestamp)                    },
                 { "ExecMainCode",                   "i",               NULL,           offsetof(UnitStatusInfo, exit_code)                         },
@@ -2061,10 +2069,14 @@ static int show_one(
                 .runtime_max_sec = USEC_INFINITY,
                 .memory_current = UINT64_MAX,
                 .memory_high = CGROUP_LIMIT_MAX,
+                .startup_memory_high = CGROUP_LIMIT_MAX,
                 .memory_max = CGROUP_LIMIT_MAX,
+                .startup_memory_max = CGROUP_LIMIT_MAX,
                 .memory_swap_max = CGROUP_LIMIT_MAX,
+                .startup_memory_swap_max = CGROUP_LIMIT_MAX,
                 .memory_zswap_max = CGROUP_LIMIT_MAX,
-                .memory_limit = UINT64_MAX,
+                .startup_memory_zswap_max = CGROUP_LIMIT_MAX,
+                .memory_limit = CGROUP_LIMIT_MAX,
                 .memory_available = CGROUP_LIMIT_MAX,
                 .cpu_usage_nsec = UINT64_MAX,
                 .tasks_current = UINT64_MAX,
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 5c71d3d97fc2cf5184237208861fde780a983503..8cfcaf89358406a16ef48c4c26af52544948e479 100644 (file)
@@ -3,7 +3,7 @@
 #include "argv-util.h"
 #include "bus-error.h"
 #include "bus-locator.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "proc-cmdline.h"
@@ -21,11 +21,11 @@ static int same_file_in_root(
         struct stat sta, stb;
         int r;
 
-        r = chase_symlinks_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta);
+        r = chase_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta);
         if (r < 0)
                 return r;
 
-        r = chase_symlinks_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb);
+        r = chase_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb);
         if (r < 0)
                 return r;
 
index 6e87b184943adb459086b8016d82d3b165744d6d..b6b28a113d85dc95150a5d5c5b656ce30fc5721f 100644 (file)
@@ -10,7 +10,7 @@
 #include "bus-locator.h"
 #include "bus-map-properties.h"
 #include "bus-unit-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dropin.h"
 #include "env-util.h"
 #include "exit-status.h"
@@ -422,7 +422,7 @@ int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **ret_unit_
                 if (!path)
                         return log_oom();
 
-                r = chase_symlinks(path, arg_root, 0, &lpath, NULL);
+                r = chase(path, arg_root, 0, &lpath, NULL);
                 if (r == -ENOENT)
                         continue;
                 if (r == -ENOMEM)
index 1a5beabc7221c2650a8f63442574f770bd814285..201d64a1f999d43c57c417a5857fccff5422742e 100644 (file)
@@ -121,6 +121,7 @@ bool arg_read_only = false;
 bool arg_mkdir = false;
 bool arg_marked = false;
 const char *arg_drop_in = NULL;
+ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep);
@@ -135,6 +136,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_host, unsetp);
 STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp);
 STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_drop_in, unsetp);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static int systemctl_help(void) {
         _cleanup_free_ char *link = NULL;
@@ -305,7 +307,9 @@ static int systemctl_help(void) {
                "     --root=PATH         Edit/enable/disable/mask unit files in the specified\n"
                "                         root directory\n"
                "     --image=PATH        Edit/enable/disable/mask unit files in the specified\n"
-               "                         image\n"
+               "                         disk image\n"
+               "     --image-policy=POLICY\n"
+               "                         Specify disk image dissection policy\n"
                "  -n --lines=INTEGER     Number of journal entries to show\n"
                "  -o --output=STRING     Change journal output mode (short, short-precise,\n"
                "                             short-iso, short-iso-precise, short-full,\n"
@@ -424,6 +428,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 ARG_NO_WALL,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_NO_RELOAD,
                 ARG_KILL_WHOM,
                 ARG_KILL_VALUE,
@@ -485,6 +490,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "no-warn",             no_argument,       NULL, ARG_NO_WARN             },
                 { "root",                required_argument, NULL, ARG_ROOT                },
                 { "image",               required_argument, NULL, ARG_IMAGE               },
+                { "image-policy",        required_argument, NULL, ARG_IMAGE_POLICY        },
                 { "force",               no_argument,       NULL, 'f'                     },
                 { "no-reload",           no_argument,       NULL, ARG_NO_RELOAD           },
                 { "kill-whom",           required_argument, NULL, ARG_KILL_WHOM           },
@@ -700,6 +706,12 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case 'l':
                         arg_full = true;
                         break;
@@ -931,7 +943,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                                              "state\n"
                                              "cache\n"
                                              "logs\n"
-                                             "configuration");
+                                             "configuration\n"
+                                             "fdstore");
                                         return 0;
                                 }
 
@@ -1248,6 +1261,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK |
index 8c96ee0b4f0ed6f26e5fef02a1bef8140eaa8f7a..03645624ee82706630b6d1e058495f3091d3c043 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "bus-print-properties.h"
 #include "bus-util.h"
+#include "image-policy.h"
 #include "install.h"
 #include "output-mode.h"
 #include "pager.h"
@@ -100,6 +101,7 @@ extern bool arg_read_only;
 extern bool arg_mkdir;
 extern bool arg_marked;
 extern const char *arg_drop_in;
+extern ImagePolicy *arg_image_policy;
 
 static inline const char* arg_job_mode(void) {
         return _arg_job_mode ?: "replace";
index 1581642275b71b3d9cdf1c89e3eba751943a9fd0..25c9ab3358c981a452cb2f7e2d880c2e4c6e88f7 100644 (file)
@@ -62,37 +62,40 @@ enum {
 /* Well-known errors. Note that this is only a sanitized subset of the
  * errors that the reference implementation generates. */
 
-#define SD_BUS_ERROR_FAILED                     "org.freedesktop.DBus.Error.Failed"
-#define SD_BUS_ERROR_NO_MEMORY                  "org.freedesktop.DBus.Error.NoMemory"
-#define SD_BUS_ERROR_SERVICE_UNKNOWN            "org.freedesktop.DBus.Error.ServiceUnknown"
-#define SD_BUS_ERROR_NAME_HAS_NO_OWNER          "org.freedesktop.DBus.Error.NameHasNoOwner"
-#define SD_BUS_ERROR_NO_REPLY                   "org.freedesktop.DBus.Error.NoReply"
-#define SD_BUS_ERROR_IO_ERROR                   "org.freedesktop.DBus.Error.IOError"
-#define SD_BUS_ERROR_BAD_ADDRESS                "org.freedesktop.DBus.Error.BadAddress"
-#define SD_BUS_ERROR_NOT_SUPPORTED              "org.freedesktop.DBus.Error.NotSupported"
-#define SD_BUS_ERROR_LIMITS_EXCEEDED            "org.freedesktop.DBus.Error.LimitsExceeded"
-#define SD_BUS_ERROR_ACCESS_DENIED              "org.freedesktop.DBus.Error.AccessDenied"
-#define SD_BUS_ERROR_AUTH_FAILED                "org.freedesktop.DBus.Error.AuthFailed"
-#define SD_BUS_ERROR_NO_SERVER                  "org.freedesktop.DBus.Error.NoServer"
-#define SD_BUS_ERROR_TIMEOUT                    "org.freedesktop.DBus.Error.Timeout"
-#define SD_BUS_ERROR_NO_NETWORK                 "org.freedesktop.DBus.Error.NoNetwork"
-#define SD_BUS_ERROR_ADDRESS_IN_USE             "org.freedesktop.DBus.Error.AddressInUse"
-#define SD_BUS_ERROR_DISCONNECTED               "org.freedesktop.DBus.Error.Disconnected"
-#define SD_BUS_ERROR_INVALID_ARGS               "org.freedesktop.DBus.Error.InvalidArgs"
-#define SD_BUS_ERROR_FILE_NOT_FOUND             "org.freedesktop.DBus.Error.FileNotFound"
-#define SD_BUS_ERROR_FILE_EXISTS                "org.freedesktop.DBus.Error.FileExists"
-#define SD_BUS_ERROR_UNKNOWN_METHOD             "org.freedesktop.DBus.Error.UnknownMethod"
-#define SD_BUS_ERROR_UNKNOWN_OBJECT             "org.freedesktop.DBus.Error.UnknownObject"
-#define SD_BUS_ERROR_UNKNOWN_INTERFACE          "org.freedesktop.DBus.Error.UnknownInterface"
-#define SD_BUS_ERROR_UNKNOWN_PROPERTY           "org.freedesktop.DBus.Error.UnknownProperty"
-#define SD_BUS_ERROR_PROPERTY_READ_ONLY         "org.freedesktop.DBus.Error.PropertyReadOnly"
-#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN    "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
-#define SD_BUS_ERROR_INVALID_SIGNATURE          "org.freedesktop.DBus.Error.InvalidSignature"
-#define SD_BUS_ERROR_INCONSISTENT_MESSAGE       "org.freedesktop.DBus.Error.InconsistentMessage"
-#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND       "org.freedesktop.DBus.Error.MatchRuleNotFound"
-#define SD_BUS_ERROR_MATCH_RULE_INVALID         "org.freedesktop.DBus.Error.MatchRuleInvalid"
-#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \
-                                                "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"
+#define SD_BUS_ERROR_FAILED                             "org.freedesktop.DBus.Error.Failed"
+#define SD_BUS_ERROR_NO_MEMORY                          "org.freedesktop.DBus.Error.NoMemory"
+#define SD_BUS_ERROR_SERVICE_UNKNOWN                    "org.freedesktop.DBus.Error.ServiceUnknown"
+#define SD_BUS_ERROR_NAME_HAS_NO_OWNER                  "org.freedesktop.DBus.Error.NameHasNoOwner"
+#define SD_BUS_ERROR_NO_REPLY                           "org.freedesktop.DBus.Error.NoReply"
+#define SD_BUS_ERROR_IO_ERROR                           "org.freedesktop.DBus.Error.IOError"
+#define SD_BUS_ERROR_BAD_ADDRESS                        "org.freedesktop.DBus.Error.BadAddress"
+#define SD_BUS_ERROR_NOT_SUPPORTED                      "org.freedesktop.DBus.Error.NotSupported"
+#define SD_BUS_ERROR_LIMITS_EXCEEDED                    "org.freedesktop.DBus.Error.LimitsExceeded"
+#define SD_BUS_ERROR_ACCESS_DENIED                      "org.freedesktop.DBus.Error.AccessDenied"
+#define SD_BUS_ERROR_AUTH_FAILED                        "org.freedesktop.DBus.Error.AuthFailed"
+#define SD_BUS_ERROR_NO_SERVER                          "org.freedesktop.DBus.Error.NoServer"
+#define SD_BUS_ERROR_TIMEOUT                            "org.freedesktop.DBus.Error.Timeout"
+#define SD_BUS_ERROR_NO_NETWORK                         "org.freedesktop.DBus.Error.NoNetwork"
+#define SD_BUS_ERROR_ADDRESS_IN_USE                     "org.freedesktop.DBus.Error.AddressInUse"
+#define SD_BUS_ERROR_DISCONNECTED                       "org.freedesktop.DBus.Error.Disconnected"
+#define SD_BUS_ERROR_INVALID_ARGS                       "org.freedesktop.DBus.Error.InvalidArgs"
+#define SD_BUS_ERROR_FILE_NOT_FOUND                     "org.freedesktop.DBus.Error.FileNotFound"
+#define SD_BUS_ERROR_FILE_EXISTS                        "org.freedesktop.DBus.Error.FileExists"
+#define SD_BUS_ERROR_UNKNOWN_METHOD                     "org.freedesktop.DBus.Error.UnknownMethod"
+#define SD_BUS_ERROR_UNKNOWN_OBJECT                     "org.freedesktop.DBus.Error.UnknownObject"
+#define SD_BUS_ERROR_UNKNOWN_INTERFACE                  "org.freedesktop.DBus.Error.UnknownInterface"
+#define SD_BUS_ERROR_UNKNOWN_PROPERTY                   "org.freedesktop.DBus.Error.UnknownProperty"
+#define SD_BUS_ERROR_PROPERTY_READ_ONLY                 "org.freedesktop.DBus.Error.PropertyReadOnly"
+#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN            "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
+#define SD_BUS_ERROR_INVALID_SIGNATURE                  "org.freedesktop.DBus.Error.InvalidSignature"
+#define SD_BUS_ERROR_INCONSISTENT_MESSAGE               "org.freedesktop.DBus.Error.InconsistentMessage"
+#define SD_BUS_ERROR_TIMED_OUT                          "org.freedesktop.DBus.Error.TimedOut"
+#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND               "org.freedesktop.DBus.Error.MatchRuleNotFound"
+#define SD_BUS_ERROR_MATCH_RULE_INVALID                 "org.freedesktop.DBus.Error.MatchRuleInvalid"
+#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"
+#define SD_BUS_ERROR_INVALID_FILE_CONTENT               "org.freedesktop.DBus.Error.InvalidFileContent"
+#define SD_BUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN   "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"
+#define SD_BUS_ERROR_OBJECT_PATH_IN_USE                 "org.freedesktop.DBus.Error.ObjectPathInUse"
 
 /* https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature */
 #define SD_BUS_MAXIMUM_SIGNATURE_LENGTH 255
index 53a1d7ccfe9c0f825457d47e718af75e70fdbccf..a8b40ff1f8c2c23f431d5868f25df69238713147 100644 (file)
@@ -195,6 +195,10 @@ int sd_is_mq(int fd, const char *path);
                   readable error message. Example: "STATUS=Completed
                   66% of file system check..."
 
+     NOTIFYACCESS=...
+                  Reset the access to the service status notification socket.
+                  Example: "NOTIFYACCESS=main"
+
      ERRNO=...    If a daemon fails, the errno-style error code,
                   formatted as string. Example: "ERRNO=2" for ENOENT.
 
@@ -286,6 +290,11 @@ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _s
 */
 int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds);
 
+/*
+  Combination of sd_pid_notifyf() and sd_pid_notify_with_fds()
+*/
+int sd_pid_notifyf_with_fds(pid_t pid, int unset_environment, const int *fds, size_t n_fds, const char *format, ...) _sd_printf_(5,6);
+
 /*
   Returns > 0 if synchronization with systemd succeeded.  Returns < 0
   on error. Returns 0 if $NOTIFY_SOCKET was not set. Note that the
index 6c05d245f8c473d52114e08b3126ac15a9924ac9..5f06377ade9c600af353ca5a0864816ae105104f 100644 (file)
@@ -6,7 +6,7 @@
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "device-util.h"
 #include "devnum-util.h"
 #include "dirent-util.h"
@@ -284,7 +284,6 @@ static int download_manifest(
                         NULL
                 };
 
-                (void) unsetenv("NOTIFY_SOCKET");
                 execv(pull_binary_path(), (char *const*) cmdline);
                 log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path());
                 _exit(EXIT_FAILURE);
@@ -563,7 +562,7 @@ int resource_resolve_path(
                 _cleanup_free_ char *resolved = NULL;
                 struct stat st;
 
-                r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd);
+                r = chase(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
 
@@ -599,7 +598,7 @@ int resource_resolve_path(
         } else if (RESOURCE_IS_FILESYSTEM(rr->type) && root) {
                 _cleanup_free_ char *resolved = NULL;
 
-                r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
+                r = chase(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
                 if (r < 0)
                         return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
 
index 0c3d65a00dcbe9fd826bdf6db07fe6a9626a30bf..8ae58c2b1b8a30027b6320c3a95d96af20d4b8b6 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-parser.h"
 #include "dirent-util.h"
 #include "fd-util.h"
@@ -793,7 +793,6 @@ static int run_helper(
         if (r == 0) {
                 /* Child */
 
-                (void) unsetenv("NOTIFY_SOCKET");
                 execv(path, (char *const*) cmdline);
                 log_error_errno(errno, "Failed to execute %s tool: %m", path);
                 _exit(EXIT_FAILURE);
@@ -1211,7 +1210,7 @@ int transfer_install_instance(
                         assert_not_reached();
 
                 if (resolve_link_path && root) {
-                        r = chase_symlinks(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
 
index 4ad901e0809255a0259537154bf425d3ee3cfb18..29cd552ea884e776ad8029cb795585a809cc0522 100644 (file)
@@ -6,7 +6,7 @@
 #include "build.h"
 #include "bus-error.h"
 #include "bus-locator.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "constants.h"
 #include "dirent-util.h"
@@ -46,11 +46,13 @@ static char *arg_image = NULL;
 static bool arg_reboot = false;
 static char *arg_component = NULL;
 static int arg_verify = -1;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 typedef struct Context {
         Transfer **transfers;
@@ -872,6 +874,7 @@ static int process_image(
 
         r = mount_image_privately_interactively(
                         arg_image,
+                        arg_image_policy,
                         (ro ? DISSECT_IMAGE_READ_ONLY : 0) |
                         DISSECT_IMAGE_FSCK |
                         DISSECT_IMAGE_MKDIR |
@@ -1022,7 +1025,7 @@ static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
 
         if (arg_image || arg_root)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "The --root=/--image switches may not be combined with the '%s' operation.", argv[0]);
+                                       "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
 
         r = context_make_offline(&context, NULL);
         if (r < 0)
@@ -1106,7 +1109,7 @@ static int verb_components(int argc, char **argv, void *userdata) {
                 _cleanup_closedir_ DIR *d = NULL;
                 _cleanup_free_ char *p = NULL;
 
-                r = chase_symlinks_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
+                r = chase_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0)
@@ -1205,8 +1208,10 @@ static int verb_help(int argc, char **argv, void *userdata) {
                "\n%3$sOptions:%4$s\n"
                "  -C --component=NAME     Select component to update\n"
                "     --definitions=DIR    Find transfer definitions in specified directory\n"
-               "     --root=PATH          Operate relative to root path\n"
-               "     --image=PATH         Operate relative to image file\n"
+               "     --root=PATH          Operate on an alternate filesystem root\n"
+               "     --image=PATH         Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY\n"
+               "                          Specify disk image dissection policy\n"
                "  -m --instances-max=INT  How many instances to maintain\n"
                "     --sync=BOOL          Controls whether to sync data to disk\n"
                "     --verify=BOOL        Force signature verification on or off\n"
@@ -1236,6 +1241,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_JSON,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_REBOOT,
                 ARG_VERIFY,
         };
@@ -1251,6 +1257,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "json",              required_argument, NULL, ARG_JSON              },
                 { "root",              required_argument, NULL, ARG_ROOT              },
                 { "image",             required_argument, NULL, ARG_IMAGE             },
+                { "image-policy",      required_argument, NULL, ARG_IMAGE_POLICY      },
                 { "reboot",            no_argument,       NULL, ARG_REBOOT            },
                 { "component",         required_argument, NULL, 'C'                   },
                 { "verify",            required_argument, NULL, ARG_VERIFY            },
@@ -1318,6 +1325,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_REBOOT:
                         arg_reboot = true;
                         break;
index 70abcfa1317429f223e28f558d802f3d62dd3967..58246b5d852381c0d53584ac327bb2eec823a062 100644 (file)
@@ -5,7 +5,7 @@
 
 #include "alloc-util.h"
 #include "build.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "conf-files.h"
 #include "constants.h"
 #include "copy.h"
@@ -99,6 +99,7 @@ static const char *arg_replace = NULL;
 static bool arg_dry_run = false;
 static bool arg_inline = false;
 static PagerFlags arg_pager_flags = 0;
+static ImagePolicy *arg_image_policy = NULL;
 
 static OrderedHashmap *users = NULL, *groups = NULL;
 static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
@@ -128,6 +129,7 @@ STATIC_DESTRUCTOR_REGISTER(database_groups, set_free_freep);
 STATIC_DESTRUCTOR_REGISTER(uid_range, uid_range_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static int errno_is_not_exists(int code) {
         /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
@@ -1571,7 +1573,7 @@ static int item_equivalent(Item *a, Item *b) {
             !(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
                 _cleanup_free_ char *pa = NULL, *pb = NULL;
 
-                r = chase_symlinks(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
+                r = chase(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
                 if (r < 0) {
                         log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
                                        r, "Failed to look up path '%s%s%s': %m",
@@ -1579,7 +1581,7 @@ static int item_equivalent(Item *a, Item *b) {
                         return ERRNO_IS_RESOURCE(r) ? r : false;
                 }
 
-                r = chase_symlinks(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
+                r = chase(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
                 if (r < 0) {
                         log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
                                        r, "Failed to look up path '%s%s%s': %m",
@@ -1964,6 +1966,7 @@ static int help(void) {
                "     --cat-config           Show configuration files\n"
                "     --root=PATH            Operate on an alternate filesystem root\n"
                "     --image=PATH           Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY  Specify disk image dissection policy\n"
                "     --replace=PATH         Treat arguments as replacement for PATH\n"
                "     --dry-run              Just print what would be done\n"
                "     --inline               Treat arguments as configuration lines\n"
@@ -1982,6 +1985,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_CAT_CONFIG,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_REPLACE,
                 ARG_DRY_RUN,
                 ARG_INLINE,
@@ -1989,15 +1993,16 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         static const struct option options[] = {
-                { "help",       no_argument,       NULL, 'h'            },
-                { "version",    no_argument,       NULL, ARG_VERSION    },
-                { "cat-config", no_argument,       NULL, ARG_CAT_CONFIG },
-                { "root",       required_argument, NULL, ARG_ROOT       },
-                { "image",      required_argument, NULL, ARG_IMAGE      },
-                { "replace",    required_argument, NULL, ARG_REPLACE    },
-                { "dry-run",    no_argument,       NULL, ARG_DRY_RUN    },
-                { "inline",     no_argument,       NULL, ARG_INLINE     },
-                { "no-pager",   no_argument,       NULL, ARG_NO_PAGER   },
+                { "help",         no_argument,       NULL, 'h'              },
+                { "version",      no_argument,       NULL, ARG_VERSION      },
+                { "cat-config",   no_argument,       NULL, ARG_CAT_CONFIG   },
+                { "root",         required_argument, NULL, ARG_ROOT         },
+                { "image",        required_argument, NULL, ARG_IMAGE        },
+                { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+                { "replace",      required_argument, NULL, ARG_REPLACE      },
+                { "dry-run",      no_argument,       NULL, ARG_DRY_RUN      },
+                { "inline",       no_argument,       NULL, ARG_INLINE       },
+                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
                 {}
         };
 
@@ -2037,6 +2042,12 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 #endif
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_REPLACE:
                         if (!path_is_absolute(optarg) ||
                             !endswith(optarg, ".conf"))
@@ -2173,6 +2184,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_VALIDATE_OS |
index e709314152282fd3b643bd7d5ee8e1aca6fc2740..289967760e641cc3318fbfacd96bc44dd56af124 100644 (file)
@@ -53,6 +53,7 @@ simple_tests += files(
         'test-cgroup-setup.c',
         'test-cgroup-util.c',
         'test-cgroup.c',
+        'test-chase.c',
         'test-clock.c',
         'test-compare-operator.c',
         'test-condition.c',
@@ -94,6 +95,7 @@ simple_tests += files(
         'test-hostname-setup.c',
         'test-hostname-util.c',
         'test-id128.c',
+        'test-image-policy.c',
         'test-import-util.c',
         'test-in-addr-prefix-util.c',
         'test-in-addr-util.c',
@@ -106,6 +108,7 @@ simple_tests += files(
         'test-list.c',
         'test-local-addresses.c',
         'test-locale-util.c',
+        'test-lock-util.c',
         'test-log.c',
         'test-logarithm.c',
         'test-macro.c',
@@ -138,6 +141,7 @@ simple_tests += files(
         'test-rm-rf.c',
         'test-sd-hwdb.c',
         'test-sd-path.c',
+        'test-secure-bits.c',
         'test-selinux.c',
         'test-serialize.c',
         'test-set.c',
@@ -214,25 +218,6 @@ tests += [
                 'sources' : files('test-boot-timestamps.c'),
                 'condition' : 'ENABLE_EFI',
         },
-        {
-                'sources' : files('test-bpf-devices.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-bpf-firewall.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-bpf-foreign-programs.c'),
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-bpf-lsm.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
         {
                 'sources' : files('test-btrfs.c'),
                 'type' : 'manual',
@@ -249,26 +234,9 @@ tests += [
                 'dependencies' : libcap,
         },
         {
-                'sources' : files('test-cgroup-cpu.c'),
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-cgroup-mask.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-cgroup-unit-default.c'),
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-chase-symlinks.c'),
+                'sources' : files('test-chase-manual.c'),
                 'type' : 'manual',
         },
-        {
-                'sources' : files('test-chown-rec.c'),
-                'base' : test_core_base,
-        },
         {
                 'sources' : files('test-compress-benchmark.c'),
                 'link_with' : [
@@ -294,27 +262,12 @@ tests += [
                 'sources' : files('test-dlopen-so.c'),
                 'dependencies' : libp11kit_cflags
         },
-        {
-                'sources' : files('test-emergency-action.c'),
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-engine.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
         {
                 'sources' : [
                         files('test-errno-list.c'),
                         generated_gperf_headers,
                 ],
         },
-        {
-                'sources' : files('test-execute.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-                'timeout' : 360,
-        },
         {
                 'sources' : files('test-fd-util.c'),
                 'dependencies' : libseccomp,
@@ -327,11 +280,6 @@ tests += [
                 ],
                 'timeout' : 180,
         },
-        {
-                'sources' : files('test-install.c'),
-                'base' : test_core_base,
-                'type' : 'manual',
-        },
         {
                 'sources' : [
                         files('test-ip-protocol-list.c'),
@@ -342,11 +290,6 @@ tests += [
                 'sources' : files('test-ipcrm.c'),
                 'type' : 'unsafe',
         },
-        {
-                'sources' : files('test-job-type.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
         {
                 'sources' : files('test-json.c'),
                 'dependencies' : libm,
@@ -363,25 +306,10 @@ tests += [
                         threads,
                 ],
         },
-        {
-                'sources' : files('test-load-fragment.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
-        {
-                'sources' : files('test-loop-block.c'),
-                'dependencies' : [threads, libblkid],
-                'base' : test_core_base,
-                'parallel' : false,
-        },
         {
                 'sources' : files('test-loopback.c'),
                 'dependencies' : common_test_dependencies,
         },
-        {
-                'sources' : files('test-manager.c'),
-                'base' : test_core_base,
-        },
         {
                 'sources' : files('test-math-util.c'),
                 'dependencies' : libm,
@@ -390,26 +318,12 @@ tests += [
                 'sources' : files('test-mempress.c'),
                 'dependencies' : threads,
         },
-        {
-                'sources' : files('test-namespace.c'),
-                'dependencies' : [
-                        libblkid,
-                        threads,
-                ],
-                'base' : test_core_base,
-        },
         {
                 'sources' : files('test-netlink-manual.c'),
                 'dependencies' : libkmod,
                 'condition' : 'HAVE_KMOD',
                 'type' : 'manual',
         },
-        {
-                'sources' : files('test-ns.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-                'type' : 'manual',
-        },
         {
                 'sources' : files('test-nscd-flush.c'),
                 'condition' : 'ENABLE_NSCD',
@@ -436,12 +350,6 @@ tests += [
                 'sources' : files('test-parse-util.c'),
                 'dependencies' : libm,
         },
-        {
-                'sources' : files('test-path.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-                'timeout' : 120,
-        },
         {
                 'sources' : files('test-process-util.c'),
                 'dependencies' : threads,
@@ -460,11 +368,6 @@ tests += [
                 'condition' : 'ENABLE_BOOTLOADER',
                 'c_args' : '-I@0@'.format(efi_config_h_dir),
         },
-        {
-                'sources' : files('test-sched-prio.c'),
-                'dependencies' : common_test_dependencies,
-                'base' : test_core_base,
-        },
         {
                 'sources' : files('test-seccomp.c'),
                 'dependencies' : libseccomp,
@@ -523,39 +426,149 @@ tests += [
                 'type' : 'manual',
         },
         {
-                'sources' : files('test-unit-name.c'),
+                'sources' : files('test-utmp.c'),
+                'condition' : 'ENABLE_UTMP',
+        },
+        {
+                'sources' : files('test-varlink.c'),
+                'dependencies' : threads,
+        },
+        {
+                'sources' : files('test-watchdog.c'),
+                'type' : 'unsafe',
+        },
+
+
+        # Tests that link to libcore, i.e. tests for pid1 code.
+        {
+                'sources' : files('test-bpf-devices.c'),
                 'dependencies' : common_test_dependencies,
                 'base' : test_core_base,
         },
         {
-                'sources' : files('test-unit-serialize.c'),
+                'sources' : files('test-bpf-firewall.c'),
                 'dependencies' : common_test_dependencies,
                 'base' : test_core_base,
         },
         {
-                'sources' : files('test-utmp.c'),
-                'condition' : 'ENABLE_UTMP',
+                'sources' : files('test-bpf-foreign-programs.c'),
+                'base' : test_core_base,
         },
         {
-                'sources' : files('test-varlink.c'),
-                'dependencies' : threads,
+                'sources' : files('test-bpf-lsm.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
         },
         {
-                'sources' : files('test-watch-pid.c'),
+                'sources' : files('test-cgroup-cpu.c'),
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-cgroup-mask.c'),
                 'dependencies' : common_test_dependencies,
                 'base' : test_core_base,
         },
         {
-                'sources' : files('test-watchdog.c'),
-                'type' : 'unsafe',
+                'sources' : files('test-cgroup-unit-default.c'),
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-chown-rec.c'),
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-core-unit.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-emergency-action.c'),
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-engine.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-execute.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+                'timeout' : 360,
+        },
+        {
+                'sources' : files('test-install.c'),
+                'base' : test_core_base,
+                'type' : 'manual',
+        },
+        {
+                'sources' : files('test-job-type.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-load-fragment.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-loop-block.c'),
+                'dependencies' : [threads, libblkid],
+                'base' : test_core_base,
+                'parallel' : false,
+        },
+        {
+                'sources' : files('test-manager.c'),
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-namespace.c'),
+                'dependencies' : [
+                        libblkid,
+                        threads,
+                ],
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-ns.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+                'type' : 'manual',
+        },
+        {
+                'sources' : files('test-path.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+                'timeout' : 120,
+        },
+        {
+                'sources' : files('test-sched-prio.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-socket-bind.c'),
+                'dependencies' : libdl,
+                'condition' : 'BPF_FRAMEWORK',
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-unit-name.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-unit-serialize.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
+        },
+        {
+                'sources' : files('test-watch-pid.c'),
+                'dependencies' : common_test_dependencies,
+                'base' : test_core_base,
         },
-]
-
-############################################################
-
-# define some tests here, because the link_with deps were not defined earlier
 
-tests += [
+        # Tests from other directories that have link_with deps that were not defined earlier
         {
                 'sources' : files('../libsystemd/sd-bus/test-bus-error.c'),
                 'link_with' : [
@@ -573,10 +586,4 @@ tests += [
                 'link_with' : libudev,
                 'dependencies' : threads,
         },
-        {
-                'sources' : files('test-socket-bind.c'),
-                'dependencies' : libdl,
-                'condition' : 'BPF_FRAMEWORK',
-                'base' : test_core_base,
-        },
 ]
index 2c406893df882a059d6b7300f4c4cbdb777ac2e2..093eaaa01b600810c1b7885aacaca0b5d214cb9c 100644 (file)
@@ -9,13 +9,14 @@
 #include "errno-util.h"
 #include "fd-util.h"
 #include "format-util.h"
+#include "fs-util.h"
 #include "string-util.h"
 #include "tests.h"
 #include "tmpfile-util.h"
 #include "user-util.h"
 
 TEST_RET(add_acls_for_user) {
-        char fn[] = "/tmp/test-empty.XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-empty.XXXXXX";
         _cleanup_close_ int fd = -EBADF;
         char *cmd;
         uid_t uid;
@@ -65,7 +66,6 @@ TEST_RET(add_acls_for_user) {
         cmd = strjoina("getfacl -p ", fn);
         assert_se(system(cmd) == 0);
 
-        (void) unlink(fn);
         return 0;
 }
 
index b97fedcfc6d37d66664acb96666d6079e0388e4a..69785e47fadf2aadad8d2c7f4226f1ae03227019 100644 (file)
@@ -4,8 +4,9 @@
 #include <unistd.h>
 
 #include "async.h"
-#include "macro.h"
+#include "fs-util.h"
 #include "tmpfile-util.h"
+#include "tests.h"
 
 static bool test_async = false;
 
@@ -15,24 +16,22 @@ static void *async_func(void *arg) {
         return NULL;
 }
 
-int main(int argc, char *argv[]) {
+TEST(test_async) {
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-asynchronous_close.XXXXXX";
         int fd;
-        char name[] = "/tmp/test-asynchronous_close.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
         asynchronous_close(fd);
 
         assert_se(asynchronous_job(async_func, NULL) >= 0);
-
         assert_se(asynchronous_sync(NULL) >= 0);
 
         sleep(1);
 
         assert_se(fcntl(fd, F_GETFD) == -1);
+        assert_se(errno == EBADF);
         assert_se(test_async);
-
-        (void) unlink(name);
-
-        return 0;
 }
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
index 655b823c7fbf646d0b29aeaf3c063186b2023fd8..6a84508b12da34a5d3bfefe857eeb7d7db782902 100644 (file)
@@ -10,8 +10,7 @@ int main(int argc, const char *argv[]) {
         assert_se(b);
 
         assert_se(bitmap_ensure_allocated(&b) == 0);
-        bitmap_free(b);
-        b = NULL;
+        b = bitmap_free(b);
         assert_se(bitmap_ensure_allocated(&b) == 0);
 
         assert_se(bitmap_isset(b, 0) == false);
@@ -89,13 +88,11 @@ int main(int argc, const char *argv[]) {
         bitmap_clear(b);
         assert_se(bitmap_isclear(b) == true);
         assert_se(bitmap_equal(b, b2) == false);
-        bitmap_free(b2);
-        b2 = NULL;
+        b2 = bitmap_free(b2);
 
         assert_se(bitmap_set(b, UINT_MAX) == -ERANGE);
 
-        bitmap_free(b);
-        b = NULL;
+        b = bitmap_free(b);
         assert_se(bitmap_ensure_allocated(&b) == 0);
         assert_se(bitmap_ensure_allocated(&b2) == 0);
 
index 564983b69927d107dbf5b0d2db7f8d6bd5718096..db64142f01cbbcd2912c8241fa2e20cef8b34951 100644 (file)
@@ -8,7 +8,7 @@
 #include "tests.h"
 
 static void _test_one(int line, const char *input, const char *output) {
-        CalendarSpec *c;
+        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
         _cleanup_free_ char *p = NULL, *q = NULL;
         usec_t u;
         int r;
@@ -28,18 +28,17 @@ static void _test_one(int line, const char *input, const char *output) {
         u = now(CLOCK_REALTIME);
         r = calendar_spec_next_usec(c, u, &u);
         log_info("Next: %s", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP(u));
-        calendar_spec_free(c);
+        c = calendar_spec_free(c);
 
         assert_se(calendar_spec_from_string(p, &c) >= 0);
         assert_se(calendar_spec_to_string(c, &q) >= 0);
-        calendar_spec_free(c);
 
         assert_se(streq(q, p));
 }
 #define test_one(input, output) _test_one(__LINE__, input, output)
 
 static void _test_next(int line, const char *input, const char *new_tz, usec_t after, usec_t expect) {
-        CalendarSpec *c;
+        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
         usec_t u;
         char *old_tz;
         int r;
@@ -66,8 +65,6 @@ static void _test_next(int line, const char *input, const char *new_tz, usec_t a
         else
                 assert_se(r == -ENOENT);
 
-        calendar_spec_free(c);
-
         assert_se(set_unset_env("TZ", old_tz, true) == 0);
         tzset();
 }
@@ -76,7 +73,7 @@ static void _test_next(int line, const char *input, const char *new_tz, usec_t a
 TEST(timestamp) {
         char buf[FORMAT_TIMESTAMP_MAX];
         _cleanup_free_ char *t = NULL;
-        CalendarSpec *c;
+        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
         usec_t x, y;
 
         /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */
@@ -87,7 +84,6 @@ TEST(timestamp) {
         log_info("%s", buf);
         assert_se(calendar_spec_from_string(buf, &c) >= 0);
         assert_se(calendar_spec_to_string(c, &t) >= 0);
-        calendar_spec_free(c);
         log_info("%s", t);
 
         assert_se(parse_timestamp(t, &y) >= 0);
@@ -95,7 +91,7 @@ TEST(timestamp) {
 }
 
 TEST(hourly_bug_4031) {
-        CalendarSpec *c;
+        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
         usec_t n, u, w;
         int r;
 
@@ -113,8 +109,6 @@ TEST(hourly_bug_4031) {
         assert_se(u <= n + USEC_PER_HOUR);
         assert_se(u < w);
         assert_se(w <= u + USEC_PER_HOUR);
-
-        calendar_spec_free(c);
 }
 
 TEST(calendar_spec_one) {
similarity index 96%
rename from src/test/test-chase-symlinks.c
rename to src/test/test-chase-manual.c
index d94173f27be55d8eb47a9f3c585279c8ca211dd7..d65fbe1d744597da30bb221d9a0e413bbe84761b 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #include <getopt.h>
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "log.h"
 #include "main-func.h"
@@ -97,7 +97,7 @@ static int run(int argc, char **argv) {
                 printf("%s ", argv[i]);
                 fflush(stdout);
 
-                r = chase_symlinks(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
+                r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL);
                 if (r < 0)
                         log_error_errno(r, "failed: %m");
                 else {
diff --git a/src/test/test-chase.c b/src/test/test-chase.c
new file mode 100644 (file)
index 0000000..558f410
--- /dev/null
@@ -0,0 +1,688 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "chase.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "id128-util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "string-util.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+
+static const char *arg_test_dir = NULL;
+
+TEST(chase) {
+        _cleanup_free_ char *result = NULL, *pwd = NULL;
+        _cleanup_close_ int pfd = -EBADF;
+        char *temp;
+        const char *top, *p, *pslash, *q, *qslash;
+        struct stat st;
+        int r;
+
+        temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
+        assert_se(mkdtemp(temp));
+
+        top = strjoina(temp, "/top");
+        assert_se(mkdir(top, 0700) >= 0);
+
+        p = strjoina(top, "/dot");
+        if (symlink(".", p) < 0) {
+                assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
+                log_tests_skipped_errno(errno, "symlink() not possible");
+                goto cleanup;
+        };
+
+        p = strjoina(top, "/dotdot");
+        assert_se(symlink("..", p) >= 0);
+
+        p = strjoina(top, "/dotdota");
+        assert_se(symlink("../a", p) >= 0);
+
+        p = strjoina(temp, "/a");
+        assert_se(symlink("b", p) >= 0);
+
+        p = strjoina(temp, "/b");
+        assert_se(symlink("/usr", p) >= 0);
+
+        p = strjoina(temp, "/start");
+        assert_se(symlink("top/dot/dotdota", p) >= 0);
+
+        /* Paths that use symlinks underneath the "root" */
+
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/usr"));
+        result = mfree(result);
+
+        pslash = strjoina(p, "/");
+        r = chase(pslash, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/usr/"));
+        result = mfree(result);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(pslash, temp, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        q = strjoina(temp, "/usr");
+
+        r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, q));
+        result = mfree(result);
+
+        qslash = strjoina(q, "/");
+
+        r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, qslash));
+        result = mfree(result);
+
+        assert_se(mkdir(q, 0700) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, q));
+        result = mfree(result);
+
+        r = chase(pslash, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, qslash));
+        result = mfree(result);
+
+        p = strjoina(temp, "/slash");
+        assert_se(symlink("/", p) >= 0);
+
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/"));
+        result = mfree(result);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, temp));
+        result = mfree(result);
+
+        /* Paths that would "escape" outside of the "root" */
+
+        p = strjoina(temp, "/6dots");
+        assert_se(symlink("../../..", p) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, temp));
+        result = mfree(result);
+
+        p = strjoina(temp, "/6dotsusr");
+        assert_se(symlink("../../../usr", p) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, q));
+        result = mfree(result);
+
+        p = strjoina(temp, "/top/8dotsusr");
+        assert_se(symlink("../../../../usr", p) >= 0);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, q));
+        result = mfree(result);
+
+        /* Paths that contain repeated slashes */
+
+        p = strjoina(temp, "/slashslash");
+        assert_se(symlink("///usr///", p) >= 0);
+
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/usr"));
+        assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
+        result = mfree(result);
+
+        r = chase(p, temp, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, q));
+        result = mfree(result);
+
+        /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
+
+        if (geteuid() == 0) {
+                p = strjoina(temp, "/user");
+                assert_se(mkdir(p, 0755) >= 0);
+                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
+
+                q = strjoina(temp, "/user/root");
+                assert_se(mkdir(q, 0755) >= 0);
+
+                p = strjoina(q, "/link");
+                assert_se(symlink("/", p) >= 0);
+
+                /* Fail when user-owned directories contain root-owned subdirectories. */
+                r = chase(p, temp, CHASE_SAFE, &result, NULL);
+                assert_se(r == -ENOLINK);
+                result = mfree(result);
+
+                /* Allow this when the user-owned directories are all in the "root". */
+                r = chase(p, q, CHASE_SAFE, &result, NULL);
+                assert_se(r > 0);
+                result = mfree(result);
+        }
+
+        /* Paths using . */
+
+        r = chase("/etc/./.././", NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(result, "/"));
+        result = mfree(result);
+
+        r = chase("/etc/./.././", "/etc", 0, &result, NULL);
+        assert_se(r > 0 && path_equal(result, "/etc"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../etc", NULL, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(streq(result, "/etc"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(streq(result, "/test-chase.fsldajfl"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
+        assert_se(r > 0);
+        assert_se(streq(result, "/etc"));
+        result = mfree(result);
+
+        r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(streq(result, "/test-chase.fsldajfl"));
+        result = mfree(result);
+
+        r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL);
+        assert_se(IN_SET(r, -ENOTDIR, -ENOENT));
+        result = mfree(result);
+
+        /* Path that loops back to self */
+
+        p = strjoina(temp, "/recursive-symlink");
+        assert_se(symlink("recursive-symlink", p) >= 0);
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ELOOP);
+
+        /* Path which doesn't exist */
+
+        p = strjoina(temp, "/idontexist");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        p = strjoina(temp, "/idontexist/meneither");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        /* Relative paths */
+
+        assert_se(safe_getcwd(&pwd) >= 0);
+
+        assert_se(chdir(temp) >= 0);
+
+        p = "this/is/a/relative/path";
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+
+        p = strjoina(temp, "/", p);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        p = "this/is/a/relative/path";
+        r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == 0);
+
+        p = strjoina(temp, "/", p);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+
+        assert_se(chdir(pwd) >= 0);
+
+        /* Path which doesn't exist, but contains weird stuff */
+
+        p = strjoina(temp, "/idontexist/..");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        p = strjoina(temp, "/target");
+        q = strjoina(temp, "/top");
+        assert_se(symlink(q, p) >= 0);
+        p = strjoina(temp, "/target/idontexist");
+        r = chase(p, NULL, 0, &result, NULL);
+        assert_se(r == -ENOENT);
+
+        if (geteuid() == 0) {
+                p = strjoina(temp, "/priv1");
+                assert_se(mkdir(p, 0755) >= 0);
+
+                q = strjoina(p, "/priv2");
+                assert_se(mkdir(q, 0755) >= 0);
+
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+                assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+
+                assert_se(chown(q, 0, 0) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
+
+                assert_se(rmdir(q) >= 0);
+                assert_se(symlink("/etc/passwd", q) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
+
+                assert_se(chown(p, 0, 0) >= 0);
+                assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
+        }
+
+        p = strjoina(temp, "/machine-id-test");
+        assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
+
+        r = chase(p, NULL, 0, NULL, &pfd);
+        if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) {
+                _cleanup_close_ int fd = -EBADF;
+                sd_id128_t a, b;
+
+                assert_se(pfd >= 0);
+
+                fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
+                assert_se(fd >= 0);
+                safe_close(pfd);
+
+                assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0);
+                assert_se(sd_id128_get_machine(&b) >= 0);
+                assert_se(sd_id128_equal(a, b));
+        }
+
+        assert_se(lstat(p, &st) >= 0);
+        r = chase_and_unlink(p, NULL, 0, 0, &result);
+        assert_se(r == 0);
+        assert_se(path_equal(result, p));
+        result = mfree(result);
+        assert_se(lstat(p, &st) == -1 && errno == ENOENT);
+
+        /* Test CHASE_NOFOLLOW */
+
+        p = strjoina(temp, "/target");
+        q = strjoina(temp, "/symlink");
+        assert_se(symlink(p, q) >= 0);
+        r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
+        assert_se(r >= 0);
+        assert_se(pfd >= 0);
+        assert_se(path_equal(result, q));
+        assert_se(fstat(pfd, &st) >= 0);
+        assert_se(S_ISLNK(st.st_mode));
+        result = mfree(result);
+        pfd = safe_close(pfd);
+
+        /* s1 -> s2 -> nonexistent */
+        q = strjoina(temp, "/s1");
+        assert_se(symlink("s2", q) >= 0);
+        p = strjoina(temp, "/s2");
+        assert_se(symlink("nonexistent", p) >= 0);
+        r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
+        assert_se(r >= 0);
+        assert_se(pfd >= 0);
+        assert_se(path_equal(result, q));
+        assert_se(fstat(pfd, &st) >= 0);
+        assert_se(S_ISLNK(st.st_mode));
+        result = mfree(result);
+        pfd = safe_close(pfd);
+
+        /* Test CHASE_STEP */
+
+        p = strjoina(temp, "/start");
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/dot/dotdota");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/dotdota");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/top/../a");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/a");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        p = strjoina(temp, "/b");
+        assert_se(streq(p, result));
+        result = mfree(result);
+
+        r = chase(p, NULL, CHASE_STEP, &result, NULL);
+        assert_se(r == 0);
+        assert_se(streq("/usr", result));
+        result = mfree(result);
+
+        r = chase("/usr", NULL, CHASE_STEP, &result, NULL);
+        assert_se(r > 0);
+        assert_se(streq("/usr", result));
+        result = mfree(result);
+
+        /* Make sure that symlinks in the "root" path are not resolved, but those below are */
+        p = strjoina("/etc/..", temp, "/self");
+        assert_se(symlink(".", p) >= 0);
+        q = strjoina(p, "/top/dot/dotdota");
+        r = chase(q, p, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(path_startswith(result, p), "usr"));
+        result = mfree(result);
+
+        /* Test CHASE_PROHIBIT_SYMLINKS */
+
+        assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
+        assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
+
+ cleanup:
+        assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+}
+
+TEST(chaseat) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+        _cleanup_free_ char *result = NULL;
+        _cleanup_closedir_ DIR *dir = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        struct stat st;
+        const char *p;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+        /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working
+         * directory. */
+
+        assert_se(symlinkat("/usr", tfd, "abc") >= 0);
+
+        p = strjoina(t, "/abc");
+        assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "/usr"));
+        result = mfree(result);
+
+        /* If the file descriptor points to the root directory, the result will be absolute. */
+
+        fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH);
+        assert_se(fd >= 0);
+
+        assert_se(chaseat(fd, p, 0, &result, NULL) >= 0);
+        assert_se(streq(result, "/usr"));
+        result = mfree(result);
+
+        assert_se(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "/usr"));
+        result = mfree(result);
+
+        fd = safe_close(fd);
+
+        /* If the file descriptor does not point to the root directory, the result will be relative
+         * unless the result is outside of the specified file descriptor. */
+
+        assert_se(chaseat(tfd, "abc", 0, &result, NULL) >= 0);
+        assert_se(streq(result, "/usr"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "/abc", 0, &result, NULL) >= 0);
+        assert_se(streq(result, "/usr"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT);
+        assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT);
+
+        assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(streq(result, "usr"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(streq(result, "usr"));
+        result = mfree(result);
+
+        /* Test that absolute path or not are the same when resolving relative to a directory file
+         * descriptor and that we always get a relative path back. */
+
+        assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0);
+        fd = safe_close(fd);
+        assert_se(symlinkat("/def", tfd, "qed") >= 0);
+        assert_se(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "def"));
+        result = mfree(result);
+        assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "def"));
+        result = mfree(result);
+
+        /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
+         * host's root. */
+        assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
+
+        /* Test CHASE_PARENT */
+
+        assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
+        assert_se(symlinkat("/def", fd, "parent") >= 0);
+        fd = safe_close(fd);
+
+        /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
+         * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
+         * directory of the symlink itself. */
+
+        assert_se(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+        assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
+        assert_se(streq(result, "def"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
+        assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+        assert_se(streq(result, "chase/parent"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+        assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
+        assert_se(streq(result, "chase"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        /* Test CHASE_MKDIR_0755 */
+
+        assert_se(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
+        assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
+        assert_se(streq(result, "m/k/d/i/r"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+        assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
+        assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
+        assert_se(streq(result, "q"));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT);
+
+        /* Test CHASE_FILENAME */
+
+        assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
+        assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+        assert_se(streq(result, "parent"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0);
+        assert_se(faccessat(fd, result, F_OK, 0) >= 0);
+        assert_se(streq(result, "chase"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0);
+        assert_se(streq(result, "."));
+        result = mfree(result);
+
+        /* Test chase_and_openat() */
+
+        fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        fd = safe_close(fd);
+
+        fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_directory(fd) >= 0);
+        fd = safe_close(fd);
+
+        /* Test chase_and_openatdir() */
+
+        assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0);
+        FOREACH_DIRENT(de, dir, assert_not_reached())
+                assert_se(streq(de->d_name, "r"));
+        assert_se(streq(result, "o/p/e/n/d/i"));
+        result = mfree(result);
+
+        /* Test chase_and_statat() */
+
+        assert_se(chase_and_statat(tfd, "o/p", 0, &result, &st) >= 0);
+        assert_se(stat_verify_directory(&st) >= 0);
+        assert_se(streq(result, "o/p"));
+        result = mfree(result);
+
+        /* Test chase_and_accessat() */
+
+        assert_se(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result) >= 0);
+        assert_se(streq(result, "o/p/e"));
+        result = mfree(result);
+
+        /* Test chase_and_fopenat_unlocked() */
+
+        assert_se(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0);
+        assert_se(fread(&(char[1]) {}, 1, 1, f) == 0);
+        assert_se(feof(f));
+        f = safe_fclose(f);
+        assert_se(streq(result, "o/p/e/n/f/i/l/e"));
+        result = mfree(result);
+
+        /* Test chase_and_unlinkat() */
+
+        assert_se(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0);
+        assert_se(streq(result, "o/p/e/n/f/i/l/e"));
+        result = mfree(result);
+
+        /* Test chase_and_open_parent_at() */
+
+        assert_se((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)) >= 0);
+        assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+        assert_se(streq(result, "parent"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+        assert_se(faccessat(fd, result, F_OK, 0) >= 0);
+        assert_se(streq(result, "chase"));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+        assert_se(streq(result, "."));
+        fd = safe_close(fd);
+        result = mfree(result);
+
+        assert_se((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0);
+        assert_se(streq(result, "."));
+        fd = safe_close(fd);
+        result = mfree(result);
+}
+
+static int intro(void) {
+        arg_test_dir = saved_argv[1];
+        return EXIT_SUCCESS;
+}
+
+TEST(chaseat_prefix_root) {
+        _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL;
+
+        assert_se(safe_getcwd(&cwd) >= 0);
+
+        assert_se(chaseat_prefix_root("/hoge", NULL, &ret) >= 0);
+        assert_se(streq(ret, "/hoge"));
+
+        ret = mfree(ret);
+
+        assert_se(chaseat_prefix_root("/hoge", "a/b/c", &ret) >= 0);
+        assert_se(streq(ret, "/hoge"));
+
+        ret = mfree(ret);
+
+        assert_se(chaseat_prefix_root("hoge", "/a/b//./c///", &ret) >= 0);
+        assert_se(streq(ret, "/a/b/c/hoge"));
+
+        ret = mfree(ret);
+
+        assert_se(chaseat_prefix_root("hoge", "a/b//./c///", &ret) >= 0);
+        assert_se(expected = path_join(cwd, "a/b/c/hoge"));
+        assert_se(streq(ret, expected));
+
+        ret = mfree(ret);
+        expected = mfree(expected);
+
+        assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0);
+        assert_se(streq(ret, "/a/b/c/hoge/aaa/../././b"));
+
+        ret = mfree(ret);
+
+        assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret) >= 0);
+        assert_se(expected = path_join(cwd, "a/b/c/hoge/aaa/../././b"));
+        assert_se(streq(ret, expected));
+}
+
+DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);
index 4aa991ac645d8032e0fba17b692874fa72a26520..3d8f46fc9670d8adcf51bd03b790ed6fc20313e6 100644 (file)
@@ -17,6 +17,7 @@ TEST(parse_compare_operator) {
         const char *str_e = "!=!="; /* parse_compare_operator() moves the pointer */
         assert_se(parse_compare_operator(&str_e, COMPARE_EQUAL_BY_STRING) == COMPARE_STRING_UNEQUAL);
         assert_se(parse_compare_operator(&str_e, 0) == COMPARE_UNEQUAL);
+        assert_se(parse_compare_operator(&str_e, 0) == _COMPARE_OPERATOR_INVALID);
 }
 
 TEST(test_order) {
index 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 a2b2b34d9845b21a81b5c505afe8f34a9523c3b7..38855fd45e07411e5d7d2af96b177b75b03069e8 100644 (file)
@@ -4,7 +4,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "copy.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -24,8 +24,8 @@
 
 TEST(copy_file) {
         _cleanup_free_ char *buf = NULL;
-        char fn[] = "/tmp/test-copy_file.XXXXXX";
-        char fn_copy[] = "/tmp/test-copy_file.XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-copy_file.XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn_copy[] = "/tmp/test-copy_file.XXXXXX";
         size_t sz = 0;
         int fd;
 
@@ -39,14 +39,11 @@ TEST(copy_file) {
 
         assert_se(write_string_file(fn, "foo bar bar bar foo", WRITE_STRING_FILE_CREATE) == 0);
 
-        assert_se(copy_file(fn, fn_copy, 0, 0644, 0, 0, COPY_REFLINK) == 0);
+        assert_se(copy_file(fn, fn_copy, 0, 0644, COPY_REFLINK) == 0);
 
         assert_se(read_full_file(fn_copy, &buf, &sz) == 0);
         assert_se(streq(buf, "foo bar bar bar foo\n"));
         assert_se(sz == 20);
-
-        unlink(fn);
-        unlink(fn_copy);
 }
 
 static bool read_file_at_and_streq(int dir_fd, const char *path, const char *expected) {
@@ -108,8 +105,8 @@ TEST(copy_tree_replace_dirs) {
 }
 
 TEST(copy_file_fd) {
-        char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
-        char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
+        _cleanup_(unlink_tempfilep) char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
+        _cleanup_(unlink_tempfilep) char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
         _cleanup_close_ int in_fd = -EBADF, out_fd = -EBADF;
         const char *text = "boohoo\nfoo\n\tbar\n";
         char buf[64] = {};
@@ -126,9 +123,6 @@ TEST(copy_file_fd) {
 
         assert_se(read(out_fd, buf, sizeof buf) == (ssize_t) strlen(text));
         assert_se(streq(buf, text));
-
-        unlink(in_fn);
-        unlink(out_fn);
 }
 
 TEST(copy_tree) {
@@ -224,7 +218,7 @@ TEST(copy_tree) {
                 assert_se(f = strjoin(original_dir, *p));
                 assert_se(l = strjoin(copy_dir, *ll));
 
-                assert_se(chase_symlinks(l, NULL, 0, &target, NULL) == 1);
+                assert_se(chase(l, NULL, 0, &target, NULL) == 1);
                 assert_se(path_equal(f, target));
         }
 
@@ -293,15 +287,15 @@ TEST(copy_bytes) {
 }
 
 static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink, uint64_t max_bytes) {
-        char fn2[] = "/tmp/test-copy-file-XXXXXX";
-        char fn3[] = "/tmp/test-copy-file-XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn2[] = "/tmp/test-copy-file-XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn3[] = "/tmp/test-copy-file-XXXXXX";
         _cleanup_close_ int fd = -EBADF, fd2 = -EBADF, fd3 = -EBADF;
         int r;
         struct stat buf, buf2, buf3;
 
         log_info("%s try_reflink=%s max_bytes=%" PRIu64, __func__, yes_no(try_reflink), max_bytes);
 
-        fd = open(src, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+        fd = open(src, O_CLOEXEC | O_PATH);
         assert_se(fd >= 0);
 
         fd2 = mkostemp_safe(fn2);
@@ -343,9 +337,6 @@ static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink,
                 assert_se(buf3.st_size == buf2.st_size);
         else
                 assert_se((uint64_t) buf3.st_size == max_bytes);
-
-        unlink(fn2);
-        unlink(fn3);
 }
 
 TEST(copy_bytes_regular_file) {
@@ -366,13 +357,13 @@ TEST(copy_atomic) {
 
         q = strjoina(p, "/fstab");
 
-        r = copy_file_atomic("/etc/fstab", q, 0644, 0, 0, COPY_REFLINK);
+        r = copy_file_atomic("/etc/fstab", q, 0644, COPY_REFLINK);
         if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r))
                 return;
 
-        assert_se(copy_file_atomic("/etc/fstab", q, 0644, 0, 0, COPY_REFLINK) == -EEXIST);
+        assert_se(copy_file_atomic("/etc/fstab", q, 0644, COPY_REFLINK) == -EEXIST);
 
-        assert_se(copy_file_atomic("/etc/fstab", q, 0644, 0, 0, COPY_REPLACE) >= 0);
+        assert_se(copy_file_atomic("/etc/fstab", q, 0644, COPY_REPLACE) >= 0);
 }
 
 TEST(copy_proc) {
@@ -383,7 +374,7 @@ TEST(copy_proc) {
 
         assert_se(mkdtemp_malloc(NULL, &p) >= 0);
         assert_se(f = path_join(p, "version"));
-        assert_se(copy_file("/proc/version", f, 0, MODE_INVALID, 0, 0, 0) >= 0);
+        assert_se(copy_file("/proc/version", f, 0, MODE_INVALID, 0) >= 0);
 
         assert_se(read_one_line_file("/proc/version", &a) >= 0);
         assert_se(read_one_line_file(f, &b) >= 0);
@@ -392,8 +383,8 @@ TEST(copy_proc) {
 }
 
 TEST_RET(copy_holes) {
-        char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
-        char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
+        _cleanup_(unlink_tempfilep) char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
         struct stat stat;
         off_t blksz;
         int r, fd, fd_copy;
@@ -441,9 +432,6 @@ TEST_RET(copy_holes) {
         close(fd);
         close(fd_copy);
 
-        unlink(fn);
-        unlink(fn_copy);
-
         return 0;
 }
 
diff --git a/src/test/test-core-unit.c b/src/test/test-core-unit.c
new file mode 100644 (file)
index 0000000..1a08507
--- /dev/null
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "tests.h"
+#include "unit.h"
+
+static void test_unit_escape_setting_one(
+                const char *s,
+                const char *expected_exec_env,
+                const char *expected_exec,
+                const char *expected_c) {
+
+        _cleanup_free_ char *a = NULL, *b, *c, *d,
+                *s_esc, *a_esc, *b_esc, *c_esc, *d_esc;
+        const char *t;
+
+        if (!expected_exec_env)
+                expected_exec_env = s;
+        if (!expected_exec)
+                expected_exec = expected_exec_env;
+        if (!expected_c)
+                expected_c = expected_exec;
+        assert_se(s_esc = cescape(s));
+
+        assert_se(t = unit_escape_setting(s, 0, &a));
+        assert_se(a_esc = cescape(t));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc);
+        assert_se(a == NULL);
+        assert_se(t == s);
+
+        assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV, &b));
+        assert_se(b_esc = cescape(t));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc);
+        assert_se(b == NULL || streq(b, t));
+        assert_se(streq(t, expected_exec_env));
+
+        assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX, &c));
+        assert_se(c_esc = cescape(t));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc);
+        assert_se(c == NULL || streq(c, t));
+        assert_se(streq(t, expected_exec));
+
+        assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_C, &d));
+        assert_se(d_esc = cescape(t));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc);
+        assert_se(d == NULL || streq(d, t));
+        assert_se(streq(t, expected_c));
+}
+
+TEST(unit_escape_setting) {
+        test_unit_escape_setting_one("/sbin/sbash", NULL, NULL, NULL);
+        test_unit_escape_setting_one("$", "$$", "$", "$");
+        test_unit_escape_setting_one("$$", "$$$$", "$$", "$$");
+        test_unit_escape_setting_one("'", "'", NULL, "\\'");
+        test_unit_escape_setting_one("\"", "\\\"", NULL, NULL);
+        test_unit_escape_setting_one("\t", "\\t", NULL, NULL);
+        test_unit_escape_setting_one(" ", NULL, NULL, NULL);
+        test_unit_escape_setting_one("$;'\"\t\n", "$$;'\\\"\\t\\n", "$;'\\\"\\t\\n", "$;\\'\\\"\\t\\n");
+}
+
+static void test_unit_concat_strv_one(
+                char **s,
+                const char *expected_none,
+                const char *expected_exec_env,
+                const char *expected_exec,
+                const char *expected_c) {
+
+        _cleanup_free_ char *a, *b, *c, *d,
+                *s_ser, *s_esc, *a_esc, *b_esc, *c_esc, *d_esc;
+
+        assert_se(s_ser = strv_join(s, "_"));
+        assert_se(s_esc = cescape(s_ser));
+        if (!expected_exec_env)
+                expected_exec_env = expected_none;
+        if (!expected_exec)
+                expected_exec = expected_none;
+        if (!expected_c)
+                expected_c = expected_none;
+
+        assert_se(a = unit_concat_strv(s, 0));
+        assert_se(a_esc = cescape(a));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc);
+        assert_se(streq(a, expected_none));
+
+        assert_se(b = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV));
+        assert_se(b_esc = cescape(b));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc);
+        assert_se(streq(b, expected_exec_env));
+
+        assert_se(c = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX));
+        assert_se(c_esc = cescape(c));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc);
+        assert_se(streq(c, expected_exec));
+
+        assert_se(d = unit_concat_strv(s, UNIT_ESCAPE_C));
+        assert_se(d_esc = cescape(d));
+        log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc);
+        assert_se(streq(d, expected_c));
+}
+
+TEST(unit_concat_strv) {
+        test_unit_concat_strv_one(STRV_MAKE("a", "b", "c"),
+                                  "\"a\" \"b\" \"c\"",
+                                  NULL,
+                                  NULL,
+                                  NULL);
+        test_unit_concat_strv_one(STRV_MAKE("a", " ", "$", "$$", ""),
+                                  "\"a\" \" \" \"$\" \"$$\" \"\"",
+                                  "\"a\" \" \" \"$$\" \"$$$$\" \"\"",
+                                  NULL,
+                                  NULL);
+        test_unit_concat_strv_one(STRV_MAKE("\n", " ", "\t"),
+                                  "\"\n\" \" \" \"\t\"",
+                                  "\"\\n\" \" \" \"\\t\"",
+                                  "\"\\n\" \" \" \"\\t\"",
+                                  "\"\\n\" \" \" \"\\t\"");
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
index 40b68df9f4c32ec2fbc49c617265c2752a022691..7a41e0fc2949b1248d453838d401e8f0616df395 100644 (file)
@@ -1,7 +1,12 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <elf.h>
+
 #include "alloc-util.h"
 #include "coredump-util.h"
+#include "fileio.h"
+#include "fd-util.h"
+#include "format-util.h"
 #include "macro.h"
 #include "tests.h"
 
@@ -64,4 +69,91 @@ TEST(coredump_filter_mask_from_string) {
                                 1 << COREDUMP_FILTER_SHARED_DAX)));
 }
 
+static void test_parse_auxv_two(
+                uint8_t elf_class,
+                size_t offset,
+                const char *data,
+                size_t data_size,
+                int expect_at_secure,
+                uid_t expect_uid,
+                uid_t expect_euid,
+                gid_t expect_gid,
+                gid_t expect_egid) {
+
+        int at_secure;
+        uid_t uid, euid;
+        gid_t gid, egid;
+        assert_se(parse_auxv(LOG_ERR, elf_class, data, data_size,
+                             &at_secure, &uid, &euid, &gid, &egid) == 0);
+
+        log_debug("[offset=%zu] at_secure=%d, uid="UID_FMT", euid="UID_FMT", gid="GID_FMT", egid="GID_FMT,
+                  offset,
+                  at_secure, uid, euid, gid, egid);
+
+        assert_se(uid == expect_uid);
+        assert_se(euid == expect_euid);
+        assert_se(gid == expect_gid);
+        assert_se(egid == expect_egid);
+}
+
+static void test_parse_auxv_one(
+                uint8_t elf_class,
+                int dir_fd,
+                const char *filename,
+                int expect_at_secure,
+                uid_t expect_uid,
+                uid_t expect_euid,
+                gid_t expect_gid,
+                gid_t expect_egid) {
+
+        _cleanup_free_ char *buf;
+        const char *data;
+        size_t data_size;
+        log_info("Parsing %s…", filename);
+        assert_se(read_full_file_at(dir_fd, filename, &buf, &data_size) >= 0);
+
+        for (size_t offset = 0; offset < 8; offset++) {
+                _cleanup_free_ char *buf2 = NULL;
+
+                if (offset == 0)
+                        data = buf;
+                else {
+                        assert_se(buf2 = malloc(offset + data_size));
+                        memcpy(buf2 + offset, buf, data_size);
+                        data = buf2 + offset;
+                }
+
+                test_parse_auxv_two(elf_class, offset, data, data_size,
+                                    expect_at_secure, expect_uid, expect_euid, expect_gid, expect_egid);
+        }
+}
+
+TEST(test_parse_auxv) {
+        _cleanup_free_ char *dir = NULL;
+        _cleanup_close_ int dir_fd = -EBADF;
+
+        assert_se(get_testdata_dir("auxv", &dir) >= 0);
+        dir_fd = open(dir, O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_PATH);
+        assert_se(dir_fd >= 0);
+
+        if (__BYTE_ORDER == __LITTLE_ENDIAN) {
+                test_parse_auxv_one(ELFCLASS32, dir_fd, "resolved.arm32", 0, 193, 193, 193, 193);
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "bash.riscv64", 0, 1001, 1001, 1001, 1001);
+                test_parse_auxv_one(ELFCLASS32, dir_fd, "sleep.i686", 0, 1000, 1000, 1000, 1000);
+                /* after chgrp and chmod g+s */
+                test_parse_auxv_one(ELFCLASS32, dir_fd, "sleep32.i686", 1, 1000, 1000, 1000, 10);
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "sleep64.amd64", 1, 1000, 1000, 1000, 10);
+
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "sudo.aarch64", 1, 1494200408, 0, 1494200408, 1494200408);
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "sudo.amd64", 1, 1000, 0, 1000, 1000);
+
+                /* Those run unprivileged, but start as root. */
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "dbus-broker-launch.amd64", 0, 0, 0, 0, 0);
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "dbus-broker-launch.aarch64", 0, 0, 0, 0, 0);
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "polkitd.aarch64", 0, 0, 0, 0, 0);
+        } else {
+                test_parse_auxv_one(ELFCLASS64, dir_fd, "cat.s390x", 0, 3481, 3481, 3481, 3481);
+        }
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);
index 461a0f0810177dcf237850eef9ae874e5bfba26d..c8ec0e2278aa449d86c756d197e272cd5e623ba5 100644 (file)
@@ -58,7 +58,6 @@
         "d= \" \\n\\t\\$\\`\\\\\n"              \
         "\"   \n"
 
-
 TEST(load_env_file_1) {
         _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX";
         assert_se(write_tmpfile(name, env_file_1) == 0);
@@ -129,6 +128,25 @@ TEST(load_env_file_6) {
         assert_se(data[4] == NULL);
 }
 
+TEST(load_env_file_invalid_utf8) {
+        /* Test out a couple of assignments where the key/value has an invalid
+         * UTF-8 character ("noncharacter")
+         *
+         * See: https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Non-characters
+         */
+        FOREACH_STRING(s,
+                       "fo\ufffeo=bar",
+                       "foo=b\uffffar",
+                       "baz=hello world\ufffe") {
+                _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX";
+                assert_se(write_tmpfile(name, s) == 0);
+
+                _cleanup_strv_free_ char **data = NULL;
+                assert_se(load_env_file(NULL, name, &data) == -EINVAL);
+                assert_se(!data);
+        }
+}
+
 TEST(write_and_load_env_file) {
         /* Make sure that our writer, parser and the shell agree on what our env var files mean */
 
index 4796e3c2080f01dfaf6a7f6afc89732c7d78455f..0f58d2fed04ead6f724b8ee95c1fb618b52d8c1f 100644 (file)
@@ -133,6 +133,32 @@ TEST(strv_env_assign) {
         assert_se(streq(a[0], "a=A"));
 }
 
+TEST(strv_env_assign_many) {
+        _cleanup_strv_free_ char **a = NULL;
+
+        assert_se(strv_env_assign_many(&a, "a", "a", "b", "b") >= 0);
+
+        assert_se(strv_length(a) == 2);
+        assert_se(strv_contains(a, "a=a"));
+        assert_se(strv_contains(a, "b=b"));
+
+        assert_se(strv_env_assign_many(&a, "a", "A", "b", "b", "c", "c") >= 0);
+        assert_se(strv_length(a) == 3);
+        assert_se(strv_contains(a, "a=A"));
+        assert_se(strv_contains(a, "b=b"));
+        assert_se(strv_contains(a, "c=c"));
+
+        assert_se(strv_env_assign_many(&a, "b", NULL, "c", "C") >= 0);
+        assert_se(strv_length(a) == 2);
+        assert_se(strv_contains(a, "a=A"));
+        assert_se(strv_contains(a, "c=C"));
+
+        assert_se(strv_env_assign_many(&a, "a=", "B") == -EINVAL);
+        assert_se(strv_length(a) == 2);
+        assert_se(strv_contains(a, "a=A"));
+        assert_se(strv_contains(a, "c=C"));
+}
+
 TEST(env_strv_get_n) {
         const char *_env[] = {
                 "FOO=NO NO NO",
@@ -452,4 +478,45 @@ TEST(getenv_steal_erase) {
         assert_se(r > 0);
 }
 
+TEST(strv_env_name_is_valid) {
+        assert_se(strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "PATH")));
+        assert_se(!strv_env_name_is_valid(STRV_MAKE("", "PATH", "home", "user", "SHELL")));
+        assert_se(!strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "USER")));
+}
+
+TEST(getenv_path_list) {
+        _cleanup_strv_free_ char **path_list = NULL;
+
+        /* Empty paths */
+        FOREACH_STRING(s, "", ":", ":::::", " : ::: :: :") {
+                assert_se(setenv("TEST_GETENV_PATH_LIST", s, 1) >= 0);
+                assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL);
+                assert_se(!path_list);
+        }
+
+        /* Invalid paths */
+        FOREACH_STRING(s, ".", "..", "/../", "/", "/foo/bar/baz/../foo", "foo/bar/baz") {
+                assert_se(setenv("TEST_GETENV_PATH_LIST", s, 1) >= 0);
+                assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL);
+                assert_se(!path_list);
+        }
+
+        /* Valid paths mixed with invalid ones */
+        assert_se(setenv("TEST_GETENV_PATH_LIST", "/foo:/bar/baz:/../:/hello", 1) >= 0);
+        assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL);
+        assert_se(!path_list);
+
+        /* Finally some valid paths */
+        assert_se(setenv("TEST_GETENV_PATH_LIST", "/foo:/bar/baz:/hello/world:/path with spaces:/final", 1) >= 0);
+        assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) >= 0);
+        assert_se(streq(path_list[0], "/foo"));
+        assert_se(streq(path_list[1], "/bar/baz"));
+        assert_se(streq(path_list[2], "/hello/world"));
+        assert_se(streq(path_list[3], "/path with spaces"));
+        assert_se(streq(path_list[4], "/final"));
+        assert_se(path_list[5] == NULL);
+
+        assert_se(unsetenv("TEST_GETENV_PATH_LIST") >= 0);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 0c96f28aa962864a27c357aef93a37e920d2dd4b..ae6227c492b64c3a241ca30d6a5f9a54253d0957 100644 (file)
@@ -283,7 +283,7 @@ static void test_exec_workingdirectory(Manager *m) {
 static void test_exec_execsearchpath(Manager *m) {
         assert_se(mkdir_p("/tmp/test-exec_execsearchpath", 0755) >= 0);
 
-        assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0,  0777, 0, 0, COPY_REPLACE) >= 0);
+        assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0,  0777, COPY_REPLACE) >= 0);
 
         test(m, "exec-execsearchpath.service", 0, CLD_EXITED);
 
@@ -401,9 +401,9 @@ static void test_exec_ignoresigpipe(Manager *m) {
 static void test_exec_privatetmp(Manager *m) {
         assert_se(touch("/tmp/test-exec_privatetmp") >= 0);
 
-        test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
         test(m, "exec-privatetmp-no.service", 0, CLD_EXITED);
-        test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 
         unlink("/tmp/test-exec_privatetmp");
 }
@@ -420,10 +420,10 @@ static void test_exec_privatedevices(Manager *m) {
                 return;
         }
 
-        test(m, "exec-privatedevices-yes.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-privatedevices-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
         test(m, "exec-privatedevices-no.service", 0, CLD_EXITED);
-        test(m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
-        test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_GROUP, CLD_EXITED);
+        test(m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
+        test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 
         /* We use capsh to test if the capabilities are
          * properly set, so be sure that it exists */
@@ -433,10 +433,10 @@ static void test_exec_privatedevices(Manager *m) {
                 return;
         }
 
-        test(m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED);
-        test(m, "exec-privatedevices-no-capability-mknod.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
-        test(m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED);
-        test(m, "exec-privatedevices-no-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-privatedevices-yes-capability-mknod.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+        test(m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED);
+        test(m, "exec-privatedevices-yes-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+        test(m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED);
 }
 
 static void test_exec_protecthome(Manager *m) {
@@ -466,23 +466,23 @@ static void test_exec_protectkernelmodules(Manager *m) {
                 return;
         }
 
-        test(m, "exec-protectkernelmodules-no-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
-        test(m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED);
-        test(m, "exec-protectkernelmodules-yes-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED);
+        test(m, "exec-protectkernelmodules-yes-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+        test(m, "exec-protectkernelmodules-yes-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_readonlypaths(Manager *m) {
 
-        test(m, "exec-readonlypaths-simple.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-readonlypaths-simple.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 
         if (path_is_read_only_fs("/var") > 0) {
                 log_notice("Directory /var is readonly, skipping remaining tests in %s", __func__);
                 return;
         }
 
-        test(m, "exec-readonlypaths.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-readonlypaths.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
         test(m, "exec-readonlypaths-with-bindpaths.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED);
-        test(m, "exec-readonlypaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-readonlypaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_readwritepaths(Manager *m) {
@@ -492,7 +492,7 @@ static void test_exec_readwritepaths(Manager *m) {
                 return;
         }
 
-        test(m, "exec-readwritepaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-readwritepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_inaccessiblepaths(Manager *m) {
@@ -502,14 +502,14 @@ static void test_exec_inaccessiblepaths(Manager *m) {
                 return;
         }
 
-        test(m, "exec-inaccessiblepaths-sys.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-inaccessiblepaths-sys.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 
         if (path_is_read_only_fs("/") > 0) {
                 log_notice("Root directory is readonly, skipping remaining tests in %s", __func__);
                 return;
         }
 
-        test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
@@ -687,14 +687,14 @@ static void test_exec_mount_apivfs(Manager *m) {
 
         assert_se(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755) >= 0);
 
-        test(m, "exec-mount-apivfs-no.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+        test(m, "exec-mount-apivfs-no.service", can_unshare || !MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
 
         (void) rm_rf("/tmp/test-exec-mount-apivfs-no/root", REMOVE_ROOT|REMOVE_PHYSICAL);
 }
 
 static void test_exec_noexecpaths(Manager *m) {
 
-        test(m, "exec-noexecpaths-simple.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-noexecpaths-simple.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_temporaryfilesystem(Manager *m) {
@@ -964,8 +964,8 @@ static void test_exec_passenvironment(Manager *m) {
 }
 
 static void test_exec_umask(Manager *m) {
-        test(m, "exec-umask-default.service", 0, CLD_EXITED);
-        test(m, "exec-umask-0177.service", 0, CLD_EXITED);
+        test(m, "exec-umask-default.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
+        test(m, "exec-umask-0177.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_runtimedirectory(Manager *m) {
@@ -1012,7 +1012,7 @@ static void test_exec_capabilityboundingset(Manager *m) {
 }
 
 static void test_exec_basic(Manager *m) {
-        test(m, "exec-basic.service", 0, CLD_EXITED);
+        test(m, "exec-basic.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_ambientcapabilities(Manager *m) {
@@ -1052,7 +1052,7 @@ static void test_exec_ambientcapabilities(Manager *m) {
 }
 
 static void test_exec_privatenetwork(Manager *m) {
-        int r, status;
+        int r;
 
         r = find_executable("ip", NULL);
         if (r < 0) {
@@ -1060,9 +1060,8 @@ static void test_exec_privatenetwork(Manager *m) {
                 return;
         }
 
-        status = can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_FAILURE;
-        test(m, "exec-privatenetwork-yes-privatemounts-no.service", status, CLD_EXITED);
-        test(m, "exec-privatenetwork-yes-privatemounts-yes.service", status, CLD_EXITED);
+        test(m, "exec-privatenetwork-yes-privatemounts-no.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-privatenetwork-yes-privatemounts-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_networknamespacepath(Manager *m) {
@@ -1075,7 +1074,7 @@ static void test_exec_networknamespacepath(Manager *m) {
         }
 
         test(m, "exec-networknamespacepath-privatemounts-no.service", MANAGER_IS_SYSTEM(m) ? EXIT_SUCCESS : EXIT_FAILURE, CLD_EXITED);
-        test(m, "exec-networknamespacepath-privatemounts-yes.service", can_unshare ? EXIT_SUCCESS : EXIT_FAILURE, CLD_EXITED);
+        test(m, "exec-networknamespacepath-privatemounts-yes.service", can_unshare ? EXIT_SUCCESS : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
 }
 
 static void test_exec_oomscoreadjust(Manager *m) {
@@ -1105,12 +1104,12 @@ static void test_exec_unsetenvironment(Manager *m) {
 }
 
 static void test_exec_specifier(Manager *m) {
-        test(m, "exec-specifier.service", 0, CLD_EXITED);
+        test(m, "exec-specifier.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
         if (MANAGER_IS_SYSTEM(m))
                 test(m, "exec-specifier-system.service", 0, CLD_EXITED);
         else
                 test(m, "exec-specifier-user.service", 0, CLD_EXITED);
-        test(m, "exec-specifier@foo-bar.service", 0, CLD_EXITED);
+        test(m, "exec-specifier@foo-bar.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED);
         test(m, "exec-specifier-interpolation.service", 0, CLD_EXITED);
 }
 
index 1f14f0b6cb4b85d78e7a758eba1cfd5314b6a069..20d412f5c6c82292644e47d365a5bd1880db986c 100644 (file)
@@ -9,6 +9,7 @@
 #include "data-fd-util.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "fs-util.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "missing_syscall.h"
 #include "rm-rf.h"
 #include "seccomp-util.h"
 #include "serialize.h"
+#include "stat-util.h"
 #include "string-util.h"
 #include "tests.h"
 #include "tmpfile-util.h"
 
 TEST(close_many) {
         int fds[3];
-        char name0[] = "/tmp/test-close-many.XXXXXX";
-        char name1[] = "/tmp/test-close-many.XXXXXX";
-        char name2[] = "/tmp/test-close-many.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name0[] = "/tmp/test-close-many.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name1[] = "/tmp/test-close-many.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name2[] = "/tmp/test-close-many.XXXXXX";
 
         fds[0] = mkostemp_safe(name0);
         fds[1] = mkostemp_safe(name1);
@@ -43,22 +45,16 @@ TEST(close_many) {
         assert_se(fcntl(fds[2], F_GETFD) >= 0);
 
         safe_close(fds[2]);
-
-        unlink(name0);
-        unlink(name1);
-        unlink(name2);
 }
 
 TEST(close_nointr) {
-        char name[] = "/tmp/test-test-close_nointr.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-test-close_nointr.XXXXXX";
         int fd;
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
         assert_se(close_nointr(fd) >= 0);
         assert_se(close_nointr(fd) < 0);
-
-        unlink(name);
 }
 
 TEST(same_fd) {
@@ -278,6 +274,7 @@ static void test_close_all_fds_inner(void) {
         /* Close logging fd first, so that we don't confuse it by closing its fd */
         log_close();
         log_set_open_when_needed(true);
+        log_settle_target();
 
         /* Close all but the ones to keep */
         assert_se(close_all_fds(keep, n_keep) >= 0);
@@ -409,13 +406,16 @@ TEST(fd_reopen) {
         assert_se(FLAGS_SET(fl, O_DIRECTORY));
         assert_se(FLAGS_SET(fl, O_PATH));
 
+        /* fd_reopen() with O_NOFOLLOW will systematically fail, since it is implemented via a symlink in /proc/self/fd/ */
+        assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC|O_NOFOLLOW) == -ELOOP);
+        assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW) == -ELOOP);
+
         fd2 = fd_reopen(fd1, O_RDONLY|O_DIRECTORY|O_CLOEXEC);  /* drop the O_PATH */
         assert_se(fd2 >= 0);
 
         assert_se(fstat(fd2, &st2) >= 0);
         assert_se(S_ISDIR(st2.st_mode));
-        assert_se(st1.st_ino == st2.st_ino);
-        assert_se(st1.st_rdev == st2.st_rdev);
+        assert_se(stat_inode_same(&st1, &st2));
 
         fl = fcntl(fd2, F_GETFL);
         assert_se(fl >= 0);
@@ -429,8 +429,7 @@ TEST(fd_reopen) {
 
         assert_se(fstat(fd1, &st1) >= 0);
         assert_se(S_ISDIR(st1.st_mode));
-        assert_se(st1.st_ino == st2.st_ino);
-        assert_se(st1.st_rdev == st2.st_rdev);
+        assert_se(stat_inode_same(&st1, &st2));
 
         fl = fcntl(fd1, F_GETFL);
         assert_se(fl >= 0);
@@ -457,8 +456,7 @@ TEST(fd_reopen) {
 
         assert_se(fstat(fd2, &st2) >= 0);
         assert_se(S_ISREG(st2.st_mode));
-        assert_se(st1.st_ino == st2.st_ino);
-        assert_se(st1.st_rdev == st2.st_rdev);
+        assert_se(stat_inode_same(&st1, &st2));
 
         fl = fcntl(fd2, F_GETFL);
         assert_se(fl >= 0);
@@ -473,8 +471,7 @@ TEST(fd_reopen) {
 
         assert_se(fstat(fd1, &st1) >= 0);
         assert_se(S_ISREG(st1.st_mode));
-        assert_se(st1.st_ino == st2.st_ino);
-        assert_se(st1.st_rdev == st2.st_rdev);
+        assert_se(stat_inode_same(&st1, &st2));
 
         fl = fcntl(fd1, F_GETFL);
         assert_se(fl >= 0);
@@ -485,6 +482,23 @@ TEST(fd_reopen) {
         safe_close(fd1);
         assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC) == -EBADF);
         fd1 = -EBADF;
+
+        /* Validate what happens if we reopen a symlink */
+        fd1 = open("/proc/self", O_PATH|O_CLOEXEC|O_NOFOLLOW);
+        assert_se(fd1 >= 0);
+        assert_se(fstat(fd1, &st1) >= 0);
+        assert_se(S_ISLNK(st1.st_mode));
+
+        fd2 = fd_reopen(fd1, O_PATH|O_CLOEXEC);
+        assert_se(fd2 >= 0);
+        assert_se(fstat(fd2, &st2) >= 0);
+        assert_se(S_ISLNK(st2.st_mode));
+        assert_se(stat_inode_same(&st1, &st2));
+        fd2 = safe_close(fd2);
+
+        /* So here's the thing: if we have an O_PATH fd to a symlink, we *cannot* convert it to a regular fd
+         * with that. i.e. you cannot have the VFS follow a symlink pinned via an O_PATH fd. */
+        assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC) == -ELOOP);
 }
 
 TEST(fd_reopen_condition) {
@@ -577,13 +591,17 @@ TEST(dir_fd_is_root) {
         _cleanup_close_ int fd = -EBADF;
         int r;
 
+        assert_se(dir_fd_is_root_or_cwd(AT_FDCWD) > 0);
+
         assert_se((fd = open("/", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
         assert_se(dir_fd_is_root(fd) > 0);
+        assert_se(dir_fd_is_root_or_cwd(fd) > 0);
 
         fd = safe_close(fd);
 
         assert_se((fd = open("/usr", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
         assert_se(dir_fd_is_root(fd) == 0);
+        assert_se(dir_fd_is_root_or_cwd(fd) == 0);
 
         r = detach_mount_namespace();
         if (r < 0)
@@ -602,16 +620,135 @@ TEST(dir_fd_is_root) {
 
         assert_se((fd = open(tmp, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
         assert_se(dir_fd_is_root(fd) == 0);
+        assert_se(dir_fd_is_root_or_cwd(fd) == 0);
 
         fd = safe_close(fd);
 
         assert_se((fd = open(x, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
         assert_se(dir_fd_is_root(fd) == 0);
+        assert_se(dir_fd_is_root_or_cwd(fd) == 0);
 
         fd = safe_close(fd);
 
         assert_se((fd = open(y, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0);
         assert_se(dir_fd_is_root(fd) == 0);
+        assert_se(dir_fd_is_root_or_cwd(fd) == 0);
+}
+
+TEST(fd_get_path) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+        _cleanup_free_ char *p = NULL, *q = NULL, *saved_cwd = NULL;
+
+        tfd = mkdtemp_open(NULL, O_PATH, &t);
+        assert_se(tfd >= 0);
+        assert_se(fd_get_path(tfd, &p) >= 0);
+        assert_se(streq(p, t));
+
+        p = mfree(p);
+
+        assert_se(safe_getcwd(&saved_cwd) >= 0);
+        assert_se(chdir(t) >= 0);
+
+        assert_se(fd_get_path(AT_FDCWD, &p) >= 0);
+        assert_se(streq(p, t));
+
+        p = mfree(p);
+
+        assert_se(q = path_join(t, "regular"));
+        assert_se(touch(q) >= 0);
+        assert_se(mkdirat_parents(tfd, "subdir/symlink", 0755) >= 0);
+        assert_se(symlinkat("../regular", tfd, "subdir/symlink") >= 0);
+        assert_se(symlinkat("subdir", tfd, "symdir") >= 0);
+
+        fd = openat(tfd, "regular", O_CLOEXEC|O_PATH);
+        assert_se(fd >= 0);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(AT_FDCWD, "regular", O_CLOEXEC|O_PATH);
+        assert_se(fd >= 0);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(tfd, "subdir/symlink", O_CLOEXEC|O_PATH);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(AT_FDCWD, "subdir/symlink", O_CLOEXEC|O_PATH);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(tfd, "symdir//./symlink", O_CLOEXEC|O_PATH);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(AT_FDCWD, "symdir//./symlink", O_CLOEXEC|O_PATH);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        q = mfree(q);
+        fd = safe_close(fd);
+
+        assert_se(q = path_join(t, "subdir/symlink"));
+        fd = openat(tfd, "subdir/symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) == -ELOOP);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(AT_FDCWD, "subdir/symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) == -ELOOP);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(tfd, "symdir//./symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) == -ELOOP);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        p = mfree(p);
+        fd = safe_close(fd);
+
+        fd = openat(AT_FDCWD, "symdir//./symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW);
+        assert_se(fd >= 0);
+        assert_se(fd_verify_regular(fd) == -ELOOP);
+        assert_se(fd_get_path(fd, &p) >= 0);
+        assert_se(streq(p, q));
+
+        assert_se(chdir(saved_cwd) >= 0);
 }
 
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 67c99d85012eaf539c3fc17bd3c4720585084937..e2b8f94833079371eeec14dc007fff9eb3b47616 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "fd-util.h"
 #include "fdset.h"
+#include "fs-util.h"
 #include "macro.h"
 #include "tests.h"
 #include "tmpfile-util.h"
 TEST(fdset_new_fill) {
         int fd = -EBADF;
         _cleanup_fdset_free_ FDSet *fdset = NULL;
-        char name[] = "/tmp/test-fdset_new_fill.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_new_fill.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
         assert_se(fdset_new_fill(&fdset) >= 0);
         assert_se(fdset_contains(fdset, fd));
-
-        unlink(name);
 }
 
 TEST(fdset_put_dup) {
         _cleanup_close_ int fd = -EBADF;
         int copyfd = -EBADF;
         _cleanup_fdset_free_ FDSet *fdset = NULL;
-        char name[] = "/tmp/test-fdset_put_dup.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_put_dup.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -37,15 +36,13 @@ TEST(fdset_put_dup) {
         assert_se(copyfd >= 0 && copyfd != fd);
         assert_se(fdset_contains(fdset, copyfd));
         assert_se(!fdset_contains(fdset, fd));
-
-        unlink(name);
 }
 
 TEST(fdset_cloexec) {
         int fd = -EBADF;
         _cleanup_fdset_free_ FDSet *fdset = NULL;
         int flags = -1;
-        char name[] = "/tmp/test-fdset_cloexec.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_cloexec.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -63,8 +60,6 @@ TEST(fdset_cloexec) {
         flags = fcntl(fd, F_GETFD);
         assert_se(flags >= 0);
         assert_se(flags & FD_CLOEXEC);
-
-        unlink(name);
 }
 
 TEST(fdset_close_others) {
@@ -72,7 +67,7 @@ TEST(fdset_close_others) {
         int copyfd = -EBADF;
         _cleanup_fdset_free_ FDSet *fdset = NULL;
         int flags = -1;
-        char name[] = "/tmp/test-fdset_close_others.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_close_others.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -87,14 +82,12 @@ TEST(fdset_close_others) {
         assert_se(flags < 0);
         flags = fcntl(copyfd, F_GETFD);
         assert_se(flags >= 0);
-
-        unlink(name);
 }
 
 TEST(fdset_remove) {
         _cleanup_close_ int fd = -EBADF;
-        FDSet *fdset = NULL;
-        char name[] = "/tmp/test-fdset_remove.XXXXXX";
+        _cleanup_fdset_free_ FDSet *fdset = NULL;
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_remove.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -104,17 +97,14 @@ TEST(fdset_remove) {
         assert_se(fdset_put(fdset, fd) >= 0);
         assert_se(fdset_remove(fdset, fd) >= 0);
         assert_se(!fdset_contains(fdset, fd));
-        fdset_free(fdset);
 
         assert_se(fcntl(fd, F_GETFD) >= 0);
-
-        unlink(name);
 }
 
 TEST(fdset_iterate) {
         int fd = -EBADF;
-        FDSet *fdset = NULL;
-        char name[] = "/tmp/test-fdset_iterate.XXXXXX";
+        _cleanup_fdset_free_ FDSet *fdset = NULL;
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_iterate.XXXXXX";
         int c = 0;
         int a;
 
@@ -132,16 +122,12 @@ TEST(fdset_iterate) {
                 assert_se(a == fd);
         }
         assert_se(c == 1);
-
-        fdset_free(fdset);
-
-        unlink(name);
 }
 
 TEST(fdset_isempty) {
         int fd;
         _cleanup_fdset_free_ FDSet *fdset = NULL;
-        char name[] = "/tmp/test-fdset_isempty.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_isempty.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -152,14 +138,12 @@ TEST(fdset_isempty) {
         assert_se(fdset_isempty(fdset));
         assert_se(fdset_put(fdset, fd) >= 0);
         assert_se(!fdset_isempty(fdset));
-
-        unlink(name);
 }
 
 TEST(fdset_steal_first) {
         int fd;
         _cleanup_fdset_free_ FDSet *fdset = NULL;
-        char name[] = "/tmp/test-fdset_steal_first.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_steal_first.XXXXXX";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -172,8 +156,6 @@ TEST(fdset_steal_first) {
         assert_se(fdset_steal_first(fdset) == fd);
         assert_se(fdset_steal_first(fdset) < 0);
         assert_se(fdset_put(fdset, fd) >= 0);
-
-        unlink(name);
 }
 
 TEST(fdset_new_array) {
index 206444093b0fd85fa3cf976fb5f6e750e927a8e7..a570941cf303fe6dd937908c4345c40a86d62f50 100644 (file)
@@ -14,6 +14,7 @@
 #include "fileio.h"
 #include "fs-util.h"
 #include "io-util.h"
+#include "memfd-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -385,6 +386,42 @@ TEST(capeff) {
         }
 }
 
+TEST(read_one_line_file) {
+        _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-fileio-1lf-XXXXXX";
+        int fd;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *buf, *buf2, *buf3, *buf4, *buf5;
+
+        fd = mkostemp_safe(fn);
+        assert_se(fd >= 0);
+
+        f = fdopen(fd, "we");
+        assert_se(f);
+
+        assert_se(read_one_line_file(fn, &buf) == 0);
+        assert_se(streq_ptr(buf, ""));
+        assert_se(read_one_line_file(fn, &buf2) == 0);
+        assert_se(streq_ptr(buf2, ""));
+
+        assert_se(write_string_stream(f, "x", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+        fflush(f);
+
+        assert_se(read_one_line_file(fn, &buf3) == 1);
+        assert_se(streq_ptr(buf3, "x"));
+
+        assert_se(write_string_stream(f, "\n", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+        fflush(f);
+
+        assert_se(read_one_line_file(fn, &buf4) == 2);
+        assert_se(streq_ptr(buf4, "x"));
+
+        assert_se(write_string_stream(f, "\n", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+        fflush(f);
+
+        assert_se(read_one_line_file(fn, &buf5) == 2);
+        assert_se(streq_ptr(buf5, "x"));
+}
+
 TEST(write_string_stream) {
         _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-write_string_stream-XXXXXX";
         _cleanup_fclose_ FILE *f = NULL;
@@ -472,6 +509,23 @@ TEST(write_string_file_verify) {
         assert_se(write_string_file("/proc/version", buf2, WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_AVOID_NEWLINE) == 0);
 }
 
+static void check_file_pairs_one(char **l) {
+        assert_se(l);
+        assert_se(strv_length(l) == 14);
+
+        STRV_FOREACH_PAIR(k, v, l) {
+                assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL"));
+                printf("%s=%s\n", *k, *v);
+                assert_se(!streq(*k, "NAME") || streq(*v, "Arch Linux"));
+                assert_se(!streq(*k, "ID") || streq(*v, "arch"));
+                assert_se(!streq(*k, "PRETTY_NAME") || streq(*v, "Arch Linux"));
+                assert_se(!streq(*k, "ANSI_COLOR") || streq(*v, "0;36"));
+                assert_se(!streq(*k, "HOME_URL") || streq(*v, "https://www.archlinux.org/"));
+                assert_se(!streq(*k, "SUPPORT_URL") || streq(*v, "https://bbs.archlinux.org/"));
+                assert_se(!streq(*k, "BUG_REPORT_URL") || streq(*v, "https://bugs.archlinux.org/"));
+        }
+}
+
 TEST(load_env_file_pairs) {
         _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-load_env_file_pairs-XXXXXX";
         int fd, r;
@@ -492,24 +546,17 @@ TEST(load_env_file_pairs) {
                         WRITE_STRING_FILE_CREATE);
         assert_se(r == 0);
 
+        r = load_env_file_pairs_fd(fd, fn, &l);
+        assert_se(r >= 0);
+        check_file_pairs_one(l);
+        l = strv_free(l);
+
         f = fdopen(fd, "r");
         assert_se(f);
 
         r = load_env_file_pairs(f, fn, &l);
         assert_se(r >= 0);
-
-        assert_se(strv_length(l) == 14);
-        STRV_FOREACH_PAIR(k, v, l) {
-                assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL"));
-                printf("%s=%s\n", *k, *v);
-                if (streq(*k, "NAME")) assert_se(streq(*v, "Arch Linux"));
-                if (streq(*k, "ID")) assert_se(streq(*v, "arch"));
-                if (streq(*k, "PRETTY_NAME")) assert_se(streq(*v, "Arch Linux"));
-                if (streq(*k, "ANSI_COLOR")) assert_se(streq(*v, "0;36"));
-                if (streq(*k, "HOME_URL")) assert_se(streq(*v, "https://www.archlinux.org/"));
-                if (streq(*k, "SUPPORT_URL")) assert_se(streq(*v, "https://bbs.archlinux.org/"));
-                if (streq(*k, "BUG_REPORT_URL")) assert_se(streq(*v, "https://bugs.archlinux.org/"));
-        }
+        check_file_pairs_one(l);
 }
 
 TEST(search_and_fopen) {
@@ -1020,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);
         }
@@ -1036,4 +1084,46 @@ TEST(test_read_virtual_file) {
         test_read_virtual_file_one(SIZE_MAX);
 }
 
+TEST(test_fdopen_independent) {
+#define TEST_TEXT "this is some random test text we are going to write to a memfd"
+        _cleanup_close_ int fd = -EBADF;
+        _cleanup_fclose_ FILE *f = NULL;
+        char buf[STRLEN(TEST_TEXT) + 1];
+
+        fd = memfd_new("fdopen_independent");
+        if (fd < 0) {
+                assert_se(ERRNO_IS_NOT_SUPPORTED(fd));
+                return;
+        }
+
+        assert_se(write(fd, TEST_TEXT, strlen(TEST_TEXT)) == strlen(TEST_TEXT));
+        /* we'll leave the read offset at the end of the memfd, the fdopen_independent() descriptors should
+         * start at the beginning anyway */
+
+        assert_se(fdopen_independent(fd, "re", &f) >= 0);
+        zero(buf);
+        assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT));
+        assert_se(streq(buf, TEST_TEXT));
+        assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDONLY);
+        assert_se(FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC));
+        f = safe_fclose(f);
+
+        assert_se(fdopen_independent(fd, "r", &f) >= 0);
+        zero(buf);
+        assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT));
+        assert_se(streq(buf, TEST_TEXT));
+        assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDONLY);
+        assert_se(!FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC));
+        f = safe_fclose(f);
+
+        assert_se(fdopen_independent(fd, "r+e", &f) >= 0);
+        zero(buf);
+        assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT));
+        assert_se(streq(buf, TEST_TEXT));
+        assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDWR);
+        assert_se(FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC));
+        f = safe_fclose(f);
+}
+
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index a1bc8b99acbe6978b771236af96e2a8fb94a508b..7d544b18fdde42db0f14bcc49ff5e5703d59bae3 100644 (file)
@@ -583,6 +583,49 @@ TEST(path_basename) {
         assert_se(streq(formatted, "bar\nbar\nbaz\n"));
 }
 
+TEST(dup_cell) {
+        _cleanup_(table_unrefp) Table *t = NULL;
+        _cleanup_free_ char *formatted = NULL;
+
+        assert_se(t = table_new("foo", "bar", "x", "baz", ".", "%", "!", "~", "+"));
+        table_set_width(t, 75);
+
+        assert_se(table_add_many(t,
+                                 TABLE_STRING, "hello",
+                                 TABLE_UINT8, UINT8_C(42),
+                                 TABLE_UINT16, UINT16_C(666),
+                                 TABLE_UINT32, UINT32_C(253),
+                                 TABLE_PERCENT, 0,
+                                 TABLE_PATH_BASENAME, "/foo/bar",
+                                 TABLE_STRING, "aaa",
+                                 TABLE_STRING, "bbb",
+                                 TABLE_STRING, "ccc") >= 0);
+
+        /* Add the second row by duping cells */
+        for (size_t i = 0; i < table_get_columns(t); i++)
+                assert_se(table_dup_cell(t, table_get_cell(t, 1, i)) >= 0);
+
+        /* Another row, but dupe the last three strings from the same cell */
+        assert_se(table_add_many(t,
+                                 TABLE_STRING, "aaa",
+                                 TABLE_UINT8, UINT8_C(0),
+                                 TABLE_UINT16, UINT16_C(65535),
+                                 TABLE_UINT32, UINT32_C(4294967295),
+                                 TABLE_PERCENT, 100,
+                                 TABLE_PATH_BASENAME, "../") >= 0);
+
+        for (size_t i = 6; i < table_get_columns(t); i++)
+                assert_se(table_dup_cell(t, table_get_cell(t, 2, 0)) >= 0);
+
+        assert_se(table_format(t, &formatted) >= 0);
+        printf("%s\n", formatted);
+        assert_se(streq(formatted,
+                        "FOO     BAR   X       BAZ          .      %      !        ~        +\n"
+                        "hello   42    666     253          0%     bar    aaa      bbb      ccc\n"
+                        "hello   42    666     253          0%     bar    aaa      bbb      ccc\n"
+                        "aaa     0     65535   4294967295   100%   ../    hello    hello    hello\n"));
+}
+
 static int intro(void) {
         assert_se(setenv("SYSTEMD_COLORS", "0", 1) >= 0);
         assert_se(setenv("COLUMNS", "40", 1) >= 0);
index 3d73e8e5b1562b05350b6f6da54970e78e42d646..fa33b807b28689eacc2c3e6890a0d705adb8ae53 100644 (file)
@@ -3,17 +3,18 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
 #include "copy.h"
+#include "dirent-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
-#include "id128-util.h"
 #include "macro.h"
 #include "mkdir.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "random-util.h"
 #include "rm-rf.h"
+#include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
 
 static const char *arg_test_dir = NULL;
 
-TEST(chase_symlinks) {
-        _cleanup_free_ char *result = NULL, *pwd = NULL;
-        _cleanup_close_ int pfd = -EBADF;
-        char *temp;
-        const char *top, *p, *pslash, *q, *qslash;
-        struct stat st;
-        int r;
-
-        temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
-        assert_se(mkdtemp(temp));
-
-        top = strjoina(temp, "/top");
-        assert_se(mkdir(top, 0700) >= 0);
-
-        p = strjoina(top, "/dot");
-        if (symlink(".", p) < 0) {
-                assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
-                log_tests_skipped_errno(errno, "symlink() not possible");
-                goto cleanup;
-        };
-
-        p = strjoina(top, "/dotdot");
-        assert_se(symlink("..", p) >= 0);
-
-        p = strjoina(top, "/dotdota");
-        assert_se(symlink("../a", p) >= 0);
-
-        p = strjoina(temp, "/a");
-        assert_se(symlink("b", p) >= 0);
-
-        p = strjoina(temp, "/b");
-        assert_se(symlink("/usr", p) >= 0);
-
-        p = strjoina(temp, "/start");
-        assert_se(symlink("top/dot/dotdota", p) >= 0);
-
-        /* Paths that use symlinks underneath the "root" */
-
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/usr"));
-        result = mfree(result);
-
-        pslash = strjoina(p, "/");
-        r = chase_symlinks(pslash, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/usr/"));
-        result = mfree(result);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase_symlinks(pslash, temp, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        q = strjoina(temp, "/usr");
-
-        r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, q));
-        result = mfree(result);
-
-        qslash = strjoina(q, "/");
-
-        r = chase_symlinks(pslash, temp, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, qslash));
-        result = mfree(result);
-
-        assert_se(mkdir(q, 0700) >= 0);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, q));
-        result = mfree(result);
-
-        r = chase_symlinks(pslash, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, qslash));
-        result = mfree(result);
-
-        p = strjoina(temp, "/slash");
-        assert_se(symlink("/", p) >= 0);
-
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/"));
-        result = mfree(result);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, temp));
-        result = mfree(result);
-
-        /* Paths that would "escape" outside of the "root" */
-
-        p = strjoina(temp, "/6dots");
-        assert_se(symlink("../../..", p) >= 0);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, temp));
-        result = mfree(result);
-
-        p = strjoina(temp, "/6dotsusr");
-        assert_se(symlink("../../../usr", p) >= 0);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, q));
-        result = mfree(result);
-
-        p = strjoina(temp, "/top/8dotsusr");
-        assert_se(symlink("../../../../usr", p) >= 0);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, q));
-        result = mfree(result);
-
-        /* Paths that contain repeated slashes */
-
-        p = strjoina(temp, "/slashslash");
-        assert_se(symlink("///usr///", p) >= 0);
-
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/usr"));
-        assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
-        result = mfree(result);
-
-        r = chase_symlinks(p, temp, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, q));
-        result = mfree(result);
-
-        /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
-
-        if (geteuid() == 0) {
-                p = strjoina(temp, "/user");
-                assert_se(mkdir(p, 0755) >= 0);
-                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
-
-                q = strjoina(temp, "/user/root");
-                assert_se(mkdir(q, 0755) >= 0);
-
-                p = strjoina(q, "/link");
-                assert_se(symlink("/", p) >= 0);
-
-                /* Fail when user-owned directories contain root-owned subdirectories. */
-                r = chase_symlinks(p, temp, CHASE_SAFE, &result, NULL);
-                assert_se(r == -ENOLINK);
-                result = mfree(result);
-
-                /* Allow this when the user-owned directories are all in the "root". */
-                r = chase_symlinks(p, q, CHASE_SAFE, &result, NULL);
-                assert_se(r > 0);
-                result = mfree(result);
-        }
-
-        /* Paths using . */
-
-        r = chase_symlinks("/etc/./.././", NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(result, "/"));
-        result = mfree(result);
-
-        r = chase_symlinks("/etc/./.././", "/etc", 0, &result, NULL);
-        assert_se(r > 0 && path_equal(result, "/etc"));
-        result = mfree(result);
-
-        r = chase_symlinks("/../.././//../../etc", NULL, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(streq(result, "/etc"));
-        result = mfree(result);
-
-        r = chase_symlinks("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(streq(result, "/test-chase.fsldajfl"));
-        result = mfree(result);
-
-        r = chase_symlinks("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL);
-        assert_se(r > 0);
-        assert_se(streq(result, "/etc"));
-        result = mfree(result);
-
-        r = chase_symlinks("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(streq(result, "/test-chase.fsldajfl"));
-        result = mfree(result);
-
-        r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result, NULL);
-        assert_se(IN_SET(r, -ENOTDIR, -ENOENT));
-        result = mfree(result);
-
-        /* Path that loops back to self */
-
-        p = strjoina(temp, "/recursive-symlink");
-        assert_se(symlink("recursive-symlink", p) >= 0);
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r == -ELOOP);
-
-        /* Path which doesn't exist */
-
-        p = strjoina(temp, "/idontexist");
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        p = strjoina(temp, "/idontexist/meneither");
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        /* Relative paths */
-
-        assert_se(safe_getcwd(&pwd) >= 0);
-
-        assert_se(chdir(temp) >= 0);
-
-        p = "this/is/a/relative/path";
-        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-
-        p = strjoina(temp, "/", p);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        p = "this/is/a/relative/path";
-        r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == 0);
-
-        p = strjoina(temp, "/", p);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-
-        assert_se(chdir(pwd) >= 0);
-
-        /* Path which doesn't exist, but contains weird stuff */
-
-        p = strjoina(temp, "/idontexist/..");
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        p = strjoina(temp, "/target");
-        q = strjoina(temp, "/top");
-        assert_se(symlink(q, p) >= 0);
-        p = strjoina(temp, "/target/idontexist");
-        r = chase_symlinks(p, NULL, 0, &result, NULL);
-        assert_se(r == -ENOENT);
-
-        if (geteuid() == 0) {
-                p = strjoina(temp, "/priv1");
-                assert_se(mkdir(p, 0755) >= 0);
-
-                q = strjoina(p, "/priv2");
-                assert_se(mkdir(q, 0755) >= 0);
-
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
-                assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
-                assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-
-                assert_se(chown(q, 0, 0) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
-                assert_se(rmdir(q) >= 0);
-                assert_se(symlink("/etc/passwd", q) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK);
-
-                assert_se(chown(p, 0, 0) >= 0);
-                assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL, NULL) >= 0);
-        }
-
-        p = strjoina(temp, "/machine-id-test");
-        assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
-
-        r = chase_symlinks(p, NULL, 0, NULL, &pfd);
-        if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) {
-                _cleanup_close_ int fd = -EBADF;
-                sd_id128_t a, b;
-
-                assert_se(pfd >= 0);
-
-                fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
-                assert_se(fd >= 0);
-                safe_close(pfd);
-
-                assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0);
-                assert_se(sd_id128_get_machine(&b) >= 0);
-                assert_se(sd_id128_equal(a, b));
-        }
-
-        assert_se(lstat(p, &st) >= 0);
-        r = chase_symlinks_and_unlink(p, NULL, 0, 0, &result);
-        assert_se(r == 0);
-        assert_se(path_equal(result, p));
-        result = mfree(result);
-        assert_se(lstat(p, &st) == -1 && errno == ENOENT);
-
-        /* Test CHASE_NOFOLLOW */
-
-        p = strjoina(temp, "/target");
-        q = strjoina(temp, "/symlink");
-        assert_se(symlink(p, q) >= 0);
-        r = chase_symlinks(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
-        assert_se(r >= 0);
-        assert_se(pfd >= 0);
-        assert_se(path_equal(result, q));
-        assert_se(fstat(pfd, &st) >= 0);
-        assert_se(S_ISLNK(st.st_mode));
-        result = mfree(result);
-        pfd = safe_close(pfd);
-
-        /* s1 -> s2 -> nonexistent */
-        q = strjoina(temp, "/s1");
-        assert_se(symlink("s2", q) >= 0);
-        p = strjoina(temp, "/s2");
-        assert_se(symlink("nonexistent", p) >= 0);
-        r = chase_symlinks(q, NULL, CHASE_NOFOLLOW, &result, &pfd);
-        assert_se(r >= 0);
-        assert_se(pfd >= 0);
-        assert_se(path_equal(result, q));
-        assert_se(fstat(pfd, &st) >= 0);
-        assert_se(S_ISLNK(st.st_mode));
-        result = mfree(result);
-        pfd = safe_close(pfd);
-
-        /* Test CHASE_STEP */
-
-        p = strjoina(temp, "/start");
-        r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/top/dot/dotdota");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/top/dotdota");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/top/../a");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/a");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        p = strjoina(temp, "/b");
-        assert_se(streq(p, result));
-        result = mfree(result);
-
-        r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
-        assert_se(r == 0);
-        assert_se(streq("/usr", result));
-        result = mfree(result);
-
-        r = chase_symlinks("/usr", NULL, CHASE_STEP, &result, NULL);
-        assert_se(r > 0);
-        assert_se(streq("/usr", result));
-        result = mfree(result);
-
-        /* Make sure that symlinks in the "root" path are not resolved, but those below are */
-        p = strjoina("/etc/..", temp, "/self");
-        assert_se(symlink(".", p) >= 0);
-        q = strjoina(p, "/top/dot/dotdota");
-        r = chase_symlinks(q, p, 0, &result, NULL);
-        assert_se(r > 0);
-        assert_se(path_equal(path_startswith(result, p), "usr"));
-        result = mfree(result);
-
-        /* Test CHASE_PROHIBIT_SYMLINKS */
-
-        assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
-        assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-        assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
-        assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-        assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
-        assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
-
- cleanup:
-        assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
-}
-
-TEST(chase_symlinks_at) {
-        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
-        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
-        _cleanup_free_ char *result = NULL;
-        const char *p;
-
-        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
-
-        /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working
-         * directory. */
-
-        assert_se(symlinkat("/usr", tfd, "abc") >= 0);
-
-        p = strjoina(t, "/abc");
-        assert_se(chase_symlinks_at(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "/usr"));
-        result = mfree(result);
-
-        /* Test that absolute path or not are the same when resolving relative to a directory file
-         * descriptor and that we always get a relative path back. */
-
-        assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0);
-        fd = safe_close(fd);
-        assert_se(symlinkat("/def", tfd, "qed") >= 0);
-        assert_se(chase_symlinks_at(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "def"));
-        result = mfree(result);
-        assert_se(chase_symlinks_at(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "def"));
-        result = mfree(result);
-
-        /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against
-         * host's root. */
-        assert_se(chase_symlinks_at(tfd, "/qed", 0, NULL, NULL) == -ENOENT);
-
-        /* Test CHASE_PARENT */
-
-        assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
-        assert_se(symlinkat("/def", fd, "parent") >= 0);
-        fd = safe_close(fd);
-
-        /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
-         * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
-         * directory of the symlink itself. */
-
-        assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
-        assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
-        assert_se(streq(result, "def"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
-        assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
-        assert_se(streq(result, "chase/parent"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chase_symlinks_at(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
-        assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
-        assert_se(streq(result, "chase"));
-        fd = safe_close(fd);
-        result = mfree(result);
-
-        assert_se(chase_symlinks_at(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "."));
-        result = mfree(result);
-
-        assert_se(chase_symlinks_at(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
-        assert_se(streq(result, "."));
-        result = mfree(result);
-
-        /* Test CHASE_MKDIR_0755 */
-
-        assert_se(chase_symlinks_at(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
-        assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
-        assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
-        assert_se(streq(result, "m/k/d/i/r"));
-        result = mfree(result);
-
-        assert_se(chase_symlinks_at(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
-        assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
-        assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
-        assert_se(streq(result, "q"));
-        result = mfree(result);
-
-        assert_se(chase_symlinks_at(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT);
-
-        /* Test chase_symlinks_at_and_open() */
-
-        fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
-        assert_se(fd >= 0);
-        assert_se(fd_verify_regular(fd) >= 0);
-        fd = safe_close(fd);
-
-        fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
-        assert_se(fd >= 0);
-        assert_se(fd_verify_directory(fd) >= 0);
-        fd = safe_close(fd);
-}
-
 TEST(readlink_and_make_absolute) {
         const char *tempdir, *name, *name2, *name_alias;
         _cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL;
@@ -935,7 +432,7 @@ TEST(conservative_rename) {
         assert_se(access(q, F_OK) < 0 && errno == ENOENT);
 
         /* Check that a manual copy is detected */
-        assert_se(copy_file(p, q, 0, MODE_INVALID, 0, 0, COPY_REFLINK) >= 0);
+        assert_se(copy_file(p, q, 0, MODE_INVALID, COPY_REFLINK) >= 0);
         assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0);
         assert_se(access(q, F_OK) < 0 && errno == ENOENT);
 
@@ -1051,6 +548,23 @@ TEST(parse_cifs_service) {
 TEST(open_mkdir_at) {
         _cleanup_close_ int fd = -EBADF, subdir_fd = -EBADF, subsubdir_fd = -EBADF;
         _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        struct stat sta, stb;
+
+        assert_se(open_mkdir_at(AT_FDCWD, "/", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
+        assert_se(open_mkdir_at(AT_FDCWD, ".", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
+
+        fd = open_mkdir_at(AT_FDCWD, "/", O_CLOEXEC, 0);
+        assert_se(fd >= 0);
+        assert_se(stat("/", &sta) >= 0);
+        assert_se(fstat(fd, &stb) >= 0);
+        assert_se(stat_inode_same(&sta, &stb));
+        fd = safe_close(fd);
+
+        fd = open_mkdir_at(AT_FDCWD, ".", O_CLOEXEC, 0);
+        assert_se(stat(".", &sta) >= 0);
+        assert_se(fstat(fd, &stb) >= 0);
+        assert_se(stat_inode_same(&sta, &stb));
+        fd = safe_close(fd);
 
         assert_se(open_mkdir_at(AT_FDCWD, "/proc", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
 
@@ -1159,8 +673,8 @@ TEST(openat_report_new) {
 }
 
 TEST(xopenat) {
-        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
         _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF;
 
         assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
 
@@ -1181,6 +695,61 @@ TEST(xopenat) {
         assert_se((fd = xopenat(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0644)) >= 0);
         assert_se(fd_verify_regular(fd) >= 0);
         fd = safe_close(fd);
+
+        /* Test that we can reopen an existing fd with xopenat() by specifying an empty path. */
+
+        assert_se((fd = xopenat(tfd, "def", O_PATH|O_CLOEXEC, 0)) >= 0);
+        assert_se((fd2 = xopenat(fd, "", O_RDWR|O_CLOEXEC, 0644)) >= 0);
+}
+
+TEST(xopenat_lock) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+        siginfo_t si;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+        /* Test that we can acquire an exclusive lock on a directory in one process, remove the directory,
+         * and close the file descriptor and still properly create the directory and acquire the lock in
+         * another process.  */
+
+        fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX);
+        assert_se(fd >= 0);
+        assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0);
+        assert_se(fd_verify_directory(fd) >= 0);
+        assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN);
+
+        pid_t pid = fork();
+        assert_se(pid >= 0);
+
+        if (pid == 0) {
+                safe_close(fd);
+
+                fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX);
+                assert_se(fd >= 0);
+                assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0);
+                assert_se(fd_verify_directory(fd) >= 0);
+                assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        /* We need to give the child process some time to get past the xopenat() call in xopenat_lock() and
+         * block in the call to lock_generic() waiting for the lock to become free. We can't modify
+         * xopenat_lock() to signal an eventfd to let us know when that has happened, so we just sleep for a
+         * little and assume that's enough time for the child process to get along far enough. It doesn't
+         * matter if it doesn't get far enough, in that case we just won't trigger the fallback logic in
+         * xopenat_lock(), but the test will still succeed. */
+        assert_se(usleep(20 * USEC_PER_MSEC) >= 0);
+
+        assert_se(unlinkat(tfd, "abc", AT_REMOVEDIR) >= 0);
+        fd = safe_close(fd);
+
+        assert_se(wait_for_terminate(pid, &si) >= 0);
+        assert_se(si.si_code == CLD_EXITED);
+
+        assert_se(xopenat_lock(tfd, "abc", 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF);
+        assert_se(xopenat_lock(tfd, "def", O_DIRECTORY, 0755, LOCK_POSIX, LOCK_EX) == -EBADF);
 }
 
 static int intro(void) {
index fea20d82a6783245155b94252aadfd57361438ca..d5e5dcaa6805914f23a9255b42b494719cc191a1 100644 (file)
@@ -11,8 +11,9 @@
 #include "tests.h"
 
 TEST(hashmap_replace) {
-        Hashmap *m;
-        char *val1, *val2, *val3, *val4, *val5, *r;
+        _cleanup_(hashmap_freep) Hashmap *m = NULL;
+        _cleanup_free_ char *val1 = NULL, *val2 = NULL, *val3 = NULL, *val4 = NULL, *val5 = NULL;
+        char *r;
 
         m = hashmap_new(&string_hash_ops);
 
@@ -39,17 +40,11 @@ TEST(hashmap_replace) {
         hashmap_replace(m, "key 5", val5);
         r = hashmap_get(m, "key 5");
         assert_se(streq(r, "val5"));
-
-        free(val1);
-        free(val2);
-        free(val3);
-        free(val4);
-        free(val5);
-        hashmap_free(m);
 }
 
 TEST(hashmap_copy) {
-        Hashmap *m, *copy;
+        _cleanup_(hashmap_freep) Hashmap *m = NULL;
+        _cleanup_(hashmap_free_freep) Hashmap *copy = NULL;
         char *val1, *val2, *val3, *val4, *r;
 
         val1 = strdup("val1");
@@ -78,14 +73,11 @@ TEST(hashmap_copy) {
         assert_se(streq(r, "val3"));
         r = hashmap_get(copy, "key 4");
         assert_se(streq(r, "val4"));
-
-        hashmap_free_free(copy);
-        hashmap_free(m);
 }
 
 TEST(hashmap_get_strv) {
-        Hashmap *m;
-        char **strv;
+        _cleanup_(hashmap_freep) Hashmap *m = NULL;
+        _cleanup_(strv_freep) char **strv = NULL;
         char *val1, *val2, *val3, *val4;
 
         val1 = strdup("val1");
@@ -114,14 +106,10 @@ TEST(hashmap_get_strv) {
         assert_se(streq(strv[1], "val2"));
         assert_se(streq(strv[2], "val3"));
         assert_se(streq(strv[3], "val4"));
-
-        strv_free(strv);
-
-        hashmap_free(m);
 }
 
 TEST(hashmap_move_one) {
-        Hashmap *m, *n;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL, *n = NULL;
         char *val1, *val2, *val3, *val4, *r;
 
         val1 = strdup("val1");
@@ -154,13 +142,10 @@ TEST(hashmap_move_one) {
         assert_se(!r);
 
         assert_se(hashmap_move_one(n, m, "key 3") == -EEXIST);
-
-        hashmap_free_free(m);
-        hashmap_free_free(n);
 }
 
 TEST(hashmap_move) {
-        Hashmap *m, *n;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL, *n = NULL;
         char *val1, *val2, *val3, *val4, *r;
 
         val1 = strdup("val1");
@@ -196,14 +181,12 @@ TEST(hashmap_move) {
         assert_se(r && streq(r, "val3"));
         r = hashmap_get(n, "key 4");
         assert_se(r && streq(r, "val4"));
-
-        hashmap_free_free(m);
-        hashmap_free_free(n);
 }
 
 TEST(hashmap_update) {
-        Hashmap *m;
-        char *val1, *val2, *r;
+        _cleanup_(hashmap_freep) Hashmap *m = NULL;
+        _cleanup_free_ char *val1 = NULL, *val2 = NULL;
+        char *r;
 
         m = hashmap_new(&string_hash_ops);
         val1 = strdup("old_value");
@@ -222,14 +205,10 @@ TEST(hashmap_update) {
         assert_se(hashmap_update(m, "key 1", val2) == 0);
         r = hashmap_get(m, "key 1");
         assert_se(streq(r, "new_value"));
-
-        free(val1);
-        free(val2);
-        hashmap_free(m);
 }
 
 TEST(hashmap_put) {
-        Hashmap *m = NULL;
+        _cleanup_(hashmap_freep) Hashmap *m = NULL;
         int valid_hashmap_put;
         void *val1 = (void*) "val 1";
         void *val2 = (void*) "val 2";
@@ -245,8 +224,6 @@ TEST(hashmap_put) {
         key1 = strdup("key 1");
         assert_se(hashmap_put(m, key1, val1) == 0);
         assert_se(hashmap_put(m, key1, val2) == -EEXIST);
-
-        hashmap_free(m);
 }
 
 TEST(hashmap_remove1) {
@@ -440,7 +417,7 @@ TEST(hashmap_ensure_allocated) {
 }
 
 TEST(hashmap_foreach_key) {
-        Hashmap *m;
+        _cleanup_(hashmap_freep) Hashmap *m = NULL;
         bool key_found[] = { false, false, false, false };
         const char *s;
         const char *key;
@@ -469,12 +446,10 @@ TEST(hashmap_foreach_key) {
 
         assert_se(m);
         assert_se(key_found[0] && key_found[1] && key_found[2] && !key_found[3]);
-
-        hashmap_free(m);
 }
 
 TEST(hashmap_foreach) {
-        Hashmap *m;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
         bool value_found[] = { false, false, false, false };
         char *val1, *val2, *val3, *val4, *s;
         unsigned count;
@@ -488,8 +463,6 @@ TEST(hashmap_foreach) {
         val4 = strdup("my val4");
         assert_se(val4);
 
-        m = NULL;
-
         count = 0;
         HASHMAP_FOREACH(s, m)
                 count++;
@@ -520,12 +493,11 @@ TEST(hashmap_foreach) {
 
         assert_se(m);
         assert_se(value_found[0] && value_found[1] && value_found[2] && value_found[3]);
-
-        hashmap_free_free(m);
 }
 
 TEST(hashmap_merge) {
-        Hashmap *m, *n;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
+        _cleanup_(hashmap_freep) Hashmap *n = NULL;
         char *val1, *val2, *val3, *val4, *r;
 
         val1 = strdup("my val1");
@@ -553,12 +525,10 @@ TEST(hashmap_merge) {
 
         assert_se(m);
         assert_se(n);
-        hashmap_free(n);
-        hashmap_free_free(m);
 }
 
 TEST(hashmap_contains) {
-        Hashmap *m;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
         char *val1;
 
         val1 = strdup("my val");
@@ -574,11 +544,10 @@ TEST(hashmap_contains) {
         assert_se(!hashmap_contains(NULL, "Key 1"));
 
         assert_se(m);
-        hashmap_free_free(m);
 }
 
 TEST(hashmap_isempty) {
-        Hashmap *m;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
         char *val1;
 
         val1 = strdup("my val");
@@ -591,11 +560,10 @@ TEST(hashmap_isempty) {
         assert_se(!hashmap_isempty(m));
 
         assert_se(m);
-        hashmap_free_free(m);
 }
 
 TEST(hashmap_size) {
-        Hashmap *m;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
         char *val1, *val2, *val3, *val4;
 
         val1 = strdup("my val");
@@ -620,11 +588,10 @@ TEST(hashmap_size) {
         assert_se(m);
         assert_se(hashmap_size(m) == 4);
         assert_se(hashmap_buckets(m) >= 4);
-        hashmap_free_free(m);
 }
 
 TEST(hashmap_get) {
-        Hashmap *m;
+        _cleanup_(hashmap_free_freep) Hashmap *m = NULL;
         char *r;
         char *val;
 
@@ -645,11 +612,10 @@ TEST(hashmap_get) {
         assert_se(r == NULL);
 
         assert_se(m);
-        hashmap_free_free(m);
 }
 
 TEST(hashmap_get2) {
-        Hashmap *m;
+        _cleanup_(hashmap_free_free_freep) Hashmap *m = NULL;
         char *r;
         char *val;
         char key_orig[] = "Key 1";
@@ -678,7 +644,6 @@ TEST(hashmap_get2) {
         assert_se(r == NULL);
 
         assert_se(m);
-        hashmap_free_free_free(m);
 }
 
 static void crippled_hashmap_func(const void *p, struct siphash *state) {
index 241b197f47e3733079121d1dfa69a462710e6731..94e5ece5602aa155d04d0f5a239f14f1c740d162 100644 (file)
@@ -4,13 +4,14 @@
 
 #include "alloc-util.h"
 #include "fileio.h"
+#include "fs-util.h"
 #include "hostname-setup.h"
 #include "string-util.h"
 #include "tests.h"
 #include "tmpfile-util.h"
 
 TEST(read_etc_hostname) {
-        char path[] = "/tmp/hostname.XXXXXX";
+        _cleanup_(unlink_tempfilep) char path[] = "/tmp/hostname.XXXXXX";
         char *hostname;
         int fd;
 
@@ -54,8 +55,6 @@ TEST(read_etc_hostname) {
         /* nonexisting file */
         assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT);
         assert_se(hostname == (char*) 0x1234);  /* does not touch argument on error */
-
-        unlink(path);
 }
 
 TEST(hostname_setup) {
index 0a97366b6daa37f930b60dd61c41116d8b3c3948..80f762bc8966a28528d57f7a3486b8af04cf7884 100644 (file)
@@ -11,6 +11,8 @@
 #include "fd-util.h"
 #include "id128-util.h"
 #include "macro.h"
+#include "path-util.h"
+#include "rm-rf.h"
 #include "string-util.h"
 #include "tests.h"
 #include "tmpfile-util.h"
@@ -215,4 +217,106 @@ TEST(benchmark_sd_id128_get_machine_app_specific) {
         log_info("%lf µs each\n", (double) q / iterations);
 }
 
+TEST(id128_at) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF;
+        _cleanup_free_ char *p = NULL;
+        sd_id128_t id, i;
+
+        tfd = mkdtemp_open(NULL, O_PATH, &t);
+        assert_se(tfd >= 0);
+        assert_se(mkdirat(tfd, "etc", 0755) >= 0);
+        assert_se(symlinkat("etc", tfd, "etc2") >= 0);
+        assert_se(symlinkat("machine-id", tfd, "etc/hoge-id") >= 0);
+
+        assert_se(sd_id128_randomize(&id) == 0);
+
+        assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) >= 0);
+        if (geteuid() == 0)
+                assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) >= 0);
+        else
+                assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) == -EACCES);
+        assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0);
+        assert_se(id128_write_at(tfd, "etc2/machine-id", ID128_FORMAT_PLAIN, id) >= 0);
+        assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0);
+        assert_se(id128_write_at(tfd, "etc/hoge-id", ID128_FORMAT_PLAIN, id) >= 0);
+        assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0);
+        assert_se(id128_write_at(tfd, "etc2/hoge-id", ID128_FORMAT_PLAIN, id) >= 0);
+
+        /* id128_read_at() */
+        i = SD_ID128_NULL; /* Not necessary in real code, but for testing that the id is really assigned. */
+        assert_se(id128_read_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read_at(tfd, "etc2/machine-id", ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read_at(tfd, "etc/hoge-id", ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read_at(tfd, "etc2/hoge-id", ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        /* id128_read() */
+        assert_se(p = path_join(t, "/etc/machine-id"));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        free(p);
+        assert_se(p = path_join(t, "/etc2/machine-id"));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        free(p);
+        assert_se(p = path_join(t, "/etc/hoge-id"));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        free(p);
+        assert_se(p = path_join(t, "/etc2/hoge-id"));
+
+        i = SD_ID128_NULL;
+        assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        /* id128_get_machine_at() */
+        i = SD_ID128_NULL;
+        assert_se(id128_get_machine_at(tfd, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+
+        /* id128_get_machine() */
+        i = SD_ID128_NULL;
+        assert_se(id128_get_machine(t, &i) >= 0);
+        assert_se(sd_id128_equal(id, i));
+}
+
+TEST(ID128_REFUSE_NULL) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF;
+        sd_id128_t id;
+
+        tfd = mkdtemp_open(NULL, O_PATH, &t);
+        assert_se(tfd >= 0);
+
+        assert_se(id128_write_at(tfd, "zero-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, (sd_id128_t) {}) == -ENOMEDIUM);
+        assert_se(unlinkat(tfd, "zero-id", 0) >= 0);
+        assert_se(id128_write_at(tfd, "zero-id", ID128_FORMAT_PLAIN, (sd_id128_t) {}) >= 0);
+
+        assert_se(sd_id128_randomize(&id) == 0);
+        assert_se(!sd_id128_equal(id, SD_ID128_NULL));
+        assert_se(id128_read_at(tfd, "zero-id", ID128_FORMAT_PLAIN, &id) >= 0);
+        assert_se(sd_id128_equal(id, SD_ID128_NULL));
+
+        assert_se(id128_read_at(tfd, "zero-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &id) == -ENOMEDIUM);
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c
new file mode 100644 (file)
index 0000000..4194170
--- /dev/null
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "image-policy.h"
+#include "pretty-print.h"
+#include "string-util.h"
+#include "tests.h"
+#include "pager.h"
+
+static void test_policy(const ImagePolicy *p, const char *name) {
+        _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL;
+        _cleanup_free_ ImagePolicy *parsed = NULL;
+
+        assert_se(image_policy_to_string(p, /* simplified= */ false, &as_string) >= 0);
+        assert_se(image_policy_to_string(p, /* simplified= */ true, &as_string_simplified) >= 0);
+
+        printf("%s%s", ansi_underline(), name);
+
+        if (!streq(as_string_simplified, name)) {
+                printf(" → %s", as_string_simplified);
+
+                if (!streq(as_string, as_string_simplified))
+                        printf(" (aka %s)", as_string);
+        }
+
+        printf("%s\n", ansi_normal());
+
+        assert_se(image_policy_from_string(as_string, &parsed) >= 0);
+        assert_se(image_policy_equal(p, parsed));
+        parsed = image_policy_free(parsed);
+
+        assert_se(image_policy_from_string(as_string_simplified, &parsed) >= 0);
+        assert_se(image_policy_equivalent(p, parsed));
+        parsed = image_policy_free(parsed);
+
+        for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+                _cleanup_free_ char *k = NULL;
+                PartitionPolicyFlags f;
+
+                f = image_policy_get(p, d);
+                if (f < 0) {
+                        f = image_policy_get_exhaustively(p, d);
+                        assert_se(f >= 0);
+                        assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0);
+
+                        printf("%s\t%s → n/a (exhaustively: %s)%s\n", ansi_grey(), partition_designator_to_string(d), k, ansi_normal());
+                } else {
+                        assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0);
+                        printf("\t%s → %s\n", partition_designator_to_string(d), k);
+                }
+        }
+
+        _cleanup_free_ char *w = NULL;
+        assert_se(partition_policy_flags_to_string(image_policy_default(p), /* simplified= */ true, &w) >= 0);
+        printf("\tdefault → %s\n", w);
+}
+
+static void test_policy_string(const char *t) {
+        _cleanup_free_ ImagePolicy *parsed = NULL;
+
+        assert_se(image_policy_from_string(t, &parsed) >= 0);
+        test_policy(parsed, t);
+}
+
+static void test_policy_equiv(const char *s, bool (*func)(const ImagePolicy *p)) {
+        _cleanup_(image_policy_freep) ImagePolicy *p = NULL;
+
+        assert_se(image_policy_from_string(s, &p) >= 0);
+
+        assert_se(func(p));
+        assert_se(func == image_policy_equiv_ignore || !image_policy_equiv_ignore(p));
+        assert_se(func == image_policy_equiv_allow || !image_policy_equiv_allow(p));
+        assert_se(func == image_policy_equiv_deny || !image_policy_equiv_deny(p));
+}
+
+TEST_RET(test_image_policy_to_string) {
+        test_policy(&image_policy_allow, "*");
+        test_policy(&image_policy_ignore, "-");
+        test_policy(&image_policy_deny, "~");
+        test_policy(&image_policy_sysext, "sysext");
+        test_policy(&image_policy_sysext_strict, "sysext-strict");
+        test_policy(&image_policy_container, "container");
+        test_policy(&image_policy_host, "host");
+        test_policy(&image_policy_service, "service");
+        test_policy(NULL, "null");
+
+        test_policy_string("");
+        test_policy_string("-");
+        test_policy_string("*");
+        test_policy_string("~");
+        test_policy_string("swap=open");
+        test_policy_string("swap=open:root=signed");
+        test_policy_string("swap=open:root=signed+read-only-on+growfs-off:=absent");
+        test_policy_string("=-");
+        test_policy_string("=");
+
+        test_policy_equiv("", image_policy_equiv_ignore);
+        test_policy_equiv("-", image_policy_equiv_ignore);
+        test_policy_equiv("*", image_policy_equiv_allow);
+        test_policy_equiv("~", image_policy_equiv_deny);
+        test_policy_equiv("=absent", image_policy_equiv_deny);
+        test_policy_equiv("=open", image_policy_equiv_allow);
+        test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow);
+        test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow);
+        test_policy_equiv("=ignore", image_policy_equiv_ignore);
+        test_policy_equiv("=absent+unused", image_policy_equiv_ignore);
+        test_policy_equiv("=unused+absent", image_policy_equiv_ignore);
+        test_policy_equiv("root=ignore:=ignore", image_policy_equiv_ignore);
+
+        assert_se(image_policy_from_string("pfft", NULL) == -EINVAL);
+        assert_se(image_policy_from_string("öäüß", NULL) == -EINVAL);
+        assert_se(image_policy_from_string(":", NULL) == -EINVAL);
+        assert_se(image_policy_from_string("a=", NULL) == -EBADSLT);
+        assert_se(image_policy_from_string("=a", NULL) == -EBADRQC);
+        assert_se(image_policy_from_string("==", NULL) == -EBADRQC);
+        assert_se(image_policy_from_string("root=verity:root=encrypted", NULL) == -ENOTUNIQ);
+        assert_se(image_policy_from_string("root=grbl", NULL) == -EBADRQC);
+        assert_se(image_policy_from_string("wowza=grbl", NULL) == -EBADSLT);
+
+        return 0;
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
index ea8015d33d32661e0eae1dc04a14954d2d9c77ef..55b8894ecc1651f686a31241067e0541067c952a 100644 (file)
@@ -578,10 +578,13 @@ TEST(preset_and_list) {
         UnitFileState state;
         bool got_yes = false, got_no = false;
         UnitFileList *fl;
-        Hashmap *h;
+        _cleanup_(hashmap_freep) Hashmap *h = NULL;
+
+        CLEANUP_ARRAY(changes, n_changes, install_changes_free);
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) == -ENOENT);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) == -ENOENT);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) == -ENOENT);
 
         p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
         assert_se(write_string_file(p,
@@ -593,13 +596,20 @@ TEST(preset_and_list) {
                                     "[Install]\n"
                                     "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
 
+        p = strjoina(root, "/usr/lib/systemd/system/preset-ignore.service");
+        assert_se(write_string_file(p,
+                                    "[Install]\n"
+                                    "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
+
         p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset");
         assert_se(write_string_file(p,
                                     "enable *-yes.*\n"
+                                    "ignore *-ignore.*\n"
                                     "disable *\n", WRITE_STRING_FILE_CREATE) >= 0);
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
         assert_se(n_changes == 1);
@@ -612,6 +622,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0);
         assert_se(n_changes == 1);
@@ -623,6 +634,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
         assert_se(n_changes == 0);
@@ -631,6 +643,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_preset_all(RUNTIME_SCOPE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
 
@@ -652,8 +665,9 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
-        assert_se(h = hashmap_new(&string_hash_ops));
+        assert_se(h = hashmap_new(&unit_file_list_hash_ops_free));
         assert_se(unit_file_get_list(RUNTIME_SCOPE_SYSTEM, root, h, NULL, NULL) >= 0);
 
         p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
@@ -673,9 +687,11 @@ TEST(preset_and_list) {
                         assert_se(IN_SET(fl->state, UNIT_FILE_DISABLED, UNIT_FILE_STATIC, UNIT_FILE_INDIRECT, UNIT_FILE_ALIAS));
         }
 
-        unit_file_list_free(h);
-
         assert_se(got_yes && got_no);
+
+        assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), &changes, &n_changes) >= 0);
+        assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
 }
 
 TEST(revert) {
index eb027950963b244149fe717d116e3639b4739ec1..9fba0a8ca88c7ce214bfab8b633bf2814db8c04a 100644 (file)
@@ -20,7 +20,7 @@ static void dump_changes(InstallChange *c, unsigned n) {
 }
 
 int main(int argc, char* argv[]) {
-        Hashmap *h;
+        _cleanup_(hashmap_freep) Hashmap *h = NULL;
         UnitFileList *p;
         int r;
         const char *const files[] = { "avahi-daemon.service", NULL };
@@ -31,7 +31,7 @@ int main(int argc, char* argv[]) {
 
         test_setup_logging(LOG_DEBUG);
 
-        h = hashmap_new(&string_hash_ops);
+        h = hashmap_new(&unit_file_list_hash_ops_free);
         r = unit_file_get_list(RUNTIME_SCOPE_SYSTEM, NULL, h, NULL, NULL);
         assert_se(r == 0);
 
@@ -48,8 +48,6 @@ int main(int argc, char* argv[]) {
                         unit_file_state_to_string(p->state));
         }
 
-        unit_file_list_free(h);
-
         log_info("/*** enable **/");
 
         r = unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes);
index 39fcdcd94a2351729e0f37afb192fcd30c6fcc96..708abb7026e2b4611ec90f81aab74943b3b33ea5 100644 (file)
@@ -41,10 +41,10 @@ STATIC_DESTRUCTOR_REGISTER(runtime_dir, rm_rf_physical_and_freep);
 
 TEST_RET(unit_file_get_set) {
         int r;
-        Hashmap *h;
+        _cleanup_(hashmap_freep) Hashmap *h = NULL;
         UnitFileList *p;
 
-        h = hashmap_new(&string_hash_ops);
+        h = hashmap_new(&unit_file_list_hash_ops_free);
         assert_se(h);
 
         r = unit_file_get_list(RUNTIME_SCOPE_SYSTEM, NULL, h, NULL, NULL);
@@ -59,8 +59,6 @@ TEST_RET(unit_file_get_set) {
         HASHMAP_FOREACH(p, h)
                 printf("%s = %s\n", p->path, unit_file_state_to_string(p->state));
 
-        unit_file_list_free(h);
-
         return 0;
 }
 
diff --git a/src/test/test-lock-util.c b/src/test/test-lock-util.c
new file mode 100644 (file)
index 0000000..a9a1b43
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "lock-util.h"
+#include "rm-rf.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+
+TEST(make_lock_file) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF;
+        _cleanup_(release_lock_file) LockFile lock1 = LOCK_FILE_INIT, lock2 = LOCK_FILE_INIT;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+        assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock1) >= 0);
+        assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0);
+        assert_se(make_lock_file_at(tfd, "lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY);
+        release_lock_file(&lock1);
+        assert_se(RET_NERRNO(faccessat(tfd, "lock", F_OK, 0)) == -ENOENT);
+        assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock2) >= 0);
+        release_lock_file(&lock2);
+        assert_se(make_lock_file_at(tfd, "lock", LOCK_SH, &lock1) >= 0);
+        assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0);
+        assert_se(make_lock_file_at(tfd, "lock", LOCK_SH, &lock2) >= 0);
+        release_lock_file(&lock1);
+        assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0);
+        release_lock_file(&lock2);
+
+        assert_se(fchdir(tfd) >= 0);
+        assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock1) >= 0);
+        assert_se(make_lock_file("lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
index 68b5cb5092c1fc74a0d309bd2d38ec0a2104f02f..e337a3c7df397395f5be9630e463ebeb5405face 100644 (file)
@@ -80,9 +80,10 @@ static void test_log_context(void) {
                 LOG_CONTEXT_PUSH_STRV(strv);
                 LOG_CONTEXT_PUSH_STRV(strv);
 
-                /* Test that the log context was set up correctly. */
-                assert_se(log_context_num_contexts() == 4);
-                assert_se(log_context_num_fields() == 6);
+                /* Test that the log context was set up correctly. The strv we pushed twice should only
+                 * result in one log context which is reused. */
+                assert_se(log_context_num_contexts() == 3);
+                assert_se(log_context_num_fields() == 4);
 
                 /* Test that everything still works with modifications to the log context. */
                 test_log_struct();
@@ -94,8 +95,8 @@ static void test_log_context(void) {
                         LOG_CONTEXT_PUSH_STRV(strv);
 
                         /* Check that our nested fields got added correctly. */
-                        assert_se(log_context_num_contexts() == 6);
-                        assert_se(log_context_num_fields() == 9);
+                        assert_se(log_context_num_contexts() == 4);
+                        assert_se(log_context_num_fields() == 5);
 
                         /* Test that everything still works in a nested block. */
                         test_log_struct();
@@ -104,18 +105,18 @@ static void test_log_context(void) {
                 }
 
                 /* Check that only the fields from the nested block got removed. */
-                assert_se(log_context_num_contexts() == 4);
-                assert_se(log_context_num_fields() == 6);
+                assert_se(log_context_num_contexts() == 3);
+                assert_se(log_context_num_fields() == 4);
         }
 
         assert_se(log_context_num_contexts() == 0);
         assert_se(log_context_num_fields() == 0);
 
         {
-                _cleanup_(log_context_freep) LogContext *ctx = NULL;
+                _cleanup_(log_context_unrefp) LogContext *ctx = NULL;
 
                 char **strv = STRV_MAKE("SIXTH=ijn", "SEVENTH=PRP");
-                assert_se(ctx = log_context_new(strv, /*owned=*/ false));
+                assert_se(ctx = log_context_new_strv(strv, /*owned=*/ false));
 
                 assert_se(log_context_num_contexts() == 1);
                 assert_se(log_context_num_fields() == 2);
@@ -146,6 +147,7 @@ static void test_log_context(void) {
                 assert_se(iovw);
                 assert_se(iovw_consume(iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1) == 0);
 
+                LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov));
                 LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov));
                 LOG_CONTEXT_CONSUME_IOV(iovw->iovec, iovw->count);
                 LOG_CONTEXT_PUSH("STU=vwx");
@@ -158,10 +160,47 @@ static void test_log_context(void) {
                 test_log_syntax();
         }
 
+        {
+                LOG_CONTEXT_PUSH_KEY_VALUE("ABC=", "QED");
+                LOG_CONTEXT_PUSH_KEY_VALUE("ABC=", "QED");
+                assert_se(log_context_num_contexts() == 1);
+                assert_se(log_context_num_fields() == 1);
+
+                test_log_struct();
+                test_long_lines();
+                test_log_syntax();
+        }
+
         assert_se(log_context_num_contexts() == 0);
         assert_se(log_context_num_fields() == 0);
 }
 
+static void test_log_prefix(void) {
+        {
+                LOG_SET_PREFIX("ABC");
+
+                test_log_struct();
+                test_long_lines();
+                test_log_syntax();
+
+                {
+                        LOG_SET_PREFIX("QED");
+
+                        test_log_struct();
+                        test_long_lines();
+                        test_log_syntax();
+                }
+
+                test_log_struct();
+                test_long_lines();
+                test_log_syntax();
+        }
+
+        test_log_struct();
+        test_long_lines();
+        test_log_syntax();
+}
+
 int main(int argc, char* argv[]) {
         test_file();
 
@@ -175,6 +214,7 @@ int main(int argc, char* argv[]) {
                 test_long_lines();
                 test_log_syntax();
                 test_log_context();
+                test_log_prefix();
         }
 
         return 0;
index b6818b422cf167d78371cb2f6932643d5240254a..b35fea9c27c7d87b4992d8eab0a50076e80d8b45 100644 (file)
@@ -71,4 +71,25 @@ TEST(log2i) {
         assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
 }
 
+TEST(popcount) {
+        uint16_t u16a = 0x0000;
+        uint16_t u16b = 0xFFFF;
+        uint32_t u32a = 0x00000010;
+        uint32_t u32b = 0xFFFFFFFF;
+        uint64_t u64a = 0x0000000000000010;
+        uint64_t u64b = 0x0100000000100010;
+
+        assert_se(popcount(u16a) == 0);
+        assert_se(popcount(u16b) == 16);
+        assert_se(popcount(u32a) == 1);
+        assert_se(popcount(u32b) == 32);
+        assert_se(popcount(u64a) == 1);
+        assert_se(popcount(u64b) == 3);
+
+        /* This would fail:
+         * error: ‘_Generic’ selector of type ‘int’ is not compatible with any association
+         * assert_se(popcount(0x10) == 1);
+         */
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);
index 97c2f66ac99f5fed4ef7ee2f242ecb7312190a82..d8f48798cb0dcb9cd1ccba0a3b1ce2d754ba4c17 100644 (file)
@@ -82,7 +82,7 @@ static void* thread_func(void *ptr) {
 
                 log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted);
 
-                r = dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected);
+                r = dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected);
                 if (r < 0)
                         log_error_errno(r, "Failed dissect loopback device %s: %m", loop->node);
                 assert_se(r >= 0);
@@ -213,7 +213,7 @@ static int run(int argc, char *argv[]) {
         sfdisk = NULL;
 
 #if HAVE_BLKID
-        assert_se(dissect_image_file(p, NULL, NULL, 0, &dissected) >= 0);
+        assert_se(dissect_image_file(p, NULL, NULL, NULL, 0, &dissected) >= 0);
         verify_dissected_image(dissected);
         dissected = dissected_image_unref(dissected);
 #endif
@@ -231,7 +231,7 @@ static int run(int argc, char *argv[]) {
         assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0);
 
 #if HAVE_BLKID
-        assert_se(dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
+        assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
         verify_dissected_image(dissected);
 
         FOREACH_STRING(fs, "vfat", "ext4") {
@@ -267,12 +267,12 @@ static int run(int argc, char *argv[]) {
 
         /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block
          * device. */
-        assert_se(dissect_loop_device(loop, NULL, NULL, 0, &dissected) >= 0);
+        assert_se(dissect_loop_device(loop, NULL, NULL, NULL, 0, &dissected) >= 0);
         verify_dissected_image_harder(dissected);
         dissected = dissected_image_unref(dissected);
 
         /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */
-        assert_se(dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
+        assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
         verify_dissected_image_harder(dissected);
 
         assert_se(mkdtemp_malloc(NULL, &mounted) >= 0);
index f5481b5d6bfcfe080e6d4ba25ea5c9dc89542ec5..669b07810040b081b7d09efc4e6bcad5ecfa0199 100644 (file)
@@ -366,6 +366,53 @@ TEST(fstype_can_umask) {
         assert_se(!fstype_can_umask("tmpfs"));
 }
 
+TEST(path_get_mnt_id_at_null) {
+        _cleanup_close_ int root_fd = -EBADF, run_fd = -EBADF;
+        int id1, id2;
+
+        assert_se(path_get_mnt_id_at(AT_FDCWD, "/run/", &id1) >= 0);
+        assert_se(id1 > 0);
+
+        assert_se(path_get_mnt_id_at(AT_FDCWD, "/run", &id2) >= 0);
+        assert_se(id1 == id2);
+        id2 = -1;
+
+        root_fd = open("/", O_DIRECTORY|O_CLOEXEC);
+        assert_se(root_fd >= 0);
+
+        assert_se(path_get_mnt_id_at(root_fd, "/run/", &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+
+        assert_se(path_get_mnt_id_at(root_fd, "/run", &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+
+        assert_se(path_get_mnt_id_at(root_fd, "run", &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+
+        assert_se(path_get_mnt_id_at(root_fd, "run/", &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+
+        run_fd = openat(root_fd, "run", O_DIRECTORY|O_CLOEXEC);
+        assert_se(run_fd >= 0);
+
+        id2 = -1;
+        assert_se(path_get_mnt_id_at(run_fd, "", &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+
+        assert_se(path_get_mnt_id_at(run_fd, NULL, &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+
+        assert_se(path_get_mnt_id_at(run_fd, ".", &id2) >= 0);
+        assert_se(id1 = id2);
+        id2 = -1;
+}
+
 static int intro(void) {
         /* let's move into our own mount namespace with all propagation from the host turned off, so
          * that /proc/self/mountinfo is static and constant for the whole time our test runs. */
index 72155127c1e90563378ee29a187d2cefe10f1c32..82be09dd6a6fe3db46f67d7a83eba8ffc38b753c 100644 (file)
@@ -176,6 +176,7 @@ TEST(protect_kernel_logs) {
                 assert_se(fd > 0);
 
                 r = setup_namespace(NULL,
+                                    NULL,
                                     NULL,
                                     NULL,
                                     &ns_info,
@@ -193,6 +194,7 @@ TEST(protect_kernel_logs) {
                                     NULL,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     0,
                                     NULL,
                                     0,
@@ -208,6 +210,7 @@ TEST(protect_kernel_logs) {
                                     NULL,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     NULL);
                 assert_se(r == 0);
 
index 7eb29d109d11ee28dc37a25a13543fe2de53bf6f..485069670b446516f7bc5eb9660591247f7da3c8 100644 (file)
@@ -77,6 +77,7 @@ int main(int argc, char *argv[]) {
                 log_info("Not chrooted");
 
         r = setup_namespace(root_directory,
+                            NULL,
                             NULL,
                             NULL,
                             &ns_info,
@@ -91,6 +92,7 @@ int main(int argc, char *argv[]) {
                             &(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
                             NULL,
                             0,
+                            NULL,
                             tmp_dir,
                             var_tmp_dir,
                             NULL,
@@ -110,6 +112,7 @@ int main(int argc, char *argv[]) {
                             NULL,
                             NULL,
                             NULL,
+                            NULL,
                             NULL);
         if (r < 0) {
                 log_error_errno(r, "Failed to set up namespace: %m");
index defecd3a51182ce83919eabddf04984d50fdac30..2fbcde41d6b489e038c967bee4d7e4ce80bda54b 100644 (file)
@@ -367,7 +367,9 @@ static int make_addresses(struct local_address **addresses) {
                                               .address.in = { htobe32(0x7F000002) } };
         addrs[n++] = (struct local_address) { .family = AF_INET6,
                                               .address.in6 = in6addr_loopback };
-        return 0;
+
+        *addresses = TAKE_PTR(addrs);
+        return n;
 }
 
 static int test_one_module(const char *dir,
index 70f1e8705d836c1d61a83c41dd90bdea57b3a156..95c25f1540cae65d731c560a3ed8aa9c89a11f53 100644 (file)
@@ -19,87 +19,66 @@ TEST(strv_split_nulstr) {
         assert_se(streq(l[3], "str3"));
 }
 
-TEST(strv_parse_nulstr) {
-        _cleanup_strv_free_ char **l = NULL;
-        const char nulstr[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx";
+#define strv_parse_nulstr_full_one(s, n, e0, e1)                        \
+        ({                                                              \
+                _cleanup_strv_free_ char **v0 = NULL, **v1 = NULL;      \
+                                                                        \
+                assert_se(v0 = strv_parse_nulstr_full(s, n, false));    \
+                assert_se(strv_equal(v0, e0));                          \
+                assert_se(v1 = strv_parse_nulstr_full(s, n, true));     \
+                assert_se(strv_equal(v1, e1));                          \
+        })
 
-        l = strv_parse_nulstr(nulstr, sizeof(nulstr)-1);
-        assert_se(l);
-        puts("Parse nulstr:");
-        strv_print(l);
-
-        assert_se(streq(l[0], "hoge"));
-        assert_se(streq(l[1], "hoge2"));
-        assert_se(streq(l[2], "hoge3"));
-        assert_se(streq(l[3], ""));
-        assert_se(streq(l[4], "hoge5"));
-        assert_se(streq(l[5], ""));
-        assert_se(streq(l[6], "xxx"));
-        strv_free(l);
-
-        l = strv_parse_nulstr((const char[0]) {}, 0);
-        assert_se(l);
-        assert_se(strv_isempty(l));
-        strv_free(l);
+TEST(strv_parse_nulstr_full) {
+        const char nulstr1[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx";
+        const char nulstr2[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx\0\0\0";
 
-        l = strv_parse_nulstr((const char[1]) { 0 }, 1);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(nulstr1, sizeof(nulstr1) - 1,
+                                   STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"),
+                                   STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"));
 
-        l = strv_parse_nulstr((const char[1]) { 'x' }, 1);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("x")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(nulstr2, sizeof(nulstr2) - 1,
+                                   STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx", "", ""),
+                                   STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"));
 
-        l = strv_parse_nulstr((const char[2]) { 0, 0 }, 2);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("", "")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[0]) {}), 0,
+                                   STRV_MAKE_EMPTY, STRV_MAKE_EMPTY);
 
-        l = strv_parse_nulstr((const char[2]) { 'x', 0 }, 2);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("x")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[1]) { 0 }), 1,
+                                   STRV_MAKE(""), STRV_MAKE_EMPTY);
 
-        l = strv_parse_nulstr((const char[3]) { 0, 0, 0 }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("", "", "")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[1]) { 'x' }), 1,
+                                   STRV_MAKE("x"), STRV_MAKE("x"));
 
-        l = strv_parse_nulstr((const char[3]) { 'x', 0, 0 }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("x", "")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[2]) { 0, 0 }), 2,
+                                   STRV_MAKE("", ""), STRV_MAKE_EMPTY);
 
-        l = strv_parse_nulstr((const char[3]) { 0, 'x', 0 }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("", "x")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[2]) { 'x', 0 }), 2,
+                                   STRV_MAKE("x"), STRV_MAKE("x"));
 
-        l = strv_parse_nulstr((const char[3]) { 0, 0, 'x' }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("", "", "x")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 0 }), 3,
+                                   STRV_MAKE("", "", ""), STRV_MAKE_EMPTY);
 
-        l = strv_parse_nulstr((const char[3]) { 'x', 'x', 0 }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("xx")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 0 }), 3,
+                                   STRV_MAKE("x", ""), STRV_MAKE("x"));
 
-        l = strv_parse_nulstr((const char[3]) { 0, 'x', 'x' }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("", "xx")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 0 }), 3,
+                                   STRV_MAKE("", "x"), STRV_MAKE("", "x"));
 
-        l = strv_parse_nulstr((const char[3]) { 'x', 0, 'x' }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("x", "x")));
-        strv_free(l);
+        strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 'x' }), 3,
+                                   STRV_MAKE("", "", "x"), STRV_MAKE("", "", "x"));
 
-        l = strv_parse_nulstr((const char[3]) { 'x', 'x', 'x' }, 3);
-        assert_se(l);
-        assert_se(strv_equal(l, STRV_MAKE("xxx")));
+        strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 0 }), 3,
+                                   STRV_MAKE("xx"), STRV_MAKE("xx"));
+
+        strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 'x' }), 3,
+                                   STRV_MAKE("", "xx"), STRV_MAKE("", "xx"));
+
+        strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 'x' }), 3,
+                                   STRV_MAKE("x", "x"), STRV_MAKE("x", "x"));
+
+        strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 'x' }), 3,
+                                   STRV_MAKE("xxx"), STRV_MAKE("xxx"));
 }
 
 static void test_strv_make_nulstr_one(char **l) {
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..e40ffea4d53e72726e70390c11421229c6981c3d 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. */
 
@@ -632,9 +676,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 +692,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 +722,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 +738,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 +780,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 1d54066ae6600f2127907a131383df1b67a7b953..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;
@@ -26,15 +29,21 @@ 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);
 
         /* First test if the overrides for /proc/cmdline still work */
-        _cleanup_free_ char *line = NULL, *value = NULL;
         assert_se(proc_cmdline(&line) >= 0);
+        assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
+        line = mfree(line);
+        assert_se(proc_cmdline_strv(&args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("foo_bar=quux", "wuff-piep=tuet", "zumm", "some_arg_with_space=foo bar", "and_one_more=zzz aaa")));
+        args = strv_free(args);
 
         /* Test if parsing makes uses of the override */
-        assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
         assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
         value = mfree(value);
 
@@ -44,10 +53,16 @@ TEST(proc_cmdline_override) {
         assert_se(proc_cmdline_get_key("and_one_more", 0, &value) > 0 && streq_ptr(value, "zzz aaa"));
         value = mfree(value);
 
-        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=") == 0);
-        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
+        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=hoge") == 0);
+        assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0);
+
+        assert_se(proc_cmdline(&line) >= 0);
+        assert_se(streq(line, "hoge"));
+        line = mfree(line);
+        assert_se(proc_cmdline_strv(&args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("hoge")));
+        args = strv_free(args);
 
-        assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\""));
         assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
         value = mfree(value);
 
@@ -90,17 +105,17 @@ static void test_proc_cmdline_given_one(bool flip_initrd) {
                 in_initrd_force(!in_initrd());
 
         bool t = true, f = false;
-        assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"",
-                                           parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
-
-        assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"",
-                                           parse_item_given, &f, 0) >= 0);
+        assert_se(proc_cmdline_parse(parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
+        assert_se(proc_cmdline_parse(parse_item_given, &f, 0) >= 0);
 
         if (flip_initrd)
                 in_initrd_force(!in_initrd());
 }
 
 TEST(test_proc_cmdline_given) {
+        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"") == 0);
+        assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=miepf=\"uuu\"") == 0);
+
         test_proc_cmdline_given_one(false);
         /* Repeat the same thing, but now flip our ininitrdness */
         test_proc_cmdline_given_one(true);
@@ -109,7 +124,7 @@ TEST(test_proc_cmdline_given) {
 TEST(proc_cmdline_get_key) {
         _cleanup_free_ char *value = NULL;
 
-        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm spaaace='ö ü ß' ticks=\"''\"\n\nkkk=uuu\n\n\n") == 0);
+        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm-ghh spaaace='ö ü ß' ticks=\"''\"\n\nkkk=uuu\n\n\n") == 0);
 
         assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL);
         assert_se(proc_cmdline_get_key("abc", 0, NULL) == 0);
@@ -120,6 +135,7 @@ TEST(proc_cmdline_get_key) {
         value = mfree(value);
         assert_se(proc_cmdline_get_key("foo_bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux"));
         value = mfree(value);
+        assert_se(proc_cmdline_get_key("foo_bar", 0, NULL) == 0);
         assert_se(proc_cmdline_get_key("foo-bar", 0, &value) > 0 && streq_ptr(value, "quux"));
         value = mfree(value);
         assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux"));
@@ -138,9 +154,12 @@ TEST(proc_cmdline_get_key) {
         assert_se(proc_cmdline_get_key("wuff_piep", 0, NULL) == 0);
         assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL);
 
-        assert_se(proc_cmdline_get_key("zumm", 0, &value) == 0 && value == NULL);
-        assert_se(proc_cmdline_get_key("zumm", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL);
-        assert_se(proc_cmdline_get_key("zumm", 0, NULL) > 0);
+        assert_se(proc_cmdline_get_key("zumm-ghh", 0, &value) == 0 && value == NULL);
+        assert_se(proc_cmdline_get_key("zumm-ghh", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL);
+        assert_se(proc_cmdline_get_key("zumm-ghh", 0, NULL) > 0);
+        assert_se(proc_cmdline_get_key("zumm_ghh", 0, &value) == 0 && value == NULL);
+        assert_se(proc_cmdline_get_key("zumm_ghh", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL);
+        assert_se(proc_cmdline_get_key("zumm_ghh", 0, NULL) > 0);
 
         assert_se(proc_cmdline_get_key("spaaace", 0, &value) > 0 && streq_ptr(value, "ö ü ß"));
         value = mfree(value);
@@ -247,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 fc0d25ea2b7002e9b71f5078d89e1d7ae0adebf7..b90b8921926e3c15e06333454e8a8794c20caaeb 100644 (file)
@@ -112,7 +112,8 @@ TEST(get_process_comm) {
 }
 
 static void test_get_process_cmdline_one(pid_t pid) {
-        _cleanup_free_ char *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL, *h = NULL;
+        _cleanup_free_ char *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL, *h = NULL, *joined = NULL;
+        _cleanup_strv_free_ char **strv_a = NULL, **strv_b = NULL;
         int r;
 
         r = get_process_cmdline(pid, SIZE_MAX, 0, &c);
@@ -132,6 +133,18 @@ static void test_get_process_cmdline_one(pid_t pid) {
 
         r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX | PROCESS_CMDLINE_COMM_FALLBACK, &h);
         log_info("      %s", r >= 0 ? h : errno_to_name(r));
+
+        r = get_process_cmdline_strv(pid, 0, &strv_a);
+        if (r >= 0)
+                assert_se(joined = strv_join(strv_a, "\", \""));
+        log_info("      \"%s\"", r >= 0 ? joined : errno_to_name(r));
+
+        joined = mfree(joined);
+
+        r = get_process_cmdline_strv(pid, PROCESS_CMDLINE_COMM_FALLBACK, &strv_b);
+        if (r >= 0)
+                assert_se(joined = strv_join(strv_b, "\", \""));
+        log_info("      \"%s\"", r >= 0 ? joined : errno_to_name(r));
 }
 
 TEST(get_process_cmdline) {
@@ -244,6 +257,7 @@ TEST(get_process_cmdline_harder) {
         char path[] = "/tmp/test-cmdlineXXXXXX";
         _cleanup_close_ int fd = -EBADF;
         _cleanup_free_ char *line = NULL;
+        _cleanup_strv_free_ char **args = NULL;
         pid_t pid;
         int r;
 
@@ -358,6 +372,10 @@ TEST(get_process_cmdline_harder) {
         assert_se(streq(line, "[testa]"));
         line = mfree(line);
 
+        assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("[testa]")));
+        args = strv_free(args);
+
         /* Test with multiple arguments that don't require quoting */
 
         assert_se(write(fd, "foo\0bar", 8) == 8);
@@ -371,6 +389,10 @@ TEST(get_process_cmdline_harder) {
         assert_se(streq(line, "foo bar"));
         line = mfree(line);
 
+        assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("foo", "bar")));
+        args = strv_free(args);
+
         assert_se(write(fd, "quux", 4) == 4);
         assert_se(get_process_cmdline(0, SIZE_MAX, 0, &line) >= 0);
         log_debug("'%s'", line);
@@ -457,6 +479,10 @@ TEST(get_process_cmdline_harder) {
         assert_se(streq(line, "foo bar quux"));
         line = mfree(line);
 
+        assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("foo", "bar", "quux")));
+        args = strv_free(args);
+
         assert_se(ftruncate(fd, 0) >= 0);
         assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0);
 
@@ -482,11 +508,17 @@ TEST(get_process_cmdline_harder) {
         assert_se(streq(line, "[aaaa bbbb …"));
         line = mfree(line);
 
+        assert_se(get_process_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0);
+        assert_se(strv_equal(args, STRV_MAKE("[aaaa bbbb cccc]")));
+        args = strv_free(args);
+
         /* Test with multiple arguments that do require quoting */
 
 #define CMDLINE1 "foo\0'bar'\0\"bar$\"\0x y z\0!``\0"
 #define EXPECT1  "foo \"'bar'\" \"\\\"bar\\$\\\"\" \"x y z\" \"!\\`\\`\""
-#define EXPECT1p  "foo $'\\'bar\\'' $'\"bar$\"' $'x y z' $'!``'"
+#define EXPECT1p "foo $'\\'bar\\'' $'\"bar$\"' $'x y z' $'!``'"
+#define EXPECT1v STRV_MAKE("foo", "'bar'", "\"bar$\"", "x y z", "!``")
+
         assert_se(lseek(fd, SEEK_SET, 0) == 0);
         assert_se(write(fd, CMDLINE1, sizeof CMDLINE1) == sizeof CMDLINE1);
         assert_se(ftruncate(fd, sizeof CMDLINE1) == 0);
@@ -503,9 +535,15 @@ TEST(get_process_cmdline_harder) {
         assert_se(streq(line, EXPECT1p));
         line = mfree(line);
 
+        assert_se(get_process_cmdline_strv(0, 0, &args) >= 0);
+        assert_se(strv_equal(args, EXPECT1v));
+        args = strv_free(args);
+
 #define CMDLINE2 "foo\0\1\2\3\0\0"
 #define EXPECT2  "foo \"\\001\\002\\003\""
-#define EXPECT2p  "foo $'\\001\\002\\003'"
+#define EXPECT2p "foo $'\\001\\002\\003'"
+#define EXPECT2v STRV_MAKE("foo", "\1\2\3")
+
         assert_se(lseek(fd, SEEK_SET, 0) == 0);
         assert_se(write(fd, CMDLINE2, sizeof CMDLINE2) == sizeof CMDLINE2);
         assert_se(ftruncate(fd, sizeof CMDLINE2) == 0);
@@ -522,6 +560,10 @@ TEST(get_process_cmdline_harder) {
         assert_se(streq(line, EXPECT2p));
         line = mfree(line);
 
+        assert_se(get_process_cmdline_strv(0, 0, &args) >= 0);
+        assert_se(strv_equal(args, EXPECT2v));
+        args = strv_free(args);
+
         safe_close(fd);
         _exit(EXIT_SUCCESS);
 }
index 1eb9a49077d261df6f8fd08db609058ee0426edc..6a8b7d823fb3bd11f2b9f1eadd78e66f31cddad0 100644 (file)
 #include "tmpfile-util.h"
 
 static void test_rm_rf_chmod_inner(void) {
-        _cleanup_free_ char *d = NULL;
-        const char *x, *y;
+        _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+        const char *a, *b, *x, *y;
+        struct stat st;
 
         assert_se(getuid() != 0);
 
-        assert_se(mkdtemp_malloc(NULL, &d) >= 0);
+        assert_se(mkdtemp_malloc("/tmp/test-rm-rf.XXXXXXX", &d) >= 0);
+        a = strjoina(d, "/a");
+        b = strjoina(a, "/b");
+        x = strjoina(d, "/x");
+        y = strjoina(x, "/y");
 
-        x = strjoina(d, "/d");
         assert_se(mkdir(x, 0700) >= 0);
-        y = strjoina(x, "/f");
         assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
 
         assert_se(chmod(y, 0400) >= 0);
         assert_se(chmod(x, 0500) >= 0);
         assert_se(chmod(d, 0500) >= 0);
 
-        assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT) == -EACCES);
+        assert_se(rm_rf(d, REMOVE_PHYSICAL) == -EACCES);
 
         assert_se(access(d, F_OK) >= 0);
         assert_se(access(x, F_OK) >= 0);
         assert_se(access(y, F_OK) >= 0);
 
-        assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT|REMOVE_CHMOD) >= 0);
+        assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD) >= 0);
+
+        assert_se(access(d, F_OK) >= 0);
+        assert_se(access(x, F_OK) < 0 && errno == ENOENT);
+        assert_se(access(y, F_OK) < 0 && errno == ENOENT);
+
+        assert_se(mkdir(a, 0700) >= 0);
+        assert_se(mkdir(b, 0700) >= 0);
+        assert_se(mkdir(x, 0700) >= 0);
+        assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
+
+        assert_se(chmod(b, 0000) >= 0);
+        assert_se(chmod(a, 0000) >= 0);
+        assert_se(chmod(y, 0000) >= 0);
+        assert_se(chmod(x, 0000) >= 0);
+        assert_se(chmod(d, 0500) >= 0);
+
+        assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_CHMOD_RESTORE|REMOVE_ONLY_DIRECTORIES) == -ENOTEMPTY);
+
+        assert_se(access(a, F_OK) < 0 && errno == ENOENT);
+        assert_se(access(d, F_OK) >= 0);
+        assert_se(stat(d, &st) >= 0 && (st.st_mode & 07777) == 0500);
+        assert_se(access(x, F_OK) >= 0);
+        assert_se(stat(x, &st) >= 0 && (st.st_mode & 07777) == 0000);
+        assert_se(chmod(x, 0700) >= 0);
+        assert_se(access(y, F_OK) >= 0);
+        assert_se(stat(y, &st) >= 0 && (st.st_mode & 07777) == 0000);
+
+        assert_se(chmod(y, 0000) >= 0);
+        assert_se(chmod(x, 0000) >= 0);
+        assert_se(chmod(d, 0000) >= 0);
+
+        assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_CHMOD_RESTORE) >= 0);
+
+        assert_se(stat(d, &st) >= 0 && (st.st_mode & 07777) == 0000);
+        assert_se(access(d, F_OK) >= 0);
+        assert_se(chmod(d, 0700) >= 0);
+        assert_se(access(x, F_OK) < 0 && errno == ENOENT);
+
+        assert_se(mkdir(x, 0700) >= 0);
+        assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
+
+        assert_se(chmod(y, 0000) >= 0);
+        assert_se(chmod(x, 0000) >= 0);
+        assert_se(chmod(d, 0000) >= 0);
+
+        assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_ROOT) >= 0);
 
-        errno = 0;
         assert_se(access(d, F_OK) < 0 && errno == ENOENT);
 }
 
diff --git a/src/test/test-secure-bits.c b/src/test/test-secure-bits.c
new file mode 100644 (file)
index 0000000..27e6a20
--- /dev/null
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "securebits-util.h"
+#include "strv.h"
+#include "tests.h"
+#include "unit-file.h"
+
+static const char * const string_bits[] = {
+        "keep-caps",
+        "keep-caps-locked",
+        "no-setuid-fixup",
+        "no-setuid-fixup-locked",
+        "noroot",
+        "noroot-locked",
+        NULL
+};
+
+TEST(secure_bits_basic) {
+        _cleanup_free_ char *joined = NULL, *str = NULL;
+        int r;
+
+        /* Check if converting each bit from string and back to string yields
+         * the same value */
+        STRV_FOREACH(bit, string_bits) {
+                _cleanup_free_ char *s = NULL;
+
+                r = secure_bits_from_string(*bit);
+                assert_se(r > 0);
+                assert_se(secure_bits_is_valid(r));
+                assert_se(secure_bits_to_string_alloc(r, &s) >= 0);
+                printf("%s = 0x%x = %s\n", *bit, (unsigned)r, s);
+                assert_se(streq(*bit, s));
+        }
+
+        /* Ditto, but with all bits at once */
+        joined = strv_join((char**)string_bits, " ");
+        assert_se(joined);
+        r = secure_bits_from_string(joined);
+        assert_se(r > 0);
+        assert_se(secure_bits_is_valid(r));
+        assert_se(secure_bits_to_string_alloc(r, &str) >= 0);
+        printf("%s = 0x%x = %s\n", joined, (unsigned)r, str);
+        assert_se(streq(joined, str));
+
+        str = mfree(str);
+
+        /* Empty string */
+        assert_se(secure_bits_from_string("") == 0);
+        assert_se(secure_bits_from_string("     ") == 0);
+
+        /* Only invalid entries */
+        assert_se(secure_bits_from_string("foo bar baz") == 0);
+
+        /* Empty secure bits */
+        assert_se(secure_bits_to_string_alloc(0, &str) >= 0);
+        assert_se(isempty(str));
+
+        str = mfree(str);
+
+        /* Bits to string with check */
+        assert_se(secure_bits_to_string_alloc_with_check(INT_MAX, &str) == -EINVAL);
+        assert_se(str == NULL);
+        assert_se(secure_bits_to_string_alloc_with_check(
+                                (1 << SECURE_KEEP_CAPS) | (1 << SECURE_KEEP_CAPS_LOCKED),
+                                &str) >= 0);
+        assert_se(streq(str, "keep-caps keep-caps-locked"));
+}
+
+TEST(secure_bits_mix) {
+        static struct sbit_table {
+                const char *input;
+                const char *expected;
+        } sbit_table[] = {
+                { "keep-caps keep-caps keep-caps",  "keep-caps" },
+                { "keep-caps noroot keep-caps",     "keep-caps noroot" },
+                { "noroot foo bar baz noroot",      "noroot" },
+                { "noroot \"foo\" \"bar keep-caps", "noroot" },
+                { "\"noroot foo\" bar keep-caps",   "keep-caps" },
+                {}
+        };
+
+        for (const struct sbit_table *s = sbit_table; s->input; s++) {
+                _cleanup_free_ char *str = NULL;
+                int r;
+
+                r = secure_bits_from_string(s->input);
+                assert_se(r > 0);
+                assert_se(secure_bits_is_valid(r));
+                assert_se(secure_bits_to_string_alloc(r, &str) >= 0);
+                printf("%s = 0x%x = %s\n", s->input, (unsigned)r, str);
+                assert_se(streq(s->expected, str));
+        }
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
index 304e3db6c924340d78e2bfbea1ac614b79e131d0..259ffa7ce20af293ce5846bbfabcdfa33fd8998d 100644 (file)
@@ -145,7 +145,8 @@ TEST(set_ensure_allocated) {
 }
 
 TEST(set_copy) {
-        Set *s, *copy;
+        _cleanup_(set_freep) Set *s = NULL;
+        _cleanup_(set_free_freep) Set *copy = NULL;
         char *key1, *key2, *key3, *key4;
 
         key1 = strdup("key1");
@@ -169,9 +170,6 @@ TEST(set_copy) {
         assert_se(copy);
 
         assert_se(set_equal(s, copy));
-
-        set_free(s);
-        set_free_free(copy);
 }
 
 TEST(set_ensure_put) {
index 55bd81e22fdb4966e2d7d6a760db78b23e82fba1..30b252ecd9da7bb31844d4403e1c6827bb777460 100644 (file)
 DISABLE_WARNING_TYPE_LIMITS;
 
 #define info_no_sign(t)                                                 \
-        printf("%s → %zu bits, %zu byte alignment\n", STRINGIFY(t),     \
+        printf("%s → %zu bits, %zu byte alignment\n", STRINGIFY(t),    \
                sizeof(t)*CHAR_BIT,                                      \
-               __alignof__(t))
+               alignof(t))
 
 #define info(t)                                                         \
-        printf("%s → %zu bits%s, %zu byte alignment\n", STRINGIFY(t),   \
+        printf("%s → %zu bits%s, %zu byte alignment\n", STRINGIFY(t),  \
                sizeof(t)*CHAR_BIT,                                      \
                strstr(STRINGIFY(t), "signed") ? "" :                    \
                (t)-1 < (t)0 ? ", signed" : ", unsigned",                \
-               __alignof__(t))
+               alignof(t))
 
 enum Enum {
         enum_value,
@@ -44,7 +44,7 @@ enum BigEnum2 {
 int main(void) {
         int (*function_pointer)(void);
 
-        info_no_sign(function_pointer);
+        info_no_sign(typeof(function_pointer));
         info_no_sign(void*);
         info(char*);
 
index 62986e8dc2e3186afdea94ca4ed2492c64230682..d2a7f922bdf67eab7ffcb8e5a64322f6fb4ff741 100644 (file)
@@ -1,5 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include "sd-id128.h"
+
 #include "alloc-util.h"
 #include "log.h"
 #include "specifier.h"
@@ -136,7 +138,7 @@ TEST(specifiers) {
                 xsprintf(spec, "%%%c", s->specifier);
 
                 r = specifier_printf(spec, SIZE_MAX, specifier_table, NULL, NULL, &resolved);
-                if (s->specifier == 'm' && IN_SET(r, -ENOENT, -ENOMEDIUM)) /* machine-id might be missing in build chroots */
+                if (s->specifier == 'm' && IN_SET(r, -EUNATCH, -ENOMEDIUM)) /* machine-id might be missing in build chroots */
                         continue;
                 assert_se(r >= 0);
 
@@ -144,6 +146,31 @@ TEST(specifiers) {
         }
 }
 
+/* Bunch of specifiers that are not part of the common lists */
+TEST(specifiers_assorted) {
+        const sd_id128_t id = SD_ID128_ALLF;
+        const uint64_t llu = UINT64_MAX;
+        const Specifier table[] = {
+                /* Used in src/partition/repart.c */
+                { 'a', specifier_uuid,      &id },
+                { 'b', specifier_uint64,    &llu },
+                {}
+        };
+
+        for (const Specifier *s = table; s->specifier; s++) {
+                char spec[3];
+                _cleanup_free_ char *resolved = NULL;
+                int r;
+
+                xsprintf(spec, "%%%c", s->specifier);
+
+                r = specifier_printf(spec, SIZE_MAX, table, NULL, NULL, &resolved);
+                assert_se(r >= 0);
+
+                log_info("%%%c → %s", s->specifier, resolved);
+        }
+}
+
 TEST(specifiers_missing_data_ok) {
         _cleanup_free_ char *resolved = NULL;
 
index 6b0f2d803371c3a8b50899135e0854216aa17a1f..f79b05a4d1b629075f3576fbfb0201077e148796 100644 (file)
@@ -44,8 +44,8 @@ TEST(null_or_empty_path_with_root) {
 
 TEST(files_same) {
         _cleanup_close_ int fd = -EBADF;
-        char name[] = "/tmp/test-files_same.XXXXXX";
-        char name_alias[] = "/tmp/test-files_same.alias";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-files_same.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name_alias[] = "/tmp/test-files_same.alias";
 
         fd = mkostemp_safe(name);
         assert_se(fd >= 0);
@@ -55,14 +55,11 @@ TEST(files_same) {
         assert_se(files_same(name, name, AT_SYMLINK_NOFOLLOW));
         assert_se(files_same(name, name_alias, 0));
         assert_se(!files_same(name, name_alias, AT_SYMLINK_NOFOLLOW));
-
-        unlink(name);
-        unlink(name_alias);
 }
 
 TEST(is_symlink) {
-        char name[] = "/tmp/test-is_symlink.XXXXXX";
-        char name_link[] = "/tmp/test-is_symlink.link";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-is_symlink.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name_link[] = "/tmp/test-is_symlink.link";
         _cleanup_close_ int fd = -EBADF;
 
         fd = mkostemp_safe(name);
@@ -72,9 +69,6 @@ TEST(is_symlink) {
         assert_se(is_symlink(name) == 0);
         assert_se(is_symlink(name_link) == 1);
         assert_se(is_symlink("/a/file/which/does/not/exist/i/guess") < 0);
-
-        unlink(name);
-        unlink(name_link);
 }
 
 TEST(path_is_fs_type) {
index b3ff7d65c1cf1ad853ba19b42686097886f66d40..65052c627d7c397fdbe4c3b4f65931ba76661f14 100644 (file)
@@ -1218,4 +1218,50 @@ TEST(make_cstring) {
         TEST_MAKE_CSTRING_ONE(test8, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL);
 }
 
+TEST(find_line_startswith) {
+        static const char text[] =
+                "foobar\n"
+                "this is a test\n"
+                "foobar: waldo\n"
+                "more\n"
+                "\n"
+                "piff\n"
+                "foobarfoobar\n"
+                "iff\n";
+        static const char emptystring[] = "";
+
+        assert_se(find_line_startswith(text, "") == text);
+        assert_se(find_line_startswith(text, "f") == text+1);
+        assert_se(find_line_startswith(text, "foobar") == text+6);
+        assert_se(!find_line_startswith(text, "foobarx"));
+        assert_se(!find_line_startswith(text, "oobar"));
+        assert_se(find_line_startswith(text, "t") == text + 8);
+        assert_se(find_line_startswith(text, "th") == text + 9);
+        assert_se(find_line_startswith(text, "this") == text + 11);
+        assert_se(find_line_startswith(text, "foobarf") == text + 54);
+        assert_se(find_line_startswith(text, "more\n") == text + 41);
+        assert_se(find_line_startswith(text, "\n") == text + 42);
+        assert_se(find_line_startswith(text, "iff") == text + 63);
+
+        assert_se(find_line_startswith(emptystring, "") == emptystring);
+        assert_se(!find_line_startswith(emptystring, "x"));
+}
+
+TEST(strstrafter) {
+        static const char buffer[] = "abcdefghijklmnopqrstuvwxyz";
+
+        assert_se(!strstrafter(NULL, NULL));
+        assert_se(!strstrafter("", NULL));
+        assert_se(!strstrafter(NULL, ""));
+        assert_se(streq_ptr(strstrafter("", ""), ""));
+
+        assert_se(strstrafter(buffer, "a") == buffer + 1);
+        assert_se(strstrafter(buffer, "") == buffer);
+        assert_se(strstrafter(buffer, "ab") == buffer + 2);
+        assert_se(strstrafter(buffer, "cde") == buffer + 5);
+        assert_se(strstrafter(buffer, "xyz") == strchr(buffer, 0));
+        assert_se(strstrafter(buffer, buffer) == strchr(buffer, 0));
+        assert_se(!strstrafter(buffer, "-"));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 0f08dd4615b3c0a497a84e38c95457afc87c7a8c..a39a2d812203f52dd268c7fbd4df496928b65f86 100644 (file)
@@ -994,4 +994,16 @@ TEST(strv_copy_n) {
         assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e")));
 }
 
+TEST(strv_find_first_field) {
+        char **haystack = STRV_MAKE("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
+
+        assert_se(strv_find_first_field(NULL, NULL) == NULL);
+        assert_se(strv_find_first_field(NULL, haystack) == NULL);
+        assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), NULL) == NULL);
+        assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), haystack) == NULL);
+        assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "a", "c"), haystack), "b"));
+        assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "c", "a"), haystack), "d"));
+        assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"));
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);
index 8d5b24e5025fc2c5bb3e188bc26018befec7d912..a2b7101573404f490c0e9eea325e24d876cdbcd7 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "alloc-util.h"
 #include "fd-util.h"
+#include "fs-util.h"
 #include "macro.h"
 #include "path-util.h"
 #include "strv.h"
@@ -40,7 +41,7 @@ TEST(read_one_char) {
         _cleanup_fclose_ FILE *file = NULL;
         char r;
         bool need_nl;
-        char name[] = "/tmp/test-read_one_char.XXXXXX";
+        _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-read_one_char.XXXXXX";
 
         assert_se(fmkostemp_safe(name, "r+", &file) == 0);
 
@@ -60,8 +61,6 @@ TEST(read_one_char) {
         assert_se(fputs("\n", file) >= 0);
         rewind(file);
         assert_se(read_one_char(file, &r, 1000000, &need_nl) < 0);
-
-        assert_se(unlink(name) >= 0);
 }
 
 TEST(getttyname_malloc) {
index a7b24674528a7e325005bcb60236679f912e654e..42701618048765e6a65760085cfc88d5960556dc 100644 (file)
@@ -448,6 +448,11 @@ static void test_format_timestamp_with_tz_one(const char *tz) {
 TEST(FORMAT_TIMESTAMP_with_tz) {
         _cleanup_strv_free_ char **timezones = NULL;
 
+        test_format_timestamp_with_tz_one("UTC");
+
+        if (!slow_tests_enabled())
+                return (void) log_tests_skipped("slow tests are disabled");
+
         assert_se(get_timezones(&timezones) >= 0);
         STRV_FOREACH(tz, timezones)
                 test_format_timestamp_with_tz_one(*tz);
@@ -896,6 +901,11 @@ static void test_parse_timestamp_with_tz_one(const char *tz) {
 TEST(parse_timestamp_with_tz) {
         _cleanup_strv_free_ char **timezones = NULL;
 
+        test_parse_timestamp_with_tz_one("UTC");
+
+        if (!slow_tests_enabled())
+                return (void) log_tests_skipped("slow tests are disabled");
+
         assert_se(get_timezones(&timezones) >= 0);
         STRV_FOREACH(tz, timezones)
                 test_parse_timestamp_with_tz_one(*tz);
index 20baa0f26134cc62734ff8db7d5b3710318ea11b..3e989251d861534f19ee2e712654068622a02e8f 100644 (file)
@@ -26,6 +26,53 @@ TEST(tpm2_mask_from_string) {
         test_tpm2_pcr_mask_from_string_one("0,2", 5, 0);
         test_tpm2_pcr_mask_from_string_one("0+2", 5, 0);
         test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL);
+        test_tpm2_pcr_mask_from_string_one("7+application-support", 8388736, 0);
+        test_tpm2_pcr_mask_from_string_one("8+boot-loader-code", 272, 0);
+        test_tpm2_pcr_mask_from_string_one("6+boot-loader-code,44", 0, -EINVAL);
+        test_tpm2_pcr_mask_from_string_one("7,shim-policy,4", 16528, 0);
+        test_tpm2_pcr_mask_from_string_one("sysexts,shim-policy+kernel-boot", 26624, 0);
+        test_tpm2_pcr_mask_from_string_one("sysexts,shim+kernel-boot", 0, -EINVAL);
+        test_tpm2_pcr_mask_from_string_one("sysexts+17+23", 8527872, 0);
+        test_tpm2_pcr_mask_from_string_one("debug+24", 16842752, 0);
+}
+
+TEST(pcr_index_from_string) {
+        assert_se(pcr_index_from_string("platform-code") == 0);
+        assert_se(pcr_index_from_string("0") == 0);
+        assert_se(pcr_index_from_string("platform-config") == 1);
+        assert_se(pcr_index_from_string("1") == 1);
+        assert_se(pcr_index_from_string("external-code") == 2);
+        assert_se(pcr_index_from_string("2") == 2);
+        assert_se(pcr_index_from_string("external-config") == 3);
+        assert_se(pcr_index_from_string("3") == 3);
+        assert_se(pcr_index_from_string("boot-loader-code") == 4);
+        assert_se(pcr_index_from_string("4") == 4);
+        assert_se(pcr_index_from_string("boot-loader-config") == 5);
+        assert_se(pcr_index_from_string("5") == 5);
+        assert_se(pcr_index_from_string("secure-boot-policy") == 7);
+        assert_se(pcr_index_from_string("7") == 7);
+        assert_se(pcr_index_from_string("kernel-initrd") == 9);
+        assert_se(pcr_index_from_string("9") == 9);
+        assert_se(pcr_index_from_string("ima") == 10);
+        assert_se(pcr_index_from_string("10") == 10);
+        assert_se(pcr_index_from_string("kernel-boot") == 11);
+        assert_se(pcr_index_from_string("11") == 11);
+        assert_se(pcr_index_from_string("kernel-config") == 12);
+        assert_se(pcr_index_from_string("12") == 12);
+        assert_se(pcr_index_from_string("sysexts") == 13);
+        assert_se(pcr_index_from_string("13") == 13);
+        assert_se(pcr_index_from_string("shim-policy") == 14);
+        assert_se(pcr_index_from_string("14") == 14);
+        assert_se(pcr_index_from_string("system-identity") == 15);
+        assert_se(pcr_index_from_string("15") == 15);
+        assert_se(pcr_index_from_string("debug") == 16);
+        assert_se(pcr_index_from_string("16") == 16);
+        assert_se(pcr_index_from_string("application-support") == 23);
+        assert_se(pcr_index_from_string("23") == 23);
+        assert_se(pcr_index_from_string("hello") == -EINVAL);
+        assert_se(pcr_index_from_string("8") == 8);
+        assert_se(pcr_index_from_string("44") == -EINVAL);
+        assert_se(pcr_index_from_string("-5") == -EINVAL);
 }
 
 TEST(tpm2_util_pbkdf2_hmac_sha256) {
@@ -409,6 +456,96 @@ TEST(tpml_pcr_selection_add_sub) {
                           expected2, expected2_count);
 }
 
+/* this test includes TPM2 specific data structures */
+TEST(tpm2_get_primary_template) {
+
+        /*
+         * Verify that if someone changes the template code, they know they're breaking things.
+         * Templates MUST be changed in a backwards compatible way.
+         *
+         */
+        static const TPM2B_PUBLIC templ[] = {
+                /* index 0 RSA old */
+                [0] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_RSA,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+                                .parameters.rsaDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .keyBits = 2048,
+                                },
+                        },
+                },
+                /* Index 1 ECC old */
+                [TPM2_SRK_TEMPLATE_ECC] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_ECC,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+                                .parameters.eccDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .curveID = TPM2_ECC_NIST_P256,
+                                        .kdf.scheme = TPM2_ALG_NULL,
+                                },
+                        },
+                },
+                /* index 2 RSA SRK */
+                [TPM2_SRK_TEMPLATE_NEW_STYLE] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_RSA,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+                                .parameters.rsaDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .keyBits = 2048,
+                                },
+                        },
+                },
+                /* Index 3 ECC SRK */
+                [TPM2_SRK_TEMPLATE_NEW_STYLE | TPM2_SRK_TEMPLATE_ECC] = {
+                        .publicArea = {
+                                .type = TPM2_ALG_ECC,
+                                .nameAlg = TPM2_ALG_SHA256,
+                                .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
+                                .parameters.eccDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .curveID = TPM2_ECC_NIST_P256,
+                                        .kdf.scheme = TPM2_ALG_NULL,
+                                },
+                        },
+                },
+        };
+
+        assert_cc(ELEMENTSOF(templ) == _TPM2_SRK_TEMPLATE_MAX + 1);
+
+        for (size_t i = 0; i < ELEMENTSOF(templ); i++) {
+                /* the index counter lines up with the flags and the expected template received */
+                const TPM2B_PUBLIC *got = tpm2_get_primary_template((Tpm2SRKTemplateFlags)i);
+                assert_se(memcmp(&templ[i], got, sizeof(*got)) == 0);
+        }
+}
+
 #endif /* HAVE_TPM2 */
 
 DEFINE_TEST_MAIN(LOG_DEBUG);
index e961887fecb20d0b49a18b91704dbb8ac594274f..679d235c78e055a7a43f9e84b556443956e90a71 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "sd-event.h"
 
+#include "data-fd-util.h"
 #include "fd-util.h"
 #include "json.h"
 #include "rm-rf.h"
@@ -48,6 +49,59 @@ static int method_something(Varlink *link, JsonVariant *parameters, VarlinkMetho
         return varlink_reply(link, ret);
 }
 
+static void test_fd(int fd, const void *buf, size_t n) {
+        char rbuf[n + 1];
+        ssize_t m;
+
+        m = read(fd, rbuf, n + 1);
+        assert_se(m >= 0);
+        assert_se(memcmp_nn(buf, n, rbuf, m) == 0);
+}
+
+static int method_passfd(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+        _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL;
+        JsonVariant *a;
+        int r;
+
+        a = json_variant_by_key(parameters, "fd");
+        if (!a)
+                return varlink_error(link, "io.test.BadParameters", NULL);
+
+        assert_se(streq_ptr(json_variant_string(a), "whoop"));
+
+        int xx = varlink_peek_fd(link, 0),
+                yy = varlink_peek_fd(link, 1),
+                zz = varlink_peek_fd(link, 2);
+
+        log_info("%i %i %i", xx, yy, zz);
+
+        assert_se(xx >= 0);
+        assert_se(yy >= 0);
+        assert_se(zz >= 0);
+
+        test_fd(xx, "foo", 3);
+        test_fd(yy, "bar", 3);
+        test_fd(zz, "quux", 4);
+
+        _cleanup_close_ int vv = acquire_data_fd("miau", 4, 0);
+        _cleanup_close_ int ww = acquire_data_fd("wuff", 4, 0);
+
+        assert_se(vv >= 0);
+        assert_se(ww >= 0);
+
+        r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("yo", JSON_BUILD_INTEGER(88))));
+        if (r < 0)
+                return r;
+
+        assert_se(varlink_push_fd(link, vv) == 0);
+        assert_se(varlink_push_fd(link, ww) == 1);
+
+        TAKE_FD(vv);
+        TAKE_FD(ww);
+
+        return varlink_reply(link, ret);
+}
+
 static int method_done(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
 
         if (++n_done == 2)
@@ -77,6 +131,8 @@ static int on_connect(VarlinkServer *s, Varlink *link, void *userdata) {
 
         assert_se(varlink_get_peer_uid(link, &uid) >= 0);
         assert_se(getuid() == uid);
+        assert_se(varlink_set_allow_fd_passing_input(link, true) >= 0);
+        assert_se(varlink_set_allow_fd_passing_output(link, true) >= 0);
 
         return 0;
 }
@@ -151,11 +207,36 @@ static void *thread(void *arg) {
 
         assert_se(varlink_connect_address(&c, arg) >= 0);
         assert_se(varlink_set_description(c, "thread-client") >= 0);
+        assert_se(varlink_set_allow_fd_passing_input(c, true) >= 0);
+        assert_se(varlink_set_allow_fd_passing_output(c, true) >= 0);
 
         assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0);
         assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99);
         assert_se(!e);
 
+        int fd1 = acquire_data_fd("foo", 3, 0);
+        int fd2 = acquire_data_fd("bar", 3, 0);
+        int fd3 = acquire_data_fd("quux", 4, 0);
+
+        assert_se(fd1 >= 0);
+        assert_se(fd2 >= 0);
+        assert_se(fd3 >= 0);
+
+        assert_se(varlink_push_fd(c, fd1) == 0);
+        assert_se(varlink_push_fd(c, fd2) == 1);
+        assert_se(varlink_push_fd(c, fd3) == 2);
+
+        assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0);
+
+        int fd4 = varlink_peek_fd(c, 0);
+        int fd5 = varlink_peek_fd(c, 1);
+
+        assert_se(fd4 >= 0);
+        assert_se(fd5 >= 0);
+
+        test_fd(fd4, "miau", 4);
+        test_fd(fd5, "wuff", 4);
+
         assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0);
         assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist"));
         assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND));
@@ -211,6 +292,7 @@ int main(int argc, char *argv[]) {
         assert_se(varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID) >= 0);
         assert_se(varlink_server_set_description(s, "our-server") >= 0);
 
+        assert_se(varlink_server_bind_method(s, "io.test.PassFD", method_passfd) >= 0);
         assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0);
         assert_se(varlink_server_bind_method(s, "io.test.Done", method_done) >= 0);
         assert_se(varlink_server_bind_connect(s, on_connect) >= 0);
index b8cc6f4ead6c61a01cd0a34751f6dc7cc255cddf..569389b9d484f71fd2e537f98c60833ebcdb9e9e 100644 (file)
@@ -411,7 +411,7 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re
                 .msg_name = &server_addr,
                 .msg_namelen = sizeof(server_addr),
         };
-        struct timespec *recv_time = NULL;
+        struct timespec *recv_time;
         triple_timestamp dts;
         ssize_t len;
         double origin, receive, trans, dest, delay, offset, root_distance;
@@ -446,7 +446,7 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re
                 return 0;
         }
 
-        recv_time = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_TIMESTAMPNS, struct timespec);
+        recv_time = CMSG_FIND_AND_COPY_DATA(&msghdr, SOL_SOCKET, SCM_TIMESTAMPNS, struct timespec);
         if (!recv_time)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Packet timestamp missing.");
 
index fe866f8ca1b503f544fd3a9a1dd614a61da50332..edf0dfb73d636f1842dba441a68d9dd0d9bea254 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "offline-passwd.h"
 #include "path-util.h"
@@ -13,7 +13,7 @@ static int open_passwd_file(const char *root, const char *fname, FILE **ret_file
         _cleanup_close_ int fd = -EBADF;
         _cleanup_fclose_ FILE *f = NULL;
 
-        fd = chase_symlinks_and_open(fname, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, &p);
+        fd = chase_and_open(fname, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, &p);
         if (fd < 0)
                 return fd;
 
index 73beff063b80ce12b921d917dded1375cef15c54..2eb6e5ea3361ed0aeccaa67c6fbda9f3639e74c9 100644 (file)
@@ -22,7 +22,7 @@
 #include "btrfs-util.h"
 #include "build.h"
 #include "capability-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "chattr-util.h"
 #include "conf-files.h"
 #include "constants.h"
@@ -206,6 +206,7 @@ static char **arg_exclude_prefixes = NULL;
 static char *arg_root = NULL;
 static char *arg_image = NULL;
 static char *arg_replace = NULL;
+static ImagePolicy *arg_image_policy = NULL;
 
 #define MAX_DEPTH 256
 
@@ -219,6 +220,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
 static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
         [CREATION_NORMAL]   = "Created",
@@ -754,6 +756,8 @@ static int dir_cleanup(
                                         r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path);
 
                 } else {
+                        _cleanup_close_ int fd = -EBADF;
+
                         /* Skip files for which the sticky bit is set. These are semantics we define, and are
                          * unknown elsewhere. See XDG_RUNTIME_DIR specification for details. */
                         if (sx.stx_mode & S_ISVTX) {
@@ -794,6 +798,14 @@ static int dir_cleanup(
                                            cutoff_nsec, sub_path, age_by_file, false))
                                 continue;
 
+                        fd = xopenat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, 0);
+                        if (fd < 0 && fd != -ENOENT)
+                                log_warning_errno(fd, "Opening file \"%s\" failed, ignoring: %m", sub_path);
+                        if (fd >= 0 && flock(fd, LOCK_EX|LOCK_NB) < 0 && errno == EAGAIN) {
+                                log_debug_errno(errno, "Couldn't acquire shared BSD lock on file \"%s\", skipping: %m", p);
+                                continue;
+                        }
+
                         log_debug("Removing \"%s\".", sub_path);
                         if (unlinkat(dirfd(d), de->d_name, 0) < 0)
                                 if (errno != ENOENT)
@@ -982,7 +994,7 @@ static int path_open_parent_safe(const char *path, bool allow_failure) {
                                       path,
                                       allow_failure ? ", ignoring" : "");
 
-        r = chase_symlinks(dn, arg_root, allow_failure ? CHASE_SAFE : CHASE_SAFE|CHASE_WARN, NULL, &fd);
+        r = chase(dn, arg_root, allow_failure ? CHASE_SAFE : CHASE_SAFE|CHASE_WARN, NULL, &fd);
         if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
                 return r;
         if (r < 0)
@@ -1007,7 +1019,7 @@ static int path_open_safe(const char *path) {
         if (!path_is_normalized(path))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to open invalid path '%s'.", path);
 
-        r = chase_symlinks(path, arg_root, CHASE_SAFE|CHASE_WARN|CHASE_NOFOLLOW, NULL, &fd);
+        r = chase(path, arg_root, CHASE_SAFE|CHASE_WARN|CHASE_NOFOLLOW, NULL, &fd);
         if (r == -ENOLINK)
                 return r; /* Unsafe symlink: already covered by CHASE_WARN */
         if (r < 0)
@@ -1824,7 +1836,7 @@ static int empty_directory(Item *i, const char *path, CreationMode creation) {
         assert(i);
         assert(i->type == EMPTY_DIRECTORY);
 
-        r = chase_symlinks(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
+        r = chase(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd);
         if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */
                 return r;
         if (r == -ENOENT) {
@@ -2789,7 +2801,7 @@ static int process_item(Item *i, OperationMask operation) {
                         path = _path;
         }
 
-        r = chase_symlinks(path, arg_root, CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_WARN, NULL, NULL);
+        r = chase(path, arg_root, CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_WARN, NULL, NULL);
         if (r == -EREMOTE) {
                 log_notice_errno(r, "Skipping %s", i->path); /* We log the configured path, to not confuse the user. */
                 return 0;
@@ -3625,8 +3637,7 @@ static int parse_line(
         if (!GREEDY_REALLOC(existing->items, existing->n_items + 1))
                 return log_oom();
 
-        existing->items[existing->n_items++] = i;
-        i = (struct Item) {};
+        existing->items[existing->n_items++] = TAKE_STRUCT(i);
 
         /* Sort item array, to enforce stable ordering of application */
         typesafe_qsort(existing->items, existing->n_items, item_compare);
@@ -3689,6 +3700,7 @@ static int help(void) {
                "  -E                        Ignore rules prefixed with /dev, /proc, /run, /sys\n"
                "     --root=PATH            Operate on an alternate filesystem root\n"
                "     --image=PATH           Operate on disk image as filesystem root\n"
+               "     --image-policy=POLICY  Specify disk image dissection policy\n"
                "     --replace=PATH         Treat arguments as replacement for PATH\n"
                "     --no-pager             Do not pipe output into a pager\n"
                "\nSee the %s for details.\n",
@@ -3714,6 +3726,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_EXCLUDE_PREFIX,
                 ARG_ROOT,
                 ARG_IMAGE,
+                ARG_IMAGE_POLICY,
                 ARG_REPLACE,
                 ARG_NO_PAGER,
         };
@@ -3731,6 +3744,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "exclude-prefix", required_argument,   NULL, ARG_EXCLUDE_PREFIX },
                 { "root",           required_argument,   NULL, ARG_ROOT           },
                 { "image",          required_argument,   NULL, ARG_IMAGE          },
+                { "image-policy",   required_argument,   NULL, ARG_IMAGE_POLICY   },
                 { "replace",        required_argument,   NULL, ARG_REPLACE        },
                 { "no-pager",       no_argument,         NULL, ARG_NO_PAGER       },
                 {}
@@ -3810,6 +3824,12 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_IMAGE_POLICY:
+                        r = parse_image_policy_argument(optarg, &arg_image_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_REPLACE:
                         if (!path_is_absolute(optarg) ||
                             !endswith(optarg, ".conf"))
@@ -4143,6 +4163,7 @@ static int run(int argc, char *argv[]) {
 
                 r = mount_image_privately_interactively(
                                 arg_image,
+                                arg_image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_VALIDATE_OS |
index 6a631e515cd5739dd32548a3fdfa5a226f926d90..0b1f0b7157df4fe6861746c0ad0b4cc96886a9fc 100644 (file)
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "build.h"
 #include "device-nodes.h"
 #include "fd-util.h"
 #include "log.h"
+#include "main-func.h"
 #include "memory-util.h"
 #include "udev-util.h"
+#include "unaligned.h"
 
 #define COMMAND_TIMEOUT_MSEC (30 * 1000)
 
+static bool arg_export = false;
+static const char *arg_device = NULL;
+
 static int disk_scsi_inquiry_command(
                 int fd,
                 void *buf,
@@ -55,45 +61,39 @@ static int disk_scsi_inquiry_command(
                 .din_xferp = (uintptr_t) buf,
                 .timeout = COMMAND_TIMEOUT_MSEC,
         };
-        int ret;
 
-        ret = ioctl(fd, SG_IO, &io_v4);
-        if (ret != 0) {
+        if (ioctl(fd, SG_IO, &io_v4) != 0) {
+                if (errno != EINVAL)
+                        return log_debug_errno(errno, "ioctl v4 failed: %m");
+
                 /* could be that the driver doesn't do version 4, try version 3 */
-                if (errno == EINVAL) {
-                        struct sg_io_hdr io_hdr = {
-                                .interface_id = 'S',
-                                .cmdp = (unsigned char*) cdb,
-                                .cmd_len = sizeof (cdb),
-                                .dxferp = buf,
-                                .dxfer_len = buf_len,
-                                .sbp = sense,
-                                .mx_sb_len = sizeof(sense),
-                                .dxfer_direction = SG_DXFER_FROM_DEV,
-                                .timeout = COMMAND_TIMEOUT_MSEC,
-                        };
-
-                        ret = ioctl(fd, SG_IO, &io_hdr);
-                        if (ret != 0)
-                                return ret;
-
-                        /* even if the ioctl succeeds, we need to check the return value */
-                        if (!(io_hdr.status == 0 &&
-                              io_hdr.host_status == 0 &&
-                              io_hdr.driver_status == 0)) {
-                                errno = EIO;
-                                return -1;
-                        }
-                } else
-                        return ret;
-        }
+                struct sg_io_hdr io_hdr = {
+                        .interface_id = 'S',
+                        .cmdp = (unsigned char*) cdb,
+                        .cmd_len = sizeof (cdb),
+                        .dxferp = buf,
+                        .dxfer_len = buf_len,
+                        .sbp = sense,
+                        .mx_sb_len = sizeof(sense),
+                        .dxfer_direction = SG_DXFER_FROM_DEV,
+                        .timeout = COMMAND_TIMEOUT_MSEC,
+                };
+
+                if (ioctl(fd, SG_IO, &io_hdr) != 0)
+                        return log_debug_errno(errno, "ioctl v3 failed: %m");
+
+                /* even if the ioctl succeeds, we need to check the return value */
+                if (io_hdr.status != 0 ||
+                    io_hdr.host_status != 0 ||
+                    io_hdr.driver_status != 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v3 failed");
 
-        /* even if the ioctl succeeds, we need to check the return value */
-        if (!(io_v4.device_status == 0 &&
-              io_v4.transport_status == 0 &&
-              io_v4.driver_status == 0)) {
-                errno = EIO;
-                return -1;
+        } else {
+                /* even if the ioctl succeeds, we need to check the return value */
+                if (io_v4.device_status != 0 ||
+                    io_v4.transport_status != 0 ||
+                    io_v4.driver_status != 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed");
         }
 
         return 0;
@@ -137,35 +137,30 @@ static int disk_identify_command(
                 .din_xferp = (uintptr_t) buf,
                 .timeout = COMMAND_TIMEOUT_MSEC,
         };
-        int ret;
 
-        ret = ioctl(fd, SG_IO, &io_v4);
-        if (ret != 0) {
-                /* could be that the driver doesn't do version 4, try version 3 */
-                if (errno == EINVAL) {
-                        struct sg_io_hdr io_hdr = {
-                                .interface_id = 'S',
-                                .cmdp = (unsigned char*) cdb,
-                                .cmd_len = sizeof (cdb),
-                                .dxferp = buf,
-                                .dxfer_len = buf_len,
-                                .sbp = sense,
-                                .mx_sb_len = sizeof (sense),
-                                .dxfer_direction = SG_DXFER_FROM_DEV,
-                                .timeout = COMMAND_TIMEOUT_MSEC,
-                        };
-
-                        ret = ioctl(fd, SG_IO, &io_hdr);
-                        if (ret != 0)
-                                return ret;
-                } else
-                        return ret;
-        }
+        if (ioctl(fd, SG_IO, &io_v4) != 0) {
+                if (errno != EINVAL)
+                        return log_debug_errno(errno, "ioctl v4 failed: %m");
 
-        if (!((sense[0] & 0x7f) == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c) &&
-                !((sense[0] & 0x7f) == 0x70 && sense[12] == 0x00 && sense[13] == 0x1d)) {
-                errno = EIO;
-                return -1;
+                /* could be that the driver doesn't do version 4, try version 3 */
+                struct sg_io_hdr io_hdr = {
+                        .interface_id = 'S',
+                        .cmdp = (unsigned char*) cdb,
+                        .cmd_len = sizeof (cdb),
+                        .dxferp = buf,
+                        .dxfer_len = buf_len,
+                        .sbp = sense,
+                        .mx_sb_len = sizeof (sense),
+                        .dxfer_direction = SG_DXFER_FROM_DEV,
+                        .timeout = COMMAND_TIMEOUT_MSEC,
+                };
+
+                if (ioctl(fd, SG_IO, &io_hdr) != 0)
+                        return log_debug_errno(errno, "ioctl v3 failed: %m");
+        } else {
+                if (!((sense[0] & 0x7f) == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c) &&
+                    !((sense[0] & 0x7f) == 0x70 && sense[12] == 0x00 && sense[13] == 0x1d))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed: %m");
         }
 
         return 0;
@@ -215,34 +210,29 @@ static int disk_identify_packet_device_command(
                 .din_xferp = (uintptr_t) buf,
                 .timeout = COMMAND_TIMEOUT_MSEC,
         };
-        int ret;
 
-        ret = ioctl(fd, SG_IO, &io_v4);
-        if (ret != 0) {
-                /* could be that the driver doesn't do version 4, try version 3 */
-                if (errno == EINVAL) {
-                        struct sg_io_hdr io_hdr = {
-                                .interface_id = 'S',
-                                .cmdp = (unsigned char*) cdb,
-                                .cmd_len = sizeof (cdb),
-                                .dxferp = buf,
-                                .dxfer_len = buf_len,
-                                .sbp = sense,
-                                .mx_sb_len = sizeof (sense),
-                                .dxfer_direction = SG_DXFER_FROM_DEV,
-                                .timeout = COMMAND_TIMEOUT_MSEC,
-                        };
-
-                        ret = ioctl(fd, SG_IO, &io_hdr);
-                        if (ret != 0)
-                                return ret;
-                } else
-                        return ret;
-        }
+        if (ioctl(fd, SG_IO, &io_v4) != 0) {
+                if (errno != EINVAL)
+                        return log_debug_errno(errno, "ioctl v4 failed: %m");
 
-        if (!((sense[0] & 0x7f) == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) {
-                errno = EIO;
-                return -1;
+                /* could be that the driver doesn't do version 4, try version 3 */
+                struct sg_io_hdr io_hdr = {
+                        .interface_id = 'S',
+                        .cmdp = (unsigned char*) cdb,
+                        .cmd_len = sizeof (cdb),
+                        .dxferp = buf,
+                        .dxfer_len = buf_len,
+                        .sbp = sense,
+                        .mx_sb_len = sizeof (sense),
+                        .dxfer_direction = SG_DXFER_FROM_DEV,
+                        .timeout = COMMAND_TIMEOUT_MSEC,
+                };
+
+                if (ioctl(fd, SG_IO, &io_hdr) != 0)
+                        return log_debug_errno(errno, "ioctl v3 failed: %m");
+        } else {
+                if ((sense[0] & 0x7f) != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed: %m");
         }
 
         return 0;
@@ -282,26 +272,25 @@ static void disk_identify_fixup_string(
                 uint8_t identify[512],
                 unsigned offset_words,
                 size_t len) {
+        assert(offset_words < 512/2);
         disk_identify_get_string(identify, offset_words,
                                  (char *) identify + offset_words * 2, len);
 }
 
-static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned offset_words) {
-        uint16_t *p;
-
-        p = (uint16_t *) identify;
-        p[offset_words] = le16toh (p[offset_words]);
+static void disk_identify_fixup_uint16(uint8_t identify[512], unsigned offset_words) {
+        assert(offset_words < 512/2);
+        unaligned_write_ne16(identify + offset_words * 2,
+                             unaligned_read_le16(identify + offset_words * 2));
 }
 
 /**
  * disk_identify:
  * @fd: File descriptor for the block device.
  * @out_identify: Return location for IDENTIFY data.
- * @out_is_packet_device: Return location for whether returned data is from an IDENTIFY PACKET DEVICE.
  *
  * Sends the IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command to the
  * device represented by @fd. If successful, then the result will be
- * copied into @out_identify and @out_is_packet_device.
+ * copied into @out_identify.
  *
  * This routine is based on code from libatasmart, LGPL v2.1.
  *
@@ -309,14 +298,9 @@ static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned offset_w
  * non-zero with errno set.
  */
 static int disk_identify(int fd,
-                         uint8_t out_identify[512],
-                         int *out_is_packet_device) {
-        int ret;
+                         uint8_t out_identify[512]) {
         uint8_t inquiry_buf[36];
-        int peripheral_device_type;
-        int all_nul_bytes;
-        int n;
-        int is_packet_device = 0;
+        int peripheral_device_type, r;
 
         /* init results */
         memzero(out_identify, 512);
@@ -342,110 +326,103 @@ static int disk_identify(int fd,
          * the original bug-fix and see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=556635
          * for the original bug-report.)
          */
-        ret = disk_scsi_inquiry_command (fd, inquiry_buf, sizeof (inquiry_buf));
-        if (ret != 0)
-                goto out;
+        r = disk_scsi_inquiry_command(fd, inquiry_buf, sizeof inquiry_buf);
+        if (r < 0)
+                return r;
 
         /* SPC-4, section 6.4.2: Standard INQUIRY data */
         peripheral_device_type = inquiry_buf[0] & 0x1f;
-        if (peripheral_device_type == 0x05)
-          {
-            is_packet_device = 1;
-            ret = disk_identify_packet_device_command(fd, out_identify, 512);
-            goto check_nul_bytes;
-          }
-        if (!IN_SET(peripheral_device_type, 0x00, 0x14)) {
-                ret = -1;
-                errno = EIO;
-                goto out;
-        }
+        if (peripheral_device_type == 0x05) {
+                r = disk_identify_packet_device_command(fd, out_identify, 512);
+                if (r < 0)
+                        return r;
 
-        /* OK, now issue the IDENTIFY DEVICE command */
-        ret = disk_identify_command(fd, out_identify, 512);
-        if (ret != 0)
-                goto out;
+        } else {
+                if (!IN_SET(peripheral_device_type, 0x00, 0x14))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Unsupported device type.");
+
+                /* OK, now issue the IDENTIFY DEVICE command */
+                r = disk_identify_command(fd, out_identify, 512);
+                if (r < 0)
+                        return r;
+        }
 
- check_nul_bytes:
          /* Check if IDENTIFY data is all NUL bytes - if so, bail */
-        all_nul_bytes = 1;
-        for (n = 0; n < 512; n++) {
+        bool all_nul_bytes = true;
+        for (size_t n = 0; n < 512; n++)
                 if (out_identify[n] != '\0') {
-                        all_nul_bytes = 0;
+                        all_nul_bytes = false;
                         break;
                 }
-        }
 
-        if (all_nul_bytes) {
-                ret = -1;
-                errno = EIO;
-                goto out;
-        }
+        if (all_nul_bytes)
+                return log_debug_errno(SYNTHETIC_ERRNO(EIO), "IDENTIFY data is all zeroes.");
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        static const struct option options[] = {
+                { "export",   no_argument, NULL, 'x' },
+                { "help",     no_argument, NULL, 'h' },
+                { "version",  no_argument, NULL, 'v' },
+                {}
+        };
+        int c;
+
+        while ((c = getopt_long(argc, argv, "xh", options, NULL)) >= 0)
+                switch (c) {
+                case 'x':
+                        arg_export = true;
+                        break;
+                case 'h':
+                        printf("%s [OPTIONS...] DEVICE\n\n"
+                               "  -x --export    Print values as environment keys\n"
+                               "  -h --help      Show this help text\n"
+                               "     --version   Show package version\n",
+                               program_invocation_short_name);
+                        return 0;
+                case 'v':
+                        return version();
+                case '?':
+                        return -EINVAL;
+                default:
+                        assert_not_reached();
+                }
 
-out:
-        if (out_is_packet_device)
-                *out_is_packet_device = is_packet_device;
-        return ret;
+        if (!argv[optind])
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "DEVICE argument missing.");
+
+        arg_device = argv[optind];
+        return 1;
 }
 
-int main(int argc, char *argv[]) {
+static int run(int argc, char *argv[]) {
         struct hd_driveid id;
         union {
                 uint8_t  byte[512];
                 uint16_t wyde[256];
         } identify;
-        char model[41];
-        char model_enc[256];
-        char serial[21];
-        char revision[9];
-        const char *node = NULL;
-        int export = 0;
+        char model[41], model_enc[256], serial[21], revision[9];
         _cleanup_close_ int fd = -EBADF;
         uint16_t word;
-        int is_packet_device = 0;
-        static const struct option options[] = {
-                { "export", no_argument, NULL, 'x' },
-                { "help", no_argument, NULL, 'h' },
-                {}
-        };
+        int r;
 
         log_set_target(LOG_TARGET_AUTO);
         udev_parse_config();
         log_parse_environment();
         log_open();
 
-        for (;;) {
-                int option;
-
-                option = getopt_long(argc, argv, "xh", options, NULL);
-                if (option == -1)
-                        break;
-
-                switch (option) {
-                case 'x':
-                        export = 1;
-                        break;
-                case 'h':
-                        printf("Usage: %s [--export] [--help] <device>\n"
-                               "  -x,--export    print values as environment keys\n"
-                               "  -h,--help      print this help text\n\n",
-                               program_invocation_short_name);
-                        return 0;
-                }
-        }
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
 
-        node = argv[optind];
-        if (!node) {
-                log_error("no node specified");
-                return 1;
-        }
+        fd = open(ASSERT_PTR(arg_device), O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
+        if (fd < 0)
+                return log_error_errno(errno, "Cannot open %s: %m", arg_device);
 
-        fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
-        if (fd < 0) {
-                log_error("unable to open '%s'", node);
-                return 1;
-        }
-
-        if (disk_identify(fd, identify.byte, &is_packet_device) == 0) {
+        if (disk_identify(fd, identify.byte) >= 0) {
                 /*
                  * fix up only the fields from the IDENTIFY data that we are going to
                  * use and copy it into the hd_driveid struct for convenience
@@ -475,10 +452,8 @@ int main(int argc, char *argv[]) {
                 memcpy(&id, identify.byte, sizeof id);
         } else {
                 /* If this fails, then try HDIO_GET_IDENTITY */
-                if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) {
-                        log_debug_errno(errno, "HDIO_GET_IDENTITY failed for '%s': %m", node);
-                        return 2;
-                }
+                if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0)
+                        return log_debug_errno(errno, "%s: HDIO_GET_IDENTITY failed: %m", arg_device);
         }
 
         memcpy(model, id.model, 40);
@@ -491,7 +466,7 @@ int main(int argc, char *argv[]) {
         udev_replace_whitespace((char *) id.fw_rev, revision, 8);
         udev_replace_chars(revision, NULL);
 
-        if (export) {
+        if (arg_export) {
                 /* Set this to convey the disk speaks the ATA protocol */
                 printf("ID_ATA=1\n");
 
@@ -649,3 +624,5 @@ int main(int argc, char *argv[]) {
 
         return 0;
 }
+
+DEFINE_MAIN_FUNCTION(run);
index ea420a961716a45bd53ff278a5d33664abf12f91..5b5fbd0109aa2b5d8215ca829c556bf67a3d5909 100644 (file)
@@ -10,6 +10,7 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
+#include "build.h"
 #include "fd-util.h"
 #include "main-func.h"
 #include "memory-util.h"
@@ -897,13 +898,13 @@ static void print_properties(const Context *c) {
 }
 
 static int help(void) {
-        printf("Usage: %s [options] <device>\n"
-               "  -l --lock-media    lock the media (to enable eject request events)\n"
-               "  -u --unlock-media  unlock the media\n"
-               "  -e --eject-media   eject the media\n"
-               "  -d --debug         print debug messages to stderr\n"
-               "  -h --help          print this help text\n"
-               "\n",
+        printf("%s [OPTIONS...] DEVICE\n\n"
+               "  -l --lock-media    Lock the media (to enable eject request events)\n"
+               "  -u --unlock-media  Unlock the media\n"
+               "  -e --eject-media   Eject the media\n"
+               "  -d --debug         Print debug messages to stderr\n"
+               "  -h --help          Show this help text\n"
+               "     --version       Show package version\n",
                program_invocation_short_name);
 
         return 0;
@@ -916,6 +917,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "eject-media",  no_argument, NULL, 'e' },
                 { "debug",        no_argument, NULL, 'd' },
                 { "help",         no_argument, NULL, 'h' },
+                { "version",      no_argument, NULL, 'v' },
                 {}
         };
         int c;
@@ -938,6 +940,10 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 case 'h':
                         return help();
+                case 'v':
+                        return version();
+                case '?':
+                        return -EINVAL;
                 default:
                         assert_not_reached();
                 }
index 1345289219e4dcdb8027ee387cdecfe779372308..37c098adc86a5b6184bb747aa9393efc4d7cd8d2 100644 (file)
 #include <getopt.h>
 
 #include "alloc-util.h"
+#include "build.h"
 #include "fileio.h"
 #include "main-func.h"
 #include "string-util.h"
 #include "udev-util.h"
 #include "unaligned.h"
-#include "version.h"
 
 #define SUPPORTED_SMBIOS_VER 0x030300
 
@@ -638,9 +638,10 @@ static int legacy_decode(const uint8_t *buf, const char *devmem, bool no_file_of
 }
 
 static int help(void) {
-        printf("Usage: %s [options]\n"
-               " -F,--from-dump FILE   read DMI information from a binary file\n"
-               " -h,--help             print this help text\n\n",
+        printf("%s [OPTIONS...]\n\n"
+               "  -F --from-dump FILE  Read DMI information from a binary file\n"
+               "  -h --help            Show this help text\n"
+               "     --version         Show package version\n",
                program_invocation_short_name);
         return 0;
 }
@@ -650,6 +651,7 @@ static int parse_argv(int argc, char * const *argv) {
                 { "from-dump", required_argument, NULL, 'F' },
                 { "version",   no_argument,       NULL, 'V' },
                 { "help",      no_argument,       NULL, 'h' },
+                { "version",   no_argument,       NULL, 'v' },
                 {}
         };
         int c;
@@ -660,12 +662,13 @@ static int parse_argv(int argc, char * const *argv) {
                         arg_source_file = optarg;
                         break;
                 case 'V':
-                        printf("%s\n", GIT_VERSION);
-                        return 0;
+                        return version();
                 case 'h':
                         return help();
                 case '?':
                         return -EINVAL;
+                case 'v':
+                        return version();
                 default:
                         assert_not_reached();
                 }
index f2fbc38003a697ae152837606893b33ed6f683b4..e01f37d04c4c372b35968e191d522bcc30de372c 100644 (file)
@@ -8,12 +8,14 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <linux/hid.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "build.h"
 #include "device-private.h"
 #include "device-util.h"
 #include "fd-util.h"
 #include "string-util.h"
 #include "udev-util.h"
 
+static const char *arg_device = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
+        static const struct option options[] = {
+                { "help",     no_argument, NULL, 'h' },
+                { "version",  no_argument, NULL, 'v' },
+                {}
+        };
+        int c;
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+                switch (c) {
+                case 'h':
+                        printf("%s [OPTIONS...] SYSFS_PATH\n\n"
+                               "  -h --help     Show this help text\n"
+                               "     --version  Show package version\n",
+                               program_invocation_short_name);
+                        return 0;
+                case 'v':
+                        return version();
+                case '?':
+                        return -EINVAL;
+                default:
+                        assert_not_reached();
+                }
+
+        if (argc > 2)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument.");
+
+        arg_device = argv[optind];
+        return 1;
+}
+
 static int run(int argc, char **argv) {
         _cleanup_(sd_device_unrefp) struct sd_device *device = NULL;
         _cleanup_free_ char *desc_path = NULL;
         _cleanup_close_ int fd = -EBADF;
-
         struct sd_device *hid_device;
         const char *sys_path;
         uint8_t desc[HID_MAX_DESCRIPTOR_SIZE];
         ssize_t desc_len;
-
         int r;
 
         log_set_target(LOG_TARGET_AUTO);
@@ -42,17 +75,18 @@ static int run(int argc, char **argv) {
         log_parse_environment();
         log_open();
 
-        if (argc > 2)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name);
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
 
-        if (argc == 1) {
-                r = device_new_from_strv(&device, environ);
+        if (arg_device) {
+                r = sd_device_new_from_syspath(&device, arg_device);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to get current device from environment: %m");
+                        return log_error_errno(r, "Failed to get device from syspath %s: %m", arg_device);
         } else {
-                r = sd_device_new_from_syspath(&device, argv[1]);
+                r = device_new_from_strv(&device, environ);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to get device from syspath: %m");
+                        return log_error_errno(r, "Failed to get current device from environment: %m");
         }
 
         r = sd_device_get_parent(device, &hid_device);
index 1d487b94eece33d48da7ba3b632cc3e2d1483827..aff176eb1bddcb701cd5f7806edf0a552f3d6249 100644 (file)
@@ -27,7 +27,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         fflush(f);
 
         assert_se(rules = udev_rules_new(RESOLVE_NAME_EARLY));
-        r = udev_rules_parse_file(rules, filename, NULL);
+        r = udev_rules_parse_file(rules, filename, /* extra_checks = */ false, NULL);
         log_info_errno(r, "Parsing %s: %m", filename);
         assert_se(r >= 0 ||             /* OK */
                   r == -ENOBUFS);       /* line length exceeded */
diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c
new file mode 100644 (file)
index 0000000..54b50b4
--- /dev/null
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "build.h"
+#include "cgroup-util.h"
+#include "conf-parser.h"
+#include "devnum-util.h"
+#include "device-util.h"
+#include "main-func.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "verbs.h"
+
+static char *arg_target_solution = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_target_solution, freep);
+
+static int parse_config(void) {
+        static const ConfigTableItem items[] = {
+                { "IOCost", "TargetSolution", config_parse_string, 0, &arg_target_solution },
+        };
+        return config_parse(
+                        NULL,
+                        "/etc/udev/iocost.conf",
+                        NULL,
+                        "IOCost\0",
+                        config_item_table_lookup,
+                        items,
+                        CONFIG_PARSE_WARN,
+                        NULL,
+                        NULL);
+}
+
+static int help(void) {
+        printf("%s [OPTIONS...]\n\n"
+               "Set up iocost model and qos solutions for block devices\n"
+               "\nCommands:\n"
+               "  apply <path> [SOLUTION]    Apply solution for the device if\n"
+               "                             found, do nothing otherwise\n"
+               "  query <path>               Query the known solution for\n"
+               "                             the device\n"
+               "\nOptions:\n"
+               "  -h --help                  Show this help\n"
+               "     --version               Show package version\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+        };
+
+        static const struct option options[] = {
+                { "help",      no_argument,       NULL, 'h'           },
+                { "version",   no_argument,       NULL, ARG_VERSION   },
+                {}
+        };
+
+        int c;
+
+        assert(argc >= 1);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+                switch (c) {
+
+                case 'h':
+                        return help();
+
+                case ARG_VERSION:
+                        return version();
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached();
+                }
+
+        return 1;
+}
+
+static int get_known_solutions(sd_device *device, char ***ret_solutions) {
+        _cleanup_free_ char **s = NULL;
+        const char *value;
+        int r;
+
+        assert(ret_solutions);
+
+        r = sd_device_get_property_value(device, "IOCOST_SOLUTIONS", &value);
+        if (r < 0)
+                return r;
+
+        s = strv_split(value, WHITESPACE);
+        if (!s)
+                return -ENOMEM;
+
+        *ret_solutions = TAKE_PTR(s);
+
+        return 0;
+}
+
+static int choose_solution(char **solutions, const char **ret_name) {
+        assert(ret_name);
+
+        if (strv_isempty(solutions))
+                return log_error_errno(
+                                SYNTHETIC_ERRNO(EINVAL), "IOCOST_SOLUTIONS exists in hwdb but is empty.");
+
+        if (arg_target_solution && strv_find(solutions, arg_target_solution)) {
+                *ret_name = arg_target_solution;
+                log_debug("Selected solution based on target solution: %s", *ret_name);
+        } else {
+                *ret_name = solutions[0];
+                log_debug("Selected first available solution: %s", *ret_name);
+        }
+
+        return 0;
+}
+
+static int query_named_solution(
+                sd_device *device,
+                const char *name,
+                const char **ret_model,
+                const char **ret_qos) {
+
+        _cleanup_strv_free_ char **solutions = NULL;
+        _cleanup_free_ char *upper_name = NULL, *qos_key = NULL, *model_key = NULL;
+        const char *qos = NULL, *model = NULL;
+        int r;
+
+        assert(ret_qos);
+        assert(ret_model);
+
+        /* If NULL is passed we query the default solution, which is the first one listed
+         * in the IOCOST_SOLUTIONS key or the one specified by the TargetSolution setting.
+         */
+        if (!name) {
+                r = get_known_solutions(device, &solutions);
+                if (r == -ENOENT)
+                        return log_device_debug_errno(device, r, "No entry found for device, skipping iocost logic.");
+                if (r < 0)
+                        return log_device_error_errno(device, r, "Failed to query solutions from device: %m");
+
+                r = choose_solution(solutions, &name);
+                if (r < 0)
+                        return r;
+        }
+
+        upper_name = strdup(name);
+        if (!upper_name)
+                return log_oom();
+
+        ascii_strupper(upper_name);
+        string_replace_char(upper_name, '-', '_');
+
+        qos_key = strjoin("IOCOST_QOS_", upper_name);
+        if (!qos_key)
+                return log_oom();
+
+        model_key = strjoin("IOCOST_MODEL_", upper_name);
+        if (!model_key)
+                return log_oom();
+
+        r = sd_device_get_property_value(device, qos_key, &qos);
+        if (r == -ENOENT)
+                return log_device_debug_errno(device, r, "No value found for key %s, skipping iocost logic.", qos_key);
+        if (r < 0)
+                return log_device_error_errno(device, r, "Failed to obtain model for iocost solution from device: %m");
+
+        r = sd_device_get_property_value(device, model_key, &model);
+        if (r == -ENOENT)
+                return log_device_debug_errno(device, r, "No value found for key %s, skipping iocost logic.", model_key);
+        if (r < 0)
+                return log_device_error_errno(device, r, "Failed to obtain model for iocost solution from device: %m");
+
+        *ret_qos = qos;
+        *ret_model = model;
+
+        return 0;
+}
+
+static int apply_solution_for_path(const char *path, const char *name) {
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        _cleanup_free_ char *qos = NULL, *model = NULL;
+        const char *qos_params = NULL, *model_params = NULL;
+        dev_t devnum;
+        int r;
+
+        r = sd_device_new_from_path(&device, path);
+        if (r < 0)
+                return log_error_errno(r, "Error looking up device: %m");
+
+        r = query_named_solution(device, name, &model_params, &qos_params);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return r;
+
+        r = sd_device_get_devnum(device, &devnum);
+        if (r < 0)
+                return log_device_error_errno(device, r, "Error getting devnum: %m");
+
+        if (asprintf(&qos, DEVNUM_FORMAT_STR " enable=1 ctrl=user %s", DEVNUM_FORMAT_VAL(devnum), qos_params) < 0)
+                return log_oom();
+
+        if (asprintf(&model, DEVNUM_FORMAT_STR " model=linear ctrl=user %s", DEVNUM_FORMAT_VAL(devnum), model_params) < 0)
+                return log_oom();
+
+        log_debug("Applying iocost parameters to %s using solution '%s'\n"
+                        "\tio.cost.qos: %s\n"
+                        "\tio.cost.model: %s\n", path, name ?: "default", qos, model);
+
+        r = cg_set_attribute("io", NULL, "io.cost.qos", qos);
+        if (r < 0) {
+                log_device_full_errno(device, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to set io.cost.qos: %m");
+                return r == -ENOENT ? 0 : r;
+        }
+
+        r = cg_set_attribute("io", NULL, "io.cost.model", model);
+        if (r < 0) {
+                log_device_full_errno(device, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to set io.cost.model: %m");
+                return r == -ENOENT ? 0 : r;
+        }
+
+        return 0;
+}
+
+static int query_solutions_for_path(const char *path) {
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        _cleanup_strv_free_ char **solutions = NULL;
+        const char *default_solution = NULL;
+        const char *model_name = NULL;
+        int r;
+
+        r = sd_device_new_from_path(&device, path);
+        if (r < 0)
+                return log_error_errno(r, "Error looking up device: %m");
+
+        r = sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &model_name);
+        if (r == -ENOENT) {
+                log_device_debug(device, "Missing ID_MODEL_FROM_DATABASE property, trying ID_MODEL");
+                r = sd_device_get_property_value(device, "ID_MODEL", &model_name);
+                if (r == -ENOENT) {
+                        log_device_info(device, "Device model not found");
+                        return 0;
+                }
+        }
+        if (r < 0)
+                return log_device_error_errno(device, r, "Model name for device %s is unknown", path);
+
+        r = get_known_solutions(device, &solutions);
+        if (r == -ENOENT) {
+                log_device_info(device, "Attribute IOCOST_SOLUTIONS missing, model not found in hwdb.");
+                return 0;
+        }
+        if (r < 0)
+                return log_device_error_errno(device, r, "Couldn't access IOCOST_SOLUTIONS for device %s, model name %s on hwdb: %m\n", path, model_name);
+
+        r = choose_solution(solutions, &default_solution);
+        if (r < 0)
+                return r;
+
+        log_info("Known solutions for %s model name: \"%s\"\n"
+                 "Preferred solution: %s\n"
+                 "Solution that would be applied: %s",
+                 path, model_name,
+                 arg_target_solution, default_solution);
+
+        STRV_FOREACH(s, solutions) {
+                const char *model = NULL, *qos = NULL;
+
+                r = query_named_solution(device, *s, &model, &qos);
+                if (r < 0 || !model || !qos)
+                        continue;
+
+                log_info("%s: io.cost.qos: %s\n"
+                         "%s: io.cost.model: %s", *s, qos, *s, model);
+        }
+
+        return 0;
+}
+
+static int verb_query(int argc, char *argv[], void *userdata) {
+        return query_solutions_for_path(ASSERT_PTR(argv[1]));
+}
+
+static int verb_apply(int argc, char *argv[], void *userdata) {
+        return apply_solution_for_path(
+                        ASSERT_PTR(argv[1]),
+                        argc > 2 ? ASSERT_PTR(argv[2]) : NULL);
+}
+
+static int iocost_main(int argc, char *argv[]) {
+        static const Verb verbs[] = {
+                { "query", 2, 2, 0, verb_query },
+                { "apply", 2, 3, 0, verb_apply },
+                {},
+        };
+
+        return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static int run(int argc, char *argv[]) {
+        int r;
+
+        log_setup();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        (void) parse_config();
+
+        if (!arg_target_solution) {
+                arg_target_solution = strdup("naive");
+                if (!arg_target_solution)
+                        return log_oom();
+        }
+
+        log_debug("Target solution: %s.", arg_target_solution);
+
+        return iocost_main(argc, argv);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/udev/iocost/iocost.conf b/src/udev/iocost/iocost.conf
new file mode 100644 (file)
index 0000000..394ea34
--- /dev/null
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it under the
+#  terms of the GNU Lesser General Public License as published by the Free
+#  Software Foundation; either version 2.1 of the License, or (at your option)
+#  any later version.
+#
+# Entries in this file show the compile time defaults. Local configuration
+# should be created by either modifying this file. Defaults can be restored by
+# simply deleting this file.
+#
+# Use 'systemd-analyze cat-config udev/iocost.conf' to display the full config.
+#
+# See iocost.conf(5) for details.
+
+[IOCost]
+#TargetSolution=naive
index af7dea0dce80630d3aa3b562f95b6e27d7a9bf4b..081948d223f59f92148c856c66a69ea4d2831a40 100644 (file)
@@ -116,6 +116,7 @@ udev_progs = [['ata_id/ata_id.c'],
               ['cdrom_id/cdrom_id.c'],
               ['fido_id/fido_id.c',
                'fido_id/fido_id_desc.c'],
+              ['iocost/iocost.c'],
               ['scsi_id/scsi_id.c',
                'scsi_id/scsi_serial.c'],
               ['v4l_id/v4l_id.c'],
@@ -143,11 +144,14 @@ foreach prog : udev_progs
                 install_dir : udevlibexecdir)
 
         udev_prog_paths += {name : exe}
+        public_programs += exe
 endforeach
 
 if install_sysconfdir_samples
         install_data('udev.conf',
                      install_dir : sysconfdir / 'udev')
+        install_data('iocost/iocost.conf',
+                     install_dir : sysconfdir / 'udev')
 endif
 
 custom_target(
index a7210a05e3cc4f28bb73fa22cc96aee1054d5c8e..1035320490af1420c0995e0c5b154ac59258b07f 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <mtd/mtd-user.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "build.h"
 #include "fd-util.h"
+#include "main-func.h"
 #include "mtd_probe.h"
 
-int main(int argc, char** argv) {
+static const char *arg_device = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
+        static const struct option options[] = {
+                { "help",     no_argument, NULL, 'h' },
+                { "version",  no_argument, NULL, 'v' },
+                {}
+        };
+        int c;
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+                switch (c) {
+                case 'h':
+                        printf("%s /dev/mtd[n]\n\n"
+                               "  -h --help     Show this help text\n"
+                               "     --version  Show package version\n",
+                               program_invocation_short_name);
+                        return 0;
+                case 'v':
+                        return version();
+                case '?':
+                        return -EINVAL;
+                default:
+                        assert_not_reached();
+                }
+
+        if (argc > 2)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument.");
+
+        arg_device = argv[optind];
+        return 1;
+}
+
+static int run(int argc, char** argv) {
         _cleanup_close_ int mtd_fd = -EBADF;
         mtd_info_t mtd_info;
+        int r;
 
-        if (argc != 2) {
-                printf("usage: mtd_probe /dev/mtd[n]\n");
-                return EXIT_FAILURE;
-        }
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
 
         mtd_fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY);
-        if (mtd_fd < 0) {
-                log_error_errno(errno, "Failed to open: %m");
-                return EXIT_FAILURE;
-        }
+        if (mtd_fd < 0)
+                return log_error_errno(errno, "Failed to open: %m");
 
-        if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) < 0) {
-                log_error_errno(errno, "Failed to issue MEMGETINFO ioctl: %m");
-                return EXIT_FAILURE;
-        }
+        if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) < 0)
+                return log_error_errno(errno, "MEMGETINFO ioctl failed: %m");
 
-        if (probe_smart_media(mtd_fd, &mtd_info) < 0)
-                return EXIT_FAILURE;
-
-        return EXIT_SUCCESS;
+        return probe_smart_media(mtd_fd, &mtd_info);
 }
+
+DEFINE_MAIN_FUNCTION(run);
index 364d5677051248f0f3b7d7cad89f65f1937d2417..6308c52b7ec33d790cbb713c6a025c94aeec5628 100644 (file)
@@ -16,6 +16,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "build.h"
 #include "device-nodes.h"
 #include "extract-word.h"
 #include "fd-util.h"
 #include "strv.h"
 #include "strxcpyx.h"
 #include "udev-util.h"
-#include "version.h"
 
 static const struct option options[] = {
         { "device",             required_argument, NULL, 'd' },
         { "config",             required_argument, NULL, 'f' },
         { "page",               required_argument, NULL, 'p' },
-        { "blacklisted",        no_argument,       NULL, 'b' },
-        { "whitelisted",        no_argument,       NULL, 'g' },
+        { "denylisted",         no_argument,       NULL, 'b' },
+        { "allowlisted",        no_argument,       NULL, 'g' },
+        { "blacklisted",        no_argument,       NULL, 'b' }, /* backward compat */
+        { "whitelisted",        no_argument,       NULL, 'g' }, /* backward compat */
         { "replace-whitespace", no_argument,       NULL, 'u' },
         { "sg-version",         required_argument, NULL, 's' },
         { "verbose",            no_argument,       NULL, 'v' },
@@ -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",
@@ -295,7 +297,7 @@ static int set_options(int argc, char **argv,
                         break;
 
                 case 'V':
-                        printf("%s\n", GIT_VERSION);
+                        version();
                         exit(EXIT_SUCCESS);
 
                 case 'x':
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 a48d5dedf8066cdcb87613235ce491ce7b7e9020..5e2b69d6f74d001ebec3df092789e5d7ebd8e79f 100644 (file)
@@ -23,7 +23,7 @@
 #include <linux/pci_regs.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "device-private.h"
 #include "device-util.h"
 #include "dirent-util.h"
index 24180f9418fe0260b51b1f2092c0bdc7eec68592..7d9a8c018f09e004ea953c16e94adf7f82fa78e4 100644 (file)
@@ -161,7 +161,6 @@ static int udev_ctrl_connection_event_handler(sd_event_source *s, int fd, uint32
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
         };
-        struct cmsghdr *cmsg;
         struct ucred *cred;
         ssize_t size;
 
@@ -185,15 +184,12 @@ static int udev_ctrl_connection_event_handler(sd_event_source *s, int fd, uint32
 
         cmsg_close_all(&smsg);
 
-        cmsg = CMSG_FIRSTHDR(&smsg);
-
-        if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
+        cred = CMSG_FIND_DATA(&smsg, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
+        if (!cred) {
                 log_error("No sender credentials received, ignoring message");
                 return 0;
         }
 
-        cred = (struct ucred *) CMSG_DATA(cmsg);
-
         if (cred->uid != 0) {
                 log_error("Invalid sender uid "UID_FMT", ignoring message", cred->uid);
                 return 0;
index 4859a142da4ac2085027cf10a145eb53f19c3928..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,        /* == */
@@ -1069,6 +1069,51 @@ static UdevRuleOperatorType parse_operator(const char *op) {
         return _OP_TYPE_INVALID;
 }
 
+static void check_token_delimiters(UdevRuleLine *rule_line, const char *line) {
+        assert(rule_line);
+
+        size_t n_comma = 0;
+        bool ws_before_comma = false, ws_after_comma = false;
+        const char *p;
+
+        for (p = line; !isempty(p); ++p) {
+                if (*p == ',')
+                        ++n_comma;
+                else if (strchr(WHITESPACE, *p)) {
+                        if (n_comma > 0)
+                                ws_after_comma = true;
+                        else
+                                ws_before_comma = true;
+                } else
+                        break;
+        }
+
+        if (line == rule_line->line) {
+                /* this is the first token of the rule */
+                if (n_comma > 0)
+                        log_line_warning(rule_line, "Stray leading comma.");
+        } else if (isempty(p)) {
+                /* there are no more tokens in the rule */
+                if (n_comma > 0)
+                        log_line_warning(rule_line, "Stray trailing comma.");
+        } else {
+                /* single comma is expected */
+                if (n_comma == 0)
+                        log_line_warning(rule_line, "A comma between tokens is expected.");
+                else if (n_comma > 1)
+                        log_line_warning(rule_line, "More than one comma between tokens.");
+
+                /* whitespace after comma is expected */
+                if (n_comma > 0) {
+                        if (ws_before_comma)
+                                log_line_warning(rule_line, "Stray whitespace before comma.");
+                        if (!ws_after_comma)
+                                log_line_warning(rule_line, "Whitespace after comma is expected.");
+                } else if (!ws_before_comma && !ws_after_comma)
+                        log_line_warning(rule_line, "Whitespace between tokens is expected.");
+        }
+}
+
 static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) {
         char *key_begin, *key_end, *attr, *tmp;
         UdevRuleOperatorType op;
@@ -1123,6 +1168,20 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
         return 1;
 }
 
+static void check_tokens_order(UdevRuleLine *rule_line) {
+        bool has_result = false;
+
+        assert(rule_line);
+
+        LIST_FOREACH(tokens, t, rule_line->tokens)
+                if (t->type == TK_M_RESULT)
+                        has_result = true;
+                else if (has_result && t->type == TK_M_PROGRAM) {
+                        log_line_warning(rule_line, "Reordering RESULT check after PROGRAM assignment.");
+                        break;
+                }
+}
+
 static void sort_tokens(UdevRuleLine *rule_line) {
         assert(rule_line);
 
@@ -1140,7 +1199,7 @@ static void sort_tokens(UdevRuleLine *rule_line) {
         }
 }
 
-static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr) {
+static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr, bool extra_checks) {
         _cleanup_(udev_rule_line_freep) UdevRuleLine *rule_line = NULL;
         _cleanup_free_ char *line = NULL;
         char *p;
@@ -1172,6 +1231,9 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned
                 char *key, *attr, *value;
                 UdevRuleOperatorType op;
 
+                if (extra_checks)
+                        check_token_delimiters(rule_line, p);
+
                 r = parse_line(&p, &key, &attr, &op, &value);
                 if (r < 0)
                         return log_line_error_errno(rule_line, r, "Invalid key/value pair, ignoring.");
@@ -1184,10 +1246,13 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned
         }
 
         if (rule_line->type == 0) {
-                log_line_warning(rule_line, "The line takes no effect, ignoring.");
+                log_line_warning(rule_line, "The line has no effect, ignoring.");
                 return 0;
         }
 
+        if (extra_checks)
+                check_tokens_order(rule_line);
+
         sort_tokens(rule_line);
         TAKE_PTR(rule_line);
         return 0;
@@ -1216,7 +1281,7 @@ static void rule_resolve_goto(UdevRuleFile *rule_file) {
                         line->goto_label = NULL;
 
                         if ((line->type & ~(LINE_HAS_LABEL|LINE_IS_REFERENCED)) == 0) {
-                                log_line_notice(line, "The line takes no effect any more, dropping");
+                                log_line_notice(line, "The line has no effect any more, dropping.");
                                 /* LINE_IS_REFERENCED implies LINE_HAS_LABEL */
                                 if (line->type & LINE_HAS_LABEL)
                                         udev_rule_line_clear_tokens(line);
@@ -1227,124 +1292,6 @@ static void rule_resolve_goto(UdevRuleFile *rule_file) {
         }
 }
 
-int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile **ret) {
-        _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL;
-        _cleanup_free_ char *continuation = NULL, *name = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        bool ignore_line = false;
-        unsigned line_nr = 0;
-        struct stat st;
-        int r;
-
-        assert(rules);
-        assert(filename);
-
-        f = fopen(filename, "re");
-        if (!f)
-                return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
-
-        if (fstat(fileno(f), &st) < 0)
-                return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
-
-        if (null_or_empty(&st)) {
-                log_debug("Skipping empty file: %s", filename);
-                if (ret)
-                        *ret = NULL;
-                return 0;
-        }
-
-        r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
-        if (r < 0)
-                return log_warning_errno(errno, "Failed to save stat for %s, ignoring: %m", filename);
-
-        (void) fd_warn_permissions(filename, fileno(f));
-
-        log_debug("Reading rules file: %s", filename);
-
-        name = strdup(filename);
-        if (!name)
-                return log_oom();
-
-        rule_file = new(UdevRuleFile, 1);
-        if (!rule_file)
-                return log_oom();
-
-        *rule_file = (UdevRuleFile) {
-                .filename = TAKE_PTR(name),
-                .rules = rules,
-        };
-
-        LIST_APPEND(rule_files, rules->rule_files, rule_file);
-
-        for (;;) {
-                _cleanup_free_ char *buf = NULL;
-                size_t len;
-                char *line;
-
-                r = read_line(f, UDEV_LINE_SIZE, &buf);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                line_nr++;
-                line = skip_leading_chars(buf, NULL);
-
-                /* Lines beginning with '#' are ignored regardless of line continuation. */
-                if (line[0] == '#')
-                        continue;
-
-                len = strlen(line);
-
-                if (continuation && !ignore_line) {
-                        if (strlen(continuation) + len >= UDEV_LINE_SIZE)
-                                ignore_line = true;
-
-                        if (!strextend(&continuation, line))
-                                return log_oom();
-
-                        if (!ignore_line) {
-                                line = continuation;
-                                len = strlen(line);
-                        }
-                }
-
-                if (len > 0 && line[len - 1] == '\\') {
-                        if (ignore_line)
-                                continue;
-
-                        line[len - 1] = '\0';
-                        if (!continuation) {
-                                continuation = strdup(line);
-                                if (!continuation)
-                                        return log_oom();
-                        }
-
-                        continue;
-                }
-
-                if (ignore_line)
-                        log_file_error(rule_file, line_nr, "Line is too long, ignored");
-                else if (len > 0)
-                        (void) rule_add_line(rule_file, line, line_nr);
-
-                continuation = mfree(continuation);
-                ignore_line = false;
-        }
-
-        if (continuation)
-                log_file_error(rule_file, line_nr,
-                               "Unexpected EOF after line continuation, line ignored");
-
-        rule_resolve_goto(rule_file);
-
-        if (ret)
-                *ret = rule_file;
-
-        TAKE_PTR(rule_file);
-        return 1;
-}
-
 static bool token_data_is_string(UdevRuleTokenType type) {
         return IN_SET(type, TK_M_ENV,
                             TK_M_CONST,
@@ -1418,17 +1365,34 @@ static bool nulstr_tokens_conflict(const UdevRuleToken *a, const UdevRuleToken *
               a->op == b->op &&
               a->op == OP_MATCH &&
               a->match_type == b->match_type &&
-              a->match_type == MATCH_TYPE_PLAIN &&
               a->attr_subst_type == b->attr_subst_type &&
               a->attr_match_remove_trailing_whitespace == b->attr_match_remove_trailing_whitespace &&
               token_type_and_data_eq(a, b)))
                 return false;
 
-        NULSTR_FOREACH(i, a->value)
-                if (nulstr_contains(b->value, i))
-                        return false;
+        if (a->match_type == MATCH_TYPE_PLAIN) {
+                NULSTR_FOREACH(i, a->value)
+                        if (nulstr_contains(b->value, i))
+                                return false;
+                return true;
+        }
 
-        return true;
+        if (a->match_type == MATCH_TYPE_GLOB) {
+                NULSTR_FOREACH(i, a->value) {
+                        size_t i_n = strcspn(i, GLOB_CHARS);
+                        if (i_n == 0)
+                                return false;
+                        NULSTR_FOREACH(j, b->value) {
+                                size_t j_n = strcspn(j, GLOB_CHARS);
+                                if (j_n == 0 || strneq(i, j, MIN(i_n, j_n)))
+                                        return false;
+                        }
+
+                }
+                return true;
+        }
+
+        return false;
 }
 
 static void udev_check_unused_labels(UdevRuleLine *line) {
@@ -1464,7 +1428,7 @@ static void udev_check_conflicts_duplicates(UdevRuleLine *line) {
                         }
                         if (new_conflicts) {
                                 conflicts = new_conflicts;
-                                log_line_error(line, "conflicting match expressions, the line takes no effect");
+                                log_line_error(line, "conflicting match expressions, the line has no effect");
                         }
                         if (conflicts && duplicates)
                                 return;
@@ -1476,12 +1440,135 @@ static void udev_check_rule_line(UdevRuleLine *line) {
         udev_check_conflicts_duplicates(line);
 }
 
+int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) {
+        _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL;
+        _cleanup_free_ char *continuation = NULL, *name = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        bool ignore_line = false;
+        unsigned line_nr = 0;
+        struct stat st;
+        int r;
+
+        assert(rules);
+        assert(filename);
+
+        f = fopen(filename, "re");
+        if (!f) {
+                if (!extra_checks && errno == ENOENT)
+                        return 0;
+
+                return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
+        }
+
+        if (fstat(fileno(f), &st) < 0)
+                return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
+
+        if (null_or_empty(&st)) {
+                log_debug("Skipping empty file: %s", filename);
+                if (ret)
+                        *ret = NULL;
+                return 0;
+        }
+
+        r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
+        if (r < 0)
+                return log_warning_errno(errno, "Failed to save stat for %s, ignoring: %m", filename);
+
+        (void) fd_warn_permissions(filename, fileno(f));
+
+        log_debug("Reading rules file: %s", filename);
+
+        name = strdup(filename);
+        if (!name)
+                return log_oom();
+
+        rule_file = new(UdevRuleFile, 1);
+        if (!rule_file)
+                return log_oom();
+
+        *rule_file = (UdevRuleFile) {
+                .filename = TAKE_PTR(name),
+                .rules = rules,
+        };
+
+        LIST_APPEND(rule_files, rules->rule_files, rule_file);
+
+        for (;;) {
+                _cleanup_free_ char *buf = NULL;
+                size_t len;
+                char *line;
+
+                r = read_line(f, UDEV_LINE_SIZE, &buf);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                line_nr++;
+                line = skip_leading_chars(buf, NULL);
+
+                /* Lines beginning with '#' are ignored regardless of line continuation. */
+                if (line[0] == '#')
+                        continue;
+
+                len = strlen(line);
+
+                if (continuation && !ignore_line) {
+                        if (strlen(continuation) + len >= UDEV_LINE_SIZE)
+                                ignore_line = true;
+
+                        if (!strextend(&continuation, line))
+                                return log_oom();
+
+                        if (!ignore_line) {
+                                line = continuation;
+                                len = strlen(line);
+                        }
+                }
+
+                if (len > 0 && line[len - 1] == '\\') {
+                        if (ignore_line)
+                                continue;
+
+                        line[len - 1] = '\0';
+                        if (!continuation) {
+                                continuation = strdup(line);
+                                if (!continuation)
+                                        return log_oom();
+                        }
+
+                        continue;
+                }
+
+                if (ignore_line)
+                        log_file_error(rule_file, line_nr, "Line is too long, ignored");
+                else if (len > 0)
+                        (void) rule_add_line(rule_file, line, line_nr, extra_checks);
+
+                continuation = mfree(continuation);
+                ignore_line = false;
+        }
+
+        if (continuation)
+                log_file_error(rule_file, line_nr,
+                               "Unexpected EOF after line continuation, line ignored");
+
+        rule_resolve_goto(rule_file);
+
+        if (extra_checks)
+                LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
+                        udev_check_rule_line(line);
+
+        if (ret)
+                *ret = rule_file;
+
+        TAKE_PTR(rule_file);
+        return 1;
+}
+
 unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file) {
         assert(rule_file);
 
-        LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
-                udev_check_rule_line(line);
-
         return rule_file->issues;
 }
 
@@ -1513,7 +1600,7 @@ int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing
                 return log_debug_errno(r, "Failed to enumerate rules files: %m");
 
         STRV_FOREACH(f, files) {
-                r = udev_rules_parse_file(rules, *f, NULL);
+                r = udev_rules_parse_file(rules, *f, /* extra_checks = */ false, NULL);
                 if (r < 0)
                         log_debug_errno(r, "Failed to read rules file %s, ignoring: %m", *f);
         }
@@ -2400,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;
         }
@@ -2454,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;
 
@@ -2467,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)
@@ -2485,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 cecc285b2ccdbaba6b43a1c5905d70d993d44421..9b477fb235909f89fc9a350276b3b526c81f7374 100644 (file)
@@ -18,7 +18,7 @@ typedef enum {
         _ESCAPE_TYPE_INVALID = -EINVAL,
 } UdevRuleEscapeType;
 
-int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile **ret);
+int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret);
 unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file);
 UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing);
 int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing);
index d19e7561f81f166cabefc3007f732d8ee3d56e6c..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) {
@@ -334,11 +337,9 @@ int lock_main(int argc, char *argv[], void *userdata) {
                         if (fd < 0)
                                 return fd;
 
-                        r = fdset_put(fds, fd);
+                        r = fdset_consume(fds, TAKE_FD(fd));
                         if (r < 0)
                                 return log_oom();
-
-                        TAKE_FD(fd);
                 }
         }
 
index 2e947ef83016842dbc3c0b5450d975b49f41ed63..48aeb8f7e75daa8f2e9424b56e754b2cd7bf40b1 100644 (file)
@@ -104,7 +104,7 @@ static int verify_rules_file(UdevRules *rules, const char *fname) {
         UdevRuleFile *file;
         int r;
 
-        r = udev_rules_parse_file(rules, fname, &file);
+        r = udev_rules_parse_file(rules, fname, /* extra_checks = */ true, &file);
         if (r < 0)
                 return log_error_errno(r, "Failed to parse rules file %s: %m", fname);
         if (r == 0) /* empty file. */
index 544c94616d590a0d005e99bbbb804689415beaee..0ec7099eb0197e0b0cd65bdd571ff13bb69af0b8 100644 (file)
@@ -6,7 +6,7 @@
 #include "sd-event.h"
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "device-monitor-private.h"
 #include "device-util.h"
 #include "errno-util.h"
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 d70fccde41e6119fdc5dcb5b628400e22f976943..c56956c378463d24d45df0dac73cfdf3180aa42c 100644 (file)
@@ -706,8 +706,6 @@ static int worker_main(Manager *_manager, sd_device_monitor *monitor, sd_device
         assert(monitor);
         assert(dev);
 
-        assert_se(unsetenv("NOTIFY_SOCKET") == 0);
-
         assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, -1) >= 0);
 
         /* Reset OOM score, we only protect the main daemon. */
index 4f163c43509e66567bd120b1274084da9cae6fc0..30527e9556d4c95878c95f11bee49b3351fa61e1 100644 (file)
 #include <unistd.h>
 #include <linux/videodev2.h>
 
+#include "build.h"
 #include "fd-util.h"
+#include "main-func.h"
 
-int main(int argc, char *argv[]) {
+static const char *arg_device = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
         static const struct option options[] = {
-                { "help", no_argument, NULL, 'h' },
+                { "help",     no_argument, NULL, 'h' },
+                { "version",  no_argument, NULL, 'v' },
                 {}
         };
-        _cleanup_close_ int fd = -EBADF;
-        char *device;
-        struct v4l2_capability v2cap;
         int c;
 
         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
-
                 switch (c) {
                 case 'h':
-                        printf("%s [-h,--help] <device file>\n\n"
+                        printf("%s [OPTIONS...] DEVICE\n\n"
                                "Video4Linux device identification.\n\n"
-                               "  -h  Print this message\n",
+                               "  -h --help     Show this help text\n"
+                               "     --version  Show package version\n",
                                program_invocation_short_name);
                         return 0;
+                case 'v':
+                        return version();
                 case '?':
                         return -EINVAL;
-
                 default:
                         assert_not_reached();
                 }
 
-        device = argv[optind];
-        if (!device)
-                return 2;
+        if (!argv[optind])
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "DEVICE argument missing.");
+
+        arg_device = argv[optind];
+        return 1;
+}
 
-        fd = open(device, O_RDONLY);
+static int run(int argc, char *argv[]) {
+        _cleanup_close_ int fd = -EBADF;
+        struct v4l2_capability v2cap;
+        int r;
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        fd = open(arg_device, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fd < 0)
-                return 3;
+                return log_error_errno(errno, "Failed to open %s: %m", arg_device);
 
         if (ioctl(fd, VIDIOC_QUERYCAP, &v2cap) == 0) {
                 int capabilities;
+
                 printf("ID_V4L_VERSION=2\n");
                 printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
                 printf("ID_V4L_CAPABILITIES=:");
+
                 if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS)
                         capabilities = v2cap.device_caps;
                 else
                         capabilities = v2cap.capabilities;
+
                 if ((capabilities & V4L2_CAP_VIDEO_CAPTURE) > 0 ||
                     (capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) > 0)
                         printf("capture:");
@@ -90,3 +109,5 @@ int main(int argc, char *argv[]) {
 
         return 0;
 }
+
+DEFINE_MAIN_FUNCTION(run);
index d34d3acc2738cf45be7bc89f1a024449d4aaed37..b319f2dc913509b5150cea70d0d58af4fd38062c 100755 (executable)
@@ -21,6 +21,7 @@ import subprocess
 import tempfile
 import typing
 
+import pefile
 
 __version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})'
 
@@ -30,6 +31,9 @@ EFI_ARCH_MAP = {
         'i[3456]86'    : ['ia32'],
         'aarch64'      : ['aa64'],
         'arm[45678]*l' : ['arm'],
+        'loongarch32'  : ['loongarch32'],
+        'loongarch64'  : ['loongarch64'],
+        'riscv32'      : ['riscv32'],
         'riscv64'      : ['riscv64'],
 }
 EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), [])
@@ -77,14 +81,6 @@ def path_is_readable(s: typing.Optional[str]) -> typing.Optional[pathlib.Path]:
     return p
 
 
-def pe_next_section_offset(filename):
-    import pefile
-
-    pe = pefile.PE(filename, fast_load=True)
-    section = pe.sections[-1]
-    return pe.OPTIONAL_HEADER.ImageBase + section.VirtualAddress + section.Misc_VirtualSize
-
-
 def round_up(x, blocksize=4096):
     return (x + blocksize - 1) // blocksize * blocksize
 
@@ -106,6 +102,10 @@ def maybe_decompress(filename):
         # not compressed
         return f.read()
 
+    if start.startswith(b'MZ'):
+        # not compressed aarch64 and riscv64
+        return f.read()
+
     if start.startswith(b'\x1f\x8b'):
         gzip = try_import('gzip')
         return gzip.open(f).read()
@@ -223,8 +223,6 @@ class Section:
     name: str
     content: pathlib.Path
     tmpfile: typing.Optional[typing.IO] = None
-    flags: list[str] = dataclasses.field(default_factory=lambda: ['data', 'readonly'])
-    offset: typing.Optional[int] = None
     measure: bool = False
 
     @classmethod
@@ -270,20 +268,11 @@ class Section:
 class UKI:
     executable: list[typing.Union[pathlib.Path, str]]
     sections: list[Section] = dataclasses.field(default_factory=list, init=False)
-    offset: typing.Optional[int] = dataclasses.field(default=None, init=False)
-
-    def __post_init__(self):
-        self.offset = round_up(pe_next_section_offset(self.executable))
 
     def add_section(self, section):
-        assert self.offset
-        assert section.offset is None
-
         if section.name in [s.name for s in self.sections]:
             raise ValueError(f'Duplicate section {section.name}')
 
-        section.offset = self.offset
-        self.offset += round_up(section.size())
         self.sections += [section]
 
 
@@ -454,16 +443,98 @@ def pairwise(iterable):
     return zip(a, b)
 
 
-def pe_validate(filename):
-    import pefile
+class PeError(Exception):
+    pass
+
+
+def pe_add_sections(uki: UKI, output: str):
+    pe = pefile.PE(uki.executable, fast_load=True)
+
+    # Old stubs do not have the symbol/string table stripped, even though image files should not have one.
+    if symbol_table := pe.FILE_HEADER.PointerToSymbolTable:
+        symbol_table_size = 18 * pe.FILE_HEADER.NumberOfSymbols
+        if string_table_size := pe.get_dword_from_offset(symbol_table + symbol_table_size):
+            symbol_table_size += string_table_size
+
+        # Let's be safe and only strip it if it's at the end of the file.
+        if symbol_table + symbol_table_size == len(pe.__data__):
+            pe.__data__ = pe.__data__[:symbol_table]
+            pe.FILE_HEADER.PointerToSymbolTable = 0
+            pe.FILE_HEADER.NumberOfSymbols = 0
+            pe.FILE_HEADER.IMAGE_FILE_LOCAL_SYMS_STRIPPED = True
+
+    # Old stubs might have been stripped, leading to unaligned raw data values, so let's fix them up here.
+    for i, section in enumerate(pe.sections):
+        oldp = section.PointerToRawData
+        oldsz = section.SizeOfRawData
+        section.PointerToRawData = round_up(oldp, pe.OPTIONAL_HEADER.FileAlignment)
+        section.SizeOfRawData = round_up(oldsz, pe.OPTIONAL_HEADER.FileAlignment)
+        padp = section.PointerToRawData - oldp
+        padsz = section.SizeOfRawData - oldsz
+
+        for later_section in pe.sections[i+1:]:
+            later_section.PointerToRawData += padp + padsz
+
+        pe.__data__ = pe.__data__[:oldp] + bytes(padp) + pe.__data__[oldp:oldp+oldsz] + bytes(padsz) + pe.__data__[oldp+oldsz:]
+
+    # We might not have any space to add new sections. Let's try our best to make some space by padding the
+    # SizeOfHeaders to a multiple of the file alignment. This is safe because the first section's data starts
+    # at a multiple of the file alignment, so all space before that is unused.
+    pe.OPTIONAL_HEADER.SizeOfHeaders = round_up(pe.OPTIONAL_HEADER.SizeOfHeaders, pe.OPTIONAL_HEADER.FileAlignment)
+    pe = pefile.PE(data=pe.write(), fast_load=True)
+
+    warnings = pe.get_warnings()
+    if warnings:
+        raise PeError(f'pefile warnings treated as errors: {warnings}')
+
+    security = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']]
+    if security.VirtualAddress != 0:
+        # We could strip the signatures, but why would anyone sign the stub?
+        raise PeError(f'Stub image is signed, refusing.')
+
+    for section in uki.sections:
+        new_section = pefile.SectionStructure(pe.__IMAGE_SECTION_HEADER_format__, pe=pe)
+        new_section.__unpack__(b'\0' * new_section.sizeof())
+
+        offset = pe.sections[-1].get_file_offset() + new_section.sizeof()
+        if offset + new_section.sizeof() > pe.OPTIONAL_HEADER.SizeOfHeaders:
+            raise PeError(f'Not enough header space to add section {section.name}.')
+
+        data = section.content.read_bytes()
+
+        new_section.set_file_offset(offset)
+        new_section.Name = section.name.encode()
+        new_section.Misc_VirtualSize = len(data)
+        # Non-stripped stubs might still have an unaligned symbol table at the end, making their size
+        # unaligned, so we make sure to explicitly pad the pointer to new sections to an aligned offset.
+        new_section.PointerToRawData = round_up(len(pe.__data__), pe.OPTIONAL_HEADER.FileAlignment)
+        new_section.SizeOfRawData = round_up(len(data), pe.OPTIONAL_HEADER.FileAlignment)
+        new_section.VirtualAddress = round_up(
+            pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize,
+            pe.OPTIONAL_HEADER.SectionAlignment,
+        )
+
+        new_section.IMAGE_SCN_MEM_READ = True
+        if section.name == '.linux':
+            # Old kernels that use EFI handover protocol will be executed inline.
+            new_section.IMAGE_SCN_CNT_CODE = True
+        else:
+            new_section.IMAGE_SCN_CNT_INITIALIZED_DATA = True
+
+        pe.__data__ = pe.__data__[:] + bytes(new_section.PointerToRawData - len(pe.__data__)) + data + bytes(new_section.SizeOfRawData - len(data))
 
-    pe = pefile.PE(filename, fast_load=True)
+        pe.FILE_HEADER.NumberOfSections += 1
+        pe.OPTIONAL_HEADER.SizeOfInitializedData += new_section.Misc_VirtualSize
+        pe.__structures__.append(new_section)
+        pe.sections.append(new_section)
 
-    sections = sorted(pe.sections, key=lambda s: (s.VirtualAddress, s.Misc_VirtualSize))
+    pe.OPTIONAL_HEADER.CheckSum = 0
+    pe.OPTIONAL_HEADER.SizeOfImage = round_up(
+        pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize,
+        pe.OPTIONAL_HEADER.SectionAlignment,
+    )
 
-    for l, r in pairwise(sections):
-        if l.VirtualAddress + l.Misc_VirtualSize > r.VirtualAddress + r.Misc_VirtualSize:
-            raise ValueError(f'Section "{l.Name.decode()}" ({l.VirtualAddress}, {l.Misc_VirtualSize}) overlaps with section "{r.Name.decode()}" ({r.VirtualAddress}, {r.Misc_VirtualSize})')
+    pe.write(output)
 
 
 def make_uki(opts):
@@ -553,9 +624,7 @@ def make_uki(opts):
 
     # UKI creation
 
-    uki.add_section(
-        Section.create('.linux', linux, measure=True,
-                       flags=['code', 'readonly']))
+    uki.add_section(Section.create('.linux', linux, measure=True))
 
     if opts.sb_key:
         unsigned = tempfile.NamedTemporaryFile(prefix='uki')
@@ -563,26 +632,7 @@ def make_uki(opts):
     else:
         output = opts.output
 
-    objcopy_tool = find_tool('llvm-objcopy', 'objcopy', opts=opts)
-
-    cmd = [
-        objcopy_tool,
-        opts.stub,
-        *itertools.chain.from_iterable(
-            ('--add-section',       f'{s.name}={s.content}',
-             '--set-section-flags', f"{s.name}={','.join(s.flags)}")
-            for s in uki.sections),
-        output,
-    ]
-
-    if pathlib.Path(objcopy_tool).name != 'llvm-objcopy':
-        cmd += itertools.chain.from_iterable(
-            ('--change-section-vma', f'{s.name}=0x{s.offset:x}') for s in uki.sections)
-
-    print('+', shell_join(cmd))
-    subprocess.check_call(cmd)
-
-    pe_validate(output)
+    pe_add_sections(uki, output)
 
     # UKI signing
 
@@ -703,7 +753,7 @@ usage: ukify [options…] linux initrd…
     p.add_argument('--tools',
                    type=pathlib.Path,
                    action='append',
-                   help='Directories to search for tools (systemd-measure, llvm-objcopy, ...)')
+                   help='Directories to search for tools (systemd-measure, ...)')
 
     p.add_argument('--output', '-o',
                    type=pathlib.Path,
index 6b7493fd8853a18a5f4ce6cb20084c5ed8e9ffbd..37867ee3ed1d8f975899d1e90f75b40071ef7cba 100644 (file)
@@ -15,7 +15,7 @@
 #include "string-util.h"
 
 static int run(int argc, char *argv[]) {
-        int r, k;
+        int r;
 
         if (argc != 2)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -29,14 +29,11 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
-        if (streq(argv[1], "start")) {
-                r = unlink_or_warn("/run/nologin");
-                k = unlink_or_warn("/etc/nologin");
-                if (r < 0)
-                        return r;
-                return k;
+        /* We only touch /run/nologin. See create_shutdown_run_nologin_or_warn() for details. */
 
-        } else if (streq(argv[1], "stop"))
+        if (streq(argv[1], "start"))
+                return unlink_or_warn("/run/nologin");
+        if (streq(argv[1], "stop"))
                 return create_shutdown_run_nologin_or_warn();
 
         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb '%s'.", argv[1]);
index b728cdc1c369d5100efa5be2c70f64c745bfacb6..67675b4b7f7ea6237e5e9585e5b52650ef468827 100644 (file)
@@ -962,7 +962,7 @@ static int display_services(int argc, char *argv[], void *userdata) {
                         return table_log_print_error(r);
         }
 
-        if (arg_legend) {
+        if (arg_legend && arg_output != OUTPUT_JSON) {
                 if (table_get_rows(t) > 1)
                         printf("\n%zu services listed.\n", table_get_rows(t) - 1);
                 else
@@ -1038,6 +1038,7 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
                         log_debug("Chain invoking: %s", s);
                 }
 
+                fflush(stdout);
                 execv(chain_invocation[0], chain_invocation);
                 if (errno == ENOENT) /* Let's handle ENOENT gracefully */
                         log_warning_errno(errno, "Chain executable '%s' does not exist, ignoring chain invocation.", chain_invocation[0]);
@@ -1149,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 9a09b0d5160a50aa915c694e60dd8a163f325a55..a359f848cc4123a52505c5d973a371decc693cdb 100644 (file)
 #include "terminal-util.h"
 #include "virt.h"
 
+typedef enum VCMeta {
+        VC_KEYMAP,
+        VC_KEYMAP_TOGGLE,
+        VC_FONT,
+        VC_FONT_MAP,
+        VC_FONT_UNIMAP,
+        _VC_META_MAX,
+        _VC_META_INVALID = -EINVAL,
+} VCMeta;
+
+typedef struct Context {
+        char *config[_VC_META_MAX];
+} Context;
+
+static const char * const vc_meta_names[_VC_META_MAX] = {
+        [VC_KEYMAP]               = "vconsole.keymap",
+        [VC_KEYMAP_TOGGLE]        = "vconsole.keymap_toggle",
+        [VC_FONT]                 = "vconsole.font",
+        [VC_FONT_MAP]             = "vconsole.font_map",
+        [VC_FONT_UNIMAP]          = "vconsole.font_unimap",
+};
+
+/* compatibility with obsolete multiple-dot scheme */
+static const char * const vc_meta_compat_names[_VC_META_MAX] = {
+        [VC_KEYMAP_TOGGLE] = "vconsole.keymap.toggle",
+        [VC_FONT_MAP]      = "vconsole.font.map",
+        [VC_FONT_UNIMAP]   = "vconsole.font.unimap",
+};
+
+static const char * const vc_env_names[_VC_META_MAX] = {
+        [VC_KEYMAP]        = "KEYMAP",
+        [VC_KEYMAP_TOGGLE] = "KEYMAP_TOGGLE",
+        [VC_FONT]          = "FONT",
+        [VC_FONT_MAP]      = "FONT_MAP",
+        [VC_FONT_UNIMAP]   = "FONT_UNIMAP",
+};
+
+static void context_done(Context *c) {
+        assert(c);
+
+        for (VCMeta i = 0; i < _VC_META_MAX; i++)
+                free(c->config[i]);
+}
+
+static void context_merge_config(
+                Context *dst,
+                Context *src,
+                Context *src_compat) {
+
+        assert(dst);
+        assert(src);
+
+        for (VCMeta i = 0; i < _VC_META_MAX; i++)
+                if (src->config[i])
+                        free_and_replace(dst->config[i], src->config[i]);
+                else if (src_compat && src_compat->config[i])
+                        free_and_replace(dst->config[i], src_compat->config[i]);
+}
+
+static const char* context_get_config(Context *c, VCMeta meta) {
+        assert(c);
+        assert(meta >= 0 && meta < _VC_META_MAX);
+
+        if (meta == VC_KEYMAP)
+                return isempty(c->config[VC_KEYMAP]) ? SYSTEMD_DEFAULT_KEYMAP : c->config[VC_KEYMAP];
+
+        return empty_to_null(c->config[meta]);
+}
+
+static int context_read_creds(Context *c) {
+        _cleanup_(context_done) Context v = {};
+        int r;
+
+        assert(c);
+
+        r = read_credential_strings_many(
+                        vc_meta_names[VC_KEYMAP],        &v.config[VC_KEYMAP],
+                        vc_meta_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
+                        vc_meta_names[VC_FONT],          &v.config[VC_FONT],
+                        vc_meta_names[VC_FONT_MAP],      &v.config[VC_FONT_MAP],
+                        vc_meta_names[VC_FONT_UNIMAP],   &v.config[VC_FONT_UNIMAP]);
+        if (r < 0) {
+                if (r != -ENXIO)
+                        log_warning_errno(r, "Failed to import credentials, ignoring: %m");
+                return r;
+        }
+
+        context_merge_config(c, &v, NULL);
+        return 0;
+}
+
+static int context_read_env(Context *c) {
+        _cleanup_(context_done) Context v = {};
+        int r;
+
+        assert(c);
+
+        r = parse_env_file(
+                        NULL, "/etc/vconsole.conf",
+                        vc_env_names[VC_KEYMAP],        &v.config[VC_KEYMAP],
+                        vc_env_names[VC_KEYMAP_TOGGLE], &v.config[VC_KEYMAP_TOGGLE],
+                        vc_env_names[VC_FONT],          &v.config[VC_FONT],
+                        vc_env_names[VC_FONT_MAP],      &v.config[VC_FONT_MAP],
+                        vc_env_names[VC_FONT_UNIMAP],   &v.config[VC_FONT_UNIMAP]);
+        if (r < 0) {
+                if (r != -ENOENT)
+                        log_warning_errno(r, "Failed to read /etc/vconsole.conf, ignoring: %m");
+                return r;
+        }
+
+        context_merge_config(c, &v, NULL);
+        return 0;
+}
+
+static int context_read_proc_cmdline(Context *c) {
+        _cleanup_(context_done) Context v = {}, w = {};
+        int r;
+
+        assert(c);
+
+        r = proc_cmdline_get_key_many(
+                        PROC_CMDLINE_STRIP_RD_PREFIX,
+                        vc_meta_names[VC_KEYMAP],               &v.config[VC_KEYMAP],
+                        vc_meta_names[VC_KEYMAP_TOGGLE],        &v.config[VC_KEYMAP_TOGGLE],
+                        vc_meta_names[VC_FONT],                 &v.config[VC_FONT],
+                        vc_meta_names[VC_FONT_MAP],             &v.config[VC_FONT_MAP],
+                        vc_meta_names[VC_FONT_UNIMAP],          &v.config[VC_FONT_UNIMAP],
+                        vc_meta_compat_names[VC_KEYMAP_TOGGLE], &w.config[VC_KEYMAP_TOGGLE],
+                        vc_meta_compat_names[VC_FONT_MAP],      &w.config[VC_FONT_MAP],
+                        vc_meta_compat_names[VC_FONT_UNIMAP],   &w.config[VC_FONT_UNIMAP]);
+        if (r < 0) {
+                if (r != -ENOENT)
+                        log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
+                return r;
+        }
+
+        context_merge_config(c, &v, &w);
+        return 0;
+}
+
+static void context_load_config(Context *c) {
+        assert(c);
+
+        /* Load data from credentials (lowest priority) */
+        (void) context_read_creds(c);
+
+        /* Load data from configuration file (middle priority) */
+        (void) context_read_env(c);
+
+        /* Let the kernel command line override /etc/vconsole.conf (highest priority) */
+        (void) context_read_proc_cmdline(c);
+}
+
 static int verify_vc_device(int fd) {
         unsigned char data[] = {
                 TIOCL_GETFGCONSOLE,
@@ -116,14 +269,20 @@ static int toggle_utf8_sysfs(bool utf8) {
         return 0;
 }
 
-static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
-        const char *args[8];
+static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) {
+        const char *map, *map_toggle, *args[8];
         unsigned i = 0;
         pid_t pid;
         int r;
 
+        assert(vc);
+        assert(c);
+
+        map = context_get_config(c, VC_KEYMAP);
+        map_toggle = context_get_config(c, VC_KEYMAP_TOGGLE);
+
         /* An empty map means kernel map */
-        if (isempty(map))
+        if (!map)
                 return 0;
 
         args[i++] = KBD_LOADKEYS;
@@ -155,28 +314,35 @@ static int keyboard_load_and_wait(const char *vc, const char *map, const char *m
         return wait_for_terminate_and_check(KBD_LOADKEYS, pid, WAIT_LOG);
 }
 
-static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
-        const char *args[9];
+static int font_load_and_wait(const char *vc, Context *c) {
+        const char *font, *map, *unimap, *args[9];
         unsigned i = 0;
         pid_t pid;
         int r;
 
+        assert(vc);
+        assert(c);
+
+        font = context_get_config(c, VC_FONT);
+        map = context_get_config(c, VC_FONT_MAP);
+        unimap = context_get_config(c, VC_FONT_UNIMAP);
+
         /* Any part can be set independently */
-        if (isempty(font) && isempty(map) && isempty(unimap))
+        if (!font && !map && !unimap)
                 return 0;
 
         args[i++] = KBD_SETFONT;
         args[i++] = "-C";
         args[i++] = vc;
-        if (!isempty(map)) {
+        if (map) {
                 args[i++] = "-m";
                 args[i++] = map;
         }
-        if (!isempty(unimap)) {
+        if (unimap) {
                 args[i++] = "-u";
                 args[i++] = unimap;
         }
-        if (!isempty(font))
+        if (font)
                 args[i++] = font;
         args[i++] = NULL;
 
@@ -413,12 +579,9 @@ static int verify_source_vc(char **ret_path, const char *src_vc) {
 }
 
 int main(int argc, char **argv) {
-        _cleanup_free_ char
-                *vc = NULL,
-                *vc_keymap_alloc = NULL, *vc_keymap_toggle = NULL,
-                *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
+        _cleanup_(context_done) Context c = {};
+        _cleanup_free_ char *vc = NULL;
         _cleanup_close_ int fd = -EBADF;
-        const char *vc_keymap;
         bool utf8, keyboard_ok;
         unsigned idx = 0;
         int r;
@@ -436,48 +599,13 @@ int main(int argc, char **argv) {
 
         utf8 = is_locale_utf8();
 
-        /* Load data from credentials (lowest priority) */
-        r = read_credential_strings_many(
-                        "vconsole.keymap", &vc_keymap_alloc,
-                        "vconsole.keymap_toggle", &vc_keymap_toggle,
-                        "vconsole.font", &vc_font,
-                        "vconsole.font_map", &vc_font_map,
-                        "vconsole.font_unimap", &vc_font_unimap);
-        if (r < 0 && r != -ENXIO)
-                log_warning_errno(r, "Failed to import credentials, ignoring: %m");
-
-        /* Load data from configuration file (middle priority) */
-        r = parse_env_file(NULL, "/etc/vconsole.conf",
-                           "KEYMAP", &vc_keymap_alloc,
-                           "KEYMAP_TOGGLE", &vc_keymap_toggle,
-                           "FONT", &vc_font,
-                           "FONT_MAP", &vc_font_map,
-                           "FONT_UNIMAP", &vc_font_unimap);
-        if (r < 0 && r != -ENOENT)
-                log_warning_errno(r, "Failed to read /etc/vconsole.conf, ignoring: %m");
-
-        /* Let the kernel command line override /etc/vconsole.conf (highest priority) */
-        r = proc_cmdline_get_key_many(
-                        PROC_CMDLINE_STRIP_RD_PREFIX,
-                        "vconsole.keymap", &vc_keymap_alloc,
-                        "vconsole.keymap_toggle", &vc_keymap_toggle,
-                        "vconsole.font", &vc_font,
-                        "vconsole.font_map", &vc_font_map,
-                        "vconsole.font_unimap", &vc_font_unimap,
-                        /* compatibility with obsolete multiple-dot scheme */
-                        "vconsole.keymap.toggle", &vc_keymap_toggle,
-                        "vconsole.font.map", &vc_font_map,
-                        "vconsole.font.unimap", &vc_font_unimap);
-        if (r < 0 && r != -ENOENT)
-                log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
-
-        vc_keymap = isempty(vc_keymap_alloc) ? SYSTEMD_DEFAULT_KEYMAP : vc_keymap_alloc;
+        context_load_config(&c);
 
         (void) toggle_utf8_sysfs(utf8);
         (void) toggle_utf8_vc(vc, fd, utf8);
 
-        r = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap);
-        keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
+        r = font_load_and_wait(vc, &c);
+        keyboard_ok = keyboard_load_and_wait(vc, &c, utf8) == 0;
 
         if (idx > 0) {
                 if (r == 0)
index ae497b02eefa829b7059730f5ae21012a40f4d12..be95c1f20641dc555748642a40d07b0b5a12d19e 100644 (file)
@@ -7,18 +7,37 @@
 #include "alloc-util.h"
 #include "cryptsetup-util.h"
 #include "fileio.h"
+#include "fstab-util.h"
 #include "hexdecoct.h"
 #include "log.h"
 #include "main-func.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "pretty-print.h"
 #include "process-util.h"
 #include "string-util.h"
 #include "terminal-util.h"
 
+static char *arg_hash = NULL;
+static bool arg_superblock = true;
+static int arg_format = 1;
+static uint64_t arg_data_block_size = 4096;
+static uint64_t arg_hash_block_size = 4096;
+static uint64_t arg_data_blocks = 0;
+static uint64_t arg_hash_offset = 0;
+static void *arg_salt = NULL;
+static uint64_t arg_salt_size = 32;
+static char *arg_uuid = NULL;
 static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY;
+static char *arg_fec_what = NULL;
+static uint64_t arg_fec_offset = 0;
+static uint64_t arg_fec_roots = 2;
 static char *arg_root_hash_signature = NULL;
 
+STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_salt, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep);
 
 static int help(void) {
@@ -62,6 +81,25 @@ static int save_roothashsig_option(const char *option, bool strict) {
                                "base64 string encoding signature prefixed by base64:.");
 }
 
+static int parse_block_size(const char *t, uint64_t *size) {
+        uint64_t u;
+        int r;
+
+        r = parse_size(t, 1024, &u);
+        if (r < 0)
+                return r;
+
+        if (u < 512 || u > (512 * 1024))
+                return -ERANGE;
+
+        if ((u % 512) != 0 || !ISPOWEROF2(u))
+                return -EINVAL;
+
+        *size = u;
+
+        return 0;
+}
+
 static int parse_options(const char *options) {
         int r;
 
@@ -104,7 +142,127 @@ static int parse_options(const char *options) {
                 else if (streq(word, "panic-on-corruption"))
                         arg_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION;
 #endif
-                else if ((val = startswith(word, "root-hash-signature="))) {
+                else if ((val = startswith(word, "superblock="))) {
+
+                        r = parse_boolean(val);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse boolean '%s': %m", word);
+
+                        arg_superblock = r;
+                } else if ((val = startswith(word, "format="))) {
+
+                        if (!STR_IN_SET(val, "0", "1"))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "format= expects either 0 (original Chrome OS version) or "
+                                                                                "1 (modern version).");
+
+                        arg_format = val[0] - '0';
+                } else if ((val = startswith(word, "data-block-size="))) {
+                        uint64_t sz;
+
+                        r = parse_block_size(val, &sz);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse size '%s': %m", word);
+
+                        arg_data_block_size = sz;
+                } else if ((val = startswith(word, "hash-block-size="))) {
+                        uint64_t sz;
+
+                        r = parse_block_size(val, &sz);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse size '%s': %m", word);
+
+                        arg_hash_block_size = sz;
+                } else if ((val = startswith(word, "data-blocks="))) {
+                        uint64_t u;
+
+                        r = safe_atou64(val, &u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse number '%s': %m", word);
+
+                        arg_data_blocks = u;
+                } else if ((val = startswith(word, "hash-offset="))) {
+                        uint64_t off;
+
+                        r = parse_size(val, 1024, &off);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse offset '%s': %m", word);
+                        if (off % 512 != 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "hash-offset= expects a 512-byte aligned value.");
+
+                        arg_hash_offset = off;
+                } else if ((val = startswith(word, "salt="))) {
+
+                        if (!string_is_safe(val))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid.");
+
+                        if (isempty(val)) {
+                                arg_salt = mfree(arg_salt);
+                                arg_salt_size = 32;
+                        } else if (streq(val, "-")) {
+                                arg_salt = mfree(arg_salt);
+                                arg_salt_size = 0;
+                        } else {
+                                size_t l;
+                                void *m;
+
+                                r = unhexmem(val, strlen(val), &m, &l);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse salt '%s': %m", word);
+
+                                free_and_replace(arg_salt, m);
+                                arg_salt_size = l;
+                        }
+                } else if ((val = startswith(word, "uuid="))) {
+
+                        r = sd_id128_from_string(val, NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse UUID '%s': %m", word);
+
+                        r = free_and_strdup(&arg_uuid, val);
+                        if (r < 0)
+                                return log_oom();
+                } else if ((val = startswith(word, "hash="))) {
+
+                        r = free_and_strdup(&arg_hash, val);
+                        if (r < 0)
+                                return log_oom();
+                } else if ((val = startswith(word, "fec-device="))) {
+                        _cleanup_free_ char *what = NULL;
+
+                        what = fstab_node_to_udev_node(val);
+                        if (!what)
+                                return log_oom();
+
+                        if (!path_is_absolute(what))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an absolute path.");
+
+                        if (!path_is_normalized(what))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an normalized path.");
+
+                        r = free_and_strdup(&arg_fec_what, what);
+                        if (r < 0)
+                                return log_oom();
+                } else if ((val = startswith(word, "fec-offset="))) {
+                        uint64_t off;
+
+                        r = parse_size(val, 1024, &off);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse offset '%s': %m", word);
+                        if (off % 512 != 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-offset= expects a 512-byte aligned value.");
+
+                        arg_fec_offset = off;
+                } else if ((val = startswith(word, "fec-roots="))) {
+                        uint64_t u;
+
+                        r = safe_atou64(val, &u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse number '%s', ignoring: %m", word);
+                        if (u < 2 || u > 24)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-rootfs= expects a value between 2 and 24 (including).");
+
+                        arg_fec_roots = u;
+                } else if ((val = startswith(word, "root-hash-signature="))) {
                         r = save_roothashsig_option(val, /* strict= */ true);
                         if (r < 0)
                                 return r;
@@ -138,6 +296,7 @@ static int run(int argc, char *argv[]) {
         if (streq(verb, "attach")) {
                 const char *volume, *data_device, *verity_device, *root_hash, *options;
                 _cleanup_free_ void *m = NULL;
+                struct crypt_params_verity p = {};
                 crypt_status_info status;
                 size_t l;
 
@@ -175,9 +334,38 @@ static int run(int argc, char *argv[]) {
                                 return log_error_errno(r, "Failed to parse options: %m");
                 }
 
-                r = crypt_load(cd, CRYPT_VERITY, NULL);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to load verity superblock: %m");
+                if (arg_superblock) {
+                        p = (struct crypt_params_verity) {
+                                .fec_device = arg_fec_what,
+                                .hash_area_offset = arg_hash_offset,
+                                .fec_area_offset = arg_fec_offset,
+                                .fec_roots = arg_fec_roots,
+                        };
+
+                        r = crypt_load(cd, CRYPT_VERITY, &p);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to load verity superblock: %m");
+                } else {
+                        p = (struct crypt_params_verity) {
+                                .hash_name = arg_hash,
+                                .data_device = data_device,
+                                .fec_device = arg_fec_what,
+                                .salt = arg_salt,
+                                .salt_size = arg_salt_size,
+                                .hash_type = arg_format,
+                                .data_block_size = arg_data_block_size,
+                                .hash_block_size = arg_hash_block_size,
+                                .data_size = arg_data_blocks,
+                                .hash_area_offset = arg_hash_offset,
+                                .fec_area_offset = arg_fec_offset,
+                                .fec_roots = arg_fec_roots,
+                                .flags = CRYPT_VERITY_NO_HEADER,
+                        };
+
+                        r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to format verity superblock: %m");
+                }
 
                 r = crypt_set_data_device(cd, data_device);
                 if (r < 0)
index bf0b1decb421f05a7256473476c60e0f4c4b1ccf..27be7bdf56fdbaed4c12f21da4661a5a39ed93d4 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "alloc-util.h"
 #include "blockdev-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "devnum-util.h"
 #include "escape.h"
 #include "main-func.h"
@@ -21,7 +21,7 @@ static int make_volatile(const char *path) {
 
         assert(path);
 
-        r = chase_symlinks("/usr", path, CHASE_PREFIX_ROOT, &old_usr, NULL);
+        r = chase("/usr", path, CHASE_PREFIX_ROOT, &old_usr, NULL);
         if (r < 0)
                 return log_error_errno(r, "/usr not available in old root: %m");
 
index 3905d4bfe1955d12e4bcf843bab42d620c98dc68..a174648f5337d32dbb041336c07ca06ac6792d37 100644 (file)
@@ -350,7 +350,8 @@ XdgAutostartService *xdg_autostart_service_parse_desktop(const char *path) {
         r = config_parse(NULL, service->path, NULL,
                          "Desktop Entry\0",
                          xdg_config_item_table_lookup, items,
-                         CONFIG_PARSE_WARN, service,
+                         CONFIG_PARSE_RELAXED | CONFIG_PARSE_WARN,
+                         service,
                          NULL);
         /* If parsing failed, only hide the file so it will still mask others. */
         if (r < 0) {
index 49cf6845969c49717925074f0304e78a23f2b6d2..9070d0c60c9caba2d0d6abe1d2ab33ddf8cbc165 100644 (file)
@@ -262,3 +262,39 @@ More about query suites here: https://codeql.github.com/docs/codeql-cli/creating
 The results are then located in the `results.csv` file as a comma separated
 values list (obviously), which is the most human-friendly output format the
 CodeQL utility provides (so far).
+
+Code coverage
+=============
+
+We have a daily cron job in CentOS CI which runs all unit and integration tests,
+collects coverage using gcov/lcov, and uploads the report to Coveralls[0]. In
+order to collect the most accurate coverage information, some measures have
+to be taken regarding sandboxing, namely:
+
+ - ProtectSystem= and ProtectHome= need to be turned off
+ - the $BUILD_DIR with necessary .gcno files needs to be present in the image
+   and needs to be writable by all processes
+
+The first point is relatively easy to handle and is handled automagically by
+our test "framework" by creating necessary dropins.
+
+Making the $BUILD_DIR accessible to _everything_ is slightly more complicated.
+First, and foremost, the $BUILD_DIR has a POSIX ACL that makes it writable
+to everyone. However, this is not enough in some cases, like for services
+that use DynamicUser=yes, since that implies ProtectSystem=strict that can't
+be turned off. A solution to this is to use ReadWritePaths=$BUILD_DIR, which
+works for the majority of cases, but can't be turned on globally, since
+ReadWritePaths= creates its own mount namespace which might break some
+services. Hence, the ReadWritePaths=$BUILD_DIR is enabled for all services
+with the `test-` prefix (i.e. test-foo.service or test-foo-bar.service), both
+in the system and the user managers.
+
+So, if you're considering writing an integration test that makes use
+of DynamicUser=yes, or other sandboxing stuff that implies it, please prefix
+the test unit (be it a static one or a transient one created via systemd-run),
+with `test-`, unless the test unit needs to be able to install mount points
+in the main mount namespace - in that case use IGNORE_MISSING_COVERAGE=yes
+in the test definition (i.e. TEST-*-NAME/test.sh), which will skip the post-test
+check for missing coverage for the respective test.
+
+[0] https://coveralls.io/github/systemd/systemd
index 80cb82a50d110f650c16ef328e3718624b0c2d9f..3d766db3e744e4ec094e88b11e9dc9c7eca502b4 100755 (executable)
@@ -20,6 +20,13 @@ $KERNEL_APPEND
 # shellcheck source=test/test-functions
 . "${TEST_BASE_DIR:?}/test-functions"
 
+test_append_files() {
+    if get_bool "$LOOKS_LIKE_SUSE"; then
+        dinfo "Install the unit test binaries needed by the TEST-02-UNITTESTS at runtime"
+        inst_recursive "${SOURCE_DIR}/unit-tests"
+    fi
+}
+
 check_result_nspawn() {
     check_result_nspawn_unittests "${1}"
 }
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:?}"
 }
 
index 80ce7926ab14c1f76b83be151642f6163a2aca35..61e8e7d1c42887844b9361f1a89478dd14c4642f 100755 (executable)
@@ -3,8 +3,6 @@
 set -e
 
 TEST_DESCRIPTION="Test queue signal logic"
-# Ignore gcov complaints caused by DynamicUser=true
-IGNORE_MISSING_COVERAGE=yes
 
 # shellcheck source=test/test-functions
 . "$TEST_BASE_DIR/test-functions"
index 95c8581238f3d6ccf5a0b93fe3609b50996765ab..4f9c896492e1fbf180a8930ad0665dc3f6571ba4 100755 (executable)
@@ -3,8 +3,6 @@
 set -e
 
 TEST_DESCRIPTION="Test Memory Pressure handling"
-# Ignore gcov complaints caused by DynamicUser=true
-IGNORE_MISSING_COVERAGE=yes
 
 # shellcheck source=test/test-functions
 . "$TEST_BASE_DIR/test-functions"
diff --git a/test/TEST-80-NOTIFYACCESS/Makefile b/test/TEST-80-NOTIFYACCESS/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-80-NOTIFYACCESS/test.sh b/test/TEST-80-NOTIFYACCESS/test.sh
new file mode 100755 (executable)
index 0000000..8ec5b1b
--- /dev/null
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+
+TEST_DESCRIPTION="test NotifyAccess through sd-notify"
+
+# shellcheck source=test/test-functions
+. "${TEST_BASE_DIR:?}/test-functions"
+
+do_test "$@"
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 "$@"
diff --git a/test/auxv/.gitattributes b/test/auxv/.gitattributes
new file mode 100644 (file)
index 0000000..58e3ff4
--- /dev/null
@@ -0,0 +1,3 @@
+/*.* -whitespace
+/*.* binary
+/*.* generated
diff --git a/test/auxv/bash.riscv64 b/test/auxv/bash.riscv64
new file mode 100644 (file)
index 0000000..273a468
Binary files /dev/null and b/test/auxv/bash.riscv64 differ
diff --git a/test/auxv/cat.s390x b/test/auxv/cat.s390x
new file mode 100644 (file)
index 0000000..aa76441
Binary files /dev/null and b/test/auxv/cat.s390x differ
diff --git a/test/auxv/dbus-broker-launch.aarch64 b/test/auxv/dbus-broker-launch.aarch64
new file mode 100644 (file)
index 0000000..3a05e3c
Binary files /dev/null and b/test/auxv/dbus-broker-launch.aarch64 differ
diff --git a/test/auxv/dbus-broker-launch.amd64 b/test/auxv/dbus-broker-launch.amd64
new file mode 100644 (file)
index 0000000..21965e8
Binary files /dev/null and b/test/auxv/dbus-broker-launch.amd64 differ
diff --git a/test/auxv/polkitd.aarch64 b/test/auxv/polkitd.aarch64
new file mode 100644 (file)
index 0000000..ff1ea9f
Binary files /dev/null and b/test/auxv/polkitd.aarch64 differ
diff --git a/test/auxv/resolved.arm32 b/test/auxv/resolved.arm32
new file mode 100644 (file)
index 0000000..7d05f3b
Binary files /dev/null and b/test/auxv/resolved.arm32 differ
diff --git a/test/auxv/sleep.i686 b/test/auxv/sleep.i686
new file mode 100644 (file)
index 0000000..d0b5f2c
Binary files /dev/null and b/test/auxv/sleep.i686 differ
diff --git a/test/auxv/sleep32.i686 b/test/auxv/sleep32.i686
new file mode 100644 (file)
index 0000000..f52f512
Binary files /dev/null and b/test/auxv/sleep32.i686 differ
diff --git a/test/auxv/sleep64.amd64 b/test/auxv/sleep64.amd64
new file mode 100644 (file)
index 0000000..c3c7ed4
Binary files /dev/null and b/test/auxv/sleep64.amd64 differ
diff --git a/test/auxv/sudo.aarch64 b/test/auxv/sudo.aarch64
new file mode 100644 (file)
index 0000000..e49ce6a
Binary files /dev/null and b/test/auxv/sudo.aarch64 differ
diff --git a/test/auxv/sudo.amd64 b/test/auxv/sudo.amd64
new file mode 100644 (file)
index 0000000..91e4646
Binary files /dev/null and b/test/auxv/sudo.amd64 differ
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 1d9ea4905a4b384a5ffab8e05c5bc776459db482..b9992fae475c62eb8fa73ffecd9f15111e26ce7b 100644 (file)
@@ -1,62 +1,33 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
 if install_tests
-        testdata_dir = testsdir + '/testdata/'
-
-        install_subdir('journal-data',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('test-execute',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('test-fstab-generator',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('test-path',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('test-path-util',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('test-umount',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('test-network-generator-conversion',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-03.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-04.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-06.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-10.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-11.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-16.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-28.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-30.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-52.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
-        install_subdir('testsuite-63.units',
-                       exclude_files : '.gitattributes',
-                       install_dir : testdata_dir)
+        foreach subdir : [
+                'auxv',
+                'journal-data',
+                'units',
+                'test-execute',
+                'test-fstab-generator',
+                'test-path',
+                'test-path-util',
+                'test-umount',
+                'test-network',
+                'test-network-generator-conversion',
+                'testsuite-03.units',
+                'testsuite-04.units',
+                'testsuite-06.units',
+                'testsuite-10.units',
+                'testsuite-11.units',
+                'testsuite-16.units',
+                'testsuite-28.units',
+                'testsuite-30.units',
+                'testsuite-52.units',
+                'testsuite-63.units',
+                'testsuite-80.units',
+        ]
+                install_subdir(subdir,
+                               exclude_files : '.gitattributes',
+                               install_dir : testdata_dir)
+        endforeach
 
         install_data(kbd_model_map,
                      install_dir : testdata_dir + '/test-keymap-util')
@@ -87,6 +58,12 @@ if install_tests
         install_data('create-busybox-container',
                      install_mode : 'rwxr-xr-x',
                      install_dir : testdata_dir)
+
+        # The unit tests implemented as shell scripts expect to find testdata/
+        # in the directory where they are stored.
+        meson.add_install_script(meson_make_symlink,
+                                 testdata_dir,
+                                 unittestsdir / 'testdata')
 endif
 
 test_bootctl_json_sh = find_program('test-bootctl-json.sh')
@@ -104,7 +81,7 @@ test_sysusers_sh = configure_file(
         configuration : conf)
 if install_tests and conf.get('ENABLE_SYSUSERS') == 1
         install_data(test_sysusers_sh,
-                     install_dir : testsdir)
+                     install_dir : unittestsdir)
         install_subdir('test-sysusers',
                        exclude_files : '.gitattributes',
                        install_dir : testdata_dir)
@@ -115,7 +92,7 @@ endif
 test_compare_versions_sh = files('test-compare-versions.sh')
 if install_tests
         install_data(test_compare_versions_sh,
-                     install_dir : testsdir)
+                     install_dir : unittestsdir)
 endif
 
 ############################################################
@@ -137,11 +114,11 @@ if install_tests
 
         install_data('test-fstab-generator.sh',
                      install_mode : 'rwxr-xr-x',
-                     install_dir : testsdir)
+                     install_dir : unittestsdir)
 
         install_data('test-network-generator-conversion.sh',
                      install_mode : 'rwxr-xr-x',
-                     install_dir : testsdir)
+                     install_dir : unittestsdir)
 endif
 
 ############################################################
diff --git a/test/mkosi.build.networkd-test b/test/mkosi.build.networkd-test
deleted file mode 100755 (executable)
index 25c935e..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: LGPL-2.1-or-later
-set -ex
-
-# First, source in the main build script
-. "$SRCDIR"/mkosi.build
-
-mkdir -p "$DESTDIR"/usr/local/bin
-cp "$SRCDIR"/test/networkd-test.py "$DESTDIR"/usr/local/bin/networkd-test.py
-
-mkdir -p "$DESTDIR"/etc/systemd/system
-cat >"$DESTDIR"/etc/systemd/system/networkd-test.service <<EOF
-[Unit]
-Description=networkd test service
-SuccessAction=exit
-FailureAction=exit
-
-[Service]
-ExecStart=/usr/local/bin/networkd-test.py
-EOF
-
-mkdir -p "$DESTDIR"/etc/systemd/system/multi-user.target.wants/
-ln -s ../networkd-test.service "$DESTDIR"/etc/systemd/system/multi-user.target.wants/
-
-systemctl --root="$DESTDIR" disable systemd-networkd.service
diff --git a/test/mkosi.default.networkd-test b/test/mkosi.default.networkd-test
deleted file mode 100644 (file)
index fe15f39..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-#
-# Puts together an nspawn container and runs networkd-test.py in it, inside a
-# network namespace and everything. Run this with
-#
-#   mkosi -C test --default=mkosi.default.networkd-test boot
-#
-# This will start the test and eventually exit with success in case the test
-# succeeded.
-
-[Distribution]
-Distribution=fedora
-Release=33
-
-[Output]
-Format=raw_btrfs
-Bootable=yes
-OutputDirectory=../mkosi.output
-Output=networkd-test.raw
-
-[Partitions]
-RootSize=3G
-
-[Content]
-BuildPackages=
-        audit-libs-devel
-        bzip2-devel
-        cryptsetup-devel
-        dbus-devel
-        diffutils
-        docbook-style-xsl
-        elfutils-devel
-        gcc
-        gettext
-        git
-        gnutls-devel
-        gperf
-        hostname
-        iptables-devel
-        kmod-devel
-        libacl-devel
-        libblkid-devel
-        libcap-devel
-        libcurl-devel
-        libgcrypt-devel
-        libidn2-devel
-        libmicrohttpd-devel
-        libmount-devel
-        libseccomp-devel
-        libselinux-devel
-        libxkbcommon-devel
-        libxslt
-        lz4
-        lz4-devel
-        meson
-        ninja-build
-        pam-devel
-        pcre2-devel
-        perl(IPC::SysV)
-        perl(Time::HiRes)
-        pkgconfig
-        python3-devel
-        python3-lxml
-        qrencode-devel
-        tree
-
-Packages=
-        dnsmasq
-        iproute
-        libidn2
-        polkit
-        python3
-
-# Share caches with the top-level mkosi
-BuildDirectory=../mkosi/mkosi.builddir
-Cache=../mkosi/mkosi.cache
-
-# Run our own script
-BuildScript=mkosi.build.networkd-test
-
-BuildSources=..
-NSpawnSettings=mkosi.nspawn.networkd-test
diff --git a/test/mkosi.nspawn.networkd-test b/test/mkosi.nspawn.networkd-test
deleted file mode 100644 (file)
index f624f24..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1-or-later
-
-[Network]
-Private=yes
index 4b180cc8df0814f7a3be5ce5625f9df385d9b4a1..2d6709b703c9bac6b38b06a48aef69221637bc0a 100755 (executable)
@@ -32,7 +32,7 @@ def argument_parser():
 
 opts = argument_parser().parse_args()
 
-unittestdir = pathlib.Path(__file__).parent.absolute()
+unittestdir = pathlib.Path(__file__).parent.absolute() / 'unit-tests'
 
 tests = list(unittestdir.glob('test-*'))
 if opts.unsafe:
index fde5fbd1de175d6017cf91e33b8448ad340bd7d8..4d7c468241b78e7168c60a3367a6dec97aab7622 100755 (executable)
@@ -28,6 +28,15 @@ command -v jq >/dev/null || {
 "$bootctl" -R || test "$?" -eq 80
 "$bootctl" -RR || test "$?" -eq 80
 
+# regression tests for
+# https://github.com/systemd/systemd/pull/27199#issuecomment-1511387731
+if ret=$("$bootctl" --print-esp-path); then
+    test "$ret" = "/efi" -o "$ret" = "/boot" -o "$ret" = "/boot/efi"
+fi
+if ret=$("bootctl" --print-boot-path); then
+    test "$ret" = "/efi" -o "$ret" = "/boot" -o "$ret" = "/boot/efi"
+fi
+
 if "$bootctl" -R > /dev/null ; then
     P=$("$bootctl" -R)
     PP=$("$bootctl" -RR)
index 820cb93e95409d87b25bc992fb8f3391317705fd..cef2a780d4c8464937efc9711f6e3417c489b4e4 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}"
@@ -107,10 +106,10 @@ fi
 TEST_BASE_DIR=${TEST_BASE_DIR:-$(realpath "$(dirname "${BASH_SOURCE[0]}")")}
 TEST_UNITS_DIR="$(realpath "$TEST_BASE_DIR/units")"
 SOURCE_DIR=$(realpath "$TEST_BASE_DIR/..")
-TOOLS_DIR="$SOURCE_DIR/tools"
 # These variables are used by test scripts
-export TEST_BASE_DIR TEST_UNITS_DIR SOURCE_DIR TOOLS_DIR
+export TEST_BASE_DIR TEST_UNITS_DIR SOURCE_DIR
 
+TOOLS_DIR="$SOURCE_DIR/tools"
 # note that find-build-dir.sh will return $BUILD_DIR if provided, else it will try to find it
 if get_bool "${NO_BUILD:=}"; then
     BUILD_DIR="$SOURCE_DIR"
@@ -133,7 +132,14 @@ if [ -z "$TESTFILE" ]; then
     exit 1
 fi
 TESTNAME="$(basename "$(dirname "$(realpath "$TESTFILE")")")"
-STATEDIR="$BUILD_DIR/test/$TESTNAME"
+
+WORKDIR="/var/tmp/systemd-tests"
+if get_bool "${NO_BUILD:=}"; then
+    STATEDIR="$WORKDIR/$TESTNAME"
+else
+    STATEDIR="$BUILD_DIR/test/$TESTNAME"
+fi
+
 STATEFILE="$STATEDIR/.testdir"
 IMAGESTATEDIR="$STATEDIR/.."
 TESTLOG="$STATEDIR/test.log"
@@ -189,6 +195,7 @@ BASICTOOLS=(
     losetup
     lz4cat
     mkfifo
+    mknod
     mktemp
     modprobe
     mount
@@ -207,11 +214,13 @@ BASICTOOLS=(
     seq
     setfattr
     setfont
+    setpriv
     setsid
     sfdisk
     sh
     sleep
     stat
+    stty
     su
     sulogin
     sysctl
@@ -254,7 +263,6 @@ DEBUGTOOLS=(
     route
     sort
     strace
-    stty
     tty
     vi
     /usr/libexec/vi
@@ -290,7 +298,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}"
@@ -504,6 +511,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
@@ -678,6 +686,8 @@ EOF
         mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
         grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app0"
         echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
+        ( echo "${version_id}"
+          echo "SYSEXT_IMAGE_ID=app" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
         cat >"$initdir/usr/lib/systemd/system/app0.service" <<EOF
 [Service]
 Type=oneshot
@@ -703,6 +713,8 @@ EOF
         grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
         ( echo "${version_id}"
           echo "SYSEXT_SCOPE=portable"
+          echo "SYSEXT_IMAGE_ID=app"
+          echo "SYSEXT_IMAGE_VERSION=1"
           echo "PORTABLE_PREFIXES=app1" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
         setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
         cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
@@ -761,7 +773,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
@@ -1144,7 +1155,7 @@ install_debian_systemd() {
         ddebug "Install debian files from package $deb"
         for file in $files; do
             [ -e "$file" ] || continue
-            [ -d "$file" ] && continue
+            [ ! -L "$file" ] && [ -d "$file" ] && continue
             inst "$file"
         done
     done < <(grep -E '^Package:' "${SOURCE_DIR}/debian/control" | cut -d ':' -f 2)
@@ -1174,23 +1185,13 @@ install_suse_systemd() {
         ddebug "Install files from package $p"
         while read -r f; do
             [ -e "$f" ] || continue
-            [ -d "$f" ] && continue
+            [ ! -L "$f" ] && [ -d "$f" ] && continue
             inst "$f"
         done < <(rpm -ql "$p")
     done
 
-    # Embed the files needed by the extended testsuite at runtime. Also include
-    # the unit tests needed by TEST-02-UNITTESTS. This is mostly equivalent to
-    # what `ninja install` does for the tests when '-Dinstall-tests=true'.
-    #
-    # Why? openSUSE ships a package named 'systemd-testsuite' which contains
-    # the minimal set of files that allows to run the testsuite on the host (as
-    # long as it runs an equivalent version of systemd) getting rid of the
-    # hassles of fetching, configuring, building the source code.
-    dinfo "Install the files needed by the tests at runtime"
-    image_install "${SOURCE_DIR}"/test-*
+    dinfo "Install the data needed by the tests at runtime"
     inst_recursive "${SOURCE_DIR}/testdata"
-    inst_recursive "${SOURCE_DIR}/manual"
 
     # On openSUSE, this directory is not created at package install, at least
     # for now.
@@ -1239,11 +1240,14 @@ install_systemd() {
         mkdir -p "$initdir/etc/systemd/system/service.d/"
         echo -e "[Service]\nProtectSystem=no\nProtectHome=no\n" >"$initdir/etc/systemd/system/service.d/99-gcov-override.conf"
         # Similarly, set ReadWritePaths= to the $BUILD_DIR in the test image
-        # to make the coverage work with units utilizing DynamicUser=yes. Do
-        # this only for services from TEST-20, as setting this system-wide
-        # has many undesirable side-effects
-        mkdir -p "$initdir/etc/systemd/system/test20-.service.d/"
-        echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/system/test20-.service.d/99-gcov-rwpaths-override.conf"
+        # to make the coverage work with units using DynamicUser=yes. Do this
+        # only for services with test- prefix, as setting this system-wide
+        # has many undesirable side-effects, as it creates its own namespace.
+        mkdir -p "$initdir/etc/systemd/system/test-.service.d/"
+        echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/system/test-.service.d/99-gcov-rwpaths-override.conf"
+        # Ditto, but for the user daemon
+        mkdir -p "$initdir/etc/systemd/user/test-.service.d/"
+        echo -e "[Service]\nReadWritePaths=${BUILD_DIR:?}\n" >"$initdir/etc/systemd/user/test-.service.d/99-gcov-rwpaths-override.conf"
     fi
 
     # If we're built with -Dportabled=false, tests with systemd-analyze
@@ -1272,7 +1276,7 @@ get_ldpath() {
 install_missing_libraries() {
     dinfo "Install missing libraries"
     # install possible missing libraries
-    for i in "${initdir:?}"{,/usr}/{sbin,bin}/* "$initdir"{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
+    for i in "${initdir:?}"{,/usr}/{sbin,bin}/* "$initdir"{,/usr}/lib/systemd/{,tests/unit-tests/{,manual/,unsafe/}}*; do
         LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath "$i")" inst_libs "$i"
     done
 
@@ -1349,8 +1353,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))
@@ -1361,11 +1365,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
@@ -1488,21 +1492,28 @@ check_coverage_reports() {
         dest="${TESTDIR:?}/coverage-info"
     fi
 
+    if [[ ! -e "${TESTDIR:?}/coverage-base" ]]; then
+        # This shouldn't happen, as the report is generated during the setup
+        # phase (test_setup()).
+        derror "Missing base coverage report"
+        return 1
+    fi
+
     # Create a coverage report that will later be uploaded. Remove info about
     # system libraries/headers, as we don't really care about them.
+    lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}.new"
     if [[ -f "$dest" ]]; then
         # If the destination report file already exists, don't overwrite it, but
-        # dump the new report in a temporary file and then merge it with the already
-        # present one - this usually happens when running both "parts" of a test
-        # in one run (the qemu and the nspawn part).
-        lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}.new"
-        lcov --remove "${dest}.new" -o "${dest}.new" '/usr/include/*' '/usr/lib/*'
+        # merge it with the already present one - this usually happens when
+        # running both "parts" of a test in one run (the qemu and the nspawn part).
         lcov --add-tracefile "${dest}" --add-tracefile "${dest}.new" -o "${dest}"
-        rm -f "${dest}.new"
     else
-        lcov --directory "${root}/${BUILD_DIR:?}" --capture --output-file "${dest}"
-        lcov --remove "${dest}" -o "${dest}" '/usr/include/*' '/usr/lib/*'
+        # If there's no prior coverage report, merge the new one with the base
+        # report we did during the setup phase (see test_setup()).
+        lcov --add-tracefile "${TESTDIR:?}/coverage-base" --add-tracefile "${dest}.new" -o "${dest}"
     fi
+    lcov --remove "$dest" -o "$dest" '/usr/include/*' '/usr/lib/*'
+    rm -f "${dest}.new"
 
     # If the test logs contain lines like:
     #
@@ -1512,6 +1523,10 @@ check_coverage_reports() {
     # usually due to the sandbox being too restrictive (e.g. ProtectSystem=yes,
     # ProtectHome=yes) or the $BUILD_DIR being inaccessible to non-root users - see
     # `setfacl` stuff in install_compiled_systemd().
+    #
+    # Also, a note: some tests, like TEST-46, overmount /home with tmpfs, which
+    # means if your build dir is under /home/your-user (which is usually the
+    # case) you might get bogus errors and missing coverage.
     if ! get_bool "${IGNORE_MISSING_COVERAGE:=}" && \
        "${JOURNALCTL:?}" -q --no-pager -D "${root:?}/var/log/journal" --grep "profiling:.+?gcda:[Cc]annot open"; then
         derror "Detected possibly missing coverage, check the journal"
@@ -1719,17 +1734,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"
@@ -2227,7 +2231,7 @@ import_testdir() {
     LOOPDEV="$_LOOPDEV"
     if [[ ! -d "$TESTDIR" ]]; then
         if [[ -z "$TESTDIR" ]]; then
-            TESTDIR="$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX)"
+            TESTDIR="$(mktemp --tmpdir=$WORKDIR -d -t systemd-test.XXXXXX)"
         else
             mkdir -p "$TESTDIR"
         fi
@@ -2639,12 +2643,12 @@ inst_binary() {
     # Same as above, but we need to wrap certain libraries unconditionally
     #
     # chown, getent, login, su, useradd, userdel - dlopen() (not only) systemd's PAM modules
-    # ls, mkfs.*, mksquashfs, mkswap, stat
+    # ls, mkfs.*, mksquashfs, mkswap, setpriv, stat
     #       - pull in nss_systemd with certain options (like ls -l) when
     #         nsswitch.conf uses [SUCCESS=merge] (like on Arch Linux)
     # delv, dig - pull in nss_resolve if `resolve` is in nsswitch.conf
     # tar - called by machinectl in TEST-25
-    bin_rx='/(chown|delv|dig|getent|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|stat|su|tar|useradd|userdel)$'
+    bin_rx='/(chown|delv|dig|getent|login|ls|mkfs\.[a-z0-9]+|mksquashfs|mkswap|setpriv|stat|su|tar|useradd|userdel)$'
     if get_bool "$IS_BUILT_WITH_ASAN" && [[ "$bin" =~ $bin_rx ]]; then
         wrap_binary=1
     fi
@@ -3115,6 +3119,12 @@ test_setup() {
             printf '[Service]\nStandardOutput=journal+console\nStandardError=journal+console' >"$dropin_dir/99-stdout.conf"
         fi
 
+        if get_bool "$IS_BUILT_WITH_COVERAGE"; then
+            # Do an initial coverage capture, to make sure the final report includes
+            # files that the tests didn't touch at all
+            lcov --initial --capture --directory "${initdir}/${BUILD_DIR:?}" --output-file "${TESTDIR:?}/coverage-base"
+        fi
+
         if get_bool "$hook_defined"; then
             test_append_files "${initdir:?}"
         fi
@@ -3183,6 +3193,7 @@ do_test() {
         [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break
     done
 
+    mkdir -p "$WORKDIR"
     mkdir -p "$STATEDIR"
 
     import_testdir
@@ -3235,7 +3246,8 @@ do_test() {
                 fi
                 test_cleanup
                 if [ $ret -eq 0 ]; then
-                    rm "$TESTLOG"
+                    # $TESTLOG is in $STATEDIR, so clean it up only on success
+                    [[ -n "$STATEDIR" ]] && rm -vfr "$STATEDIR"
                     echo "[OK]"
                 else
                     echo "[FAILED]"
index 4b62a30c9c43f02c21832216fc9fd60f2901dd15..f88878435cc62eb53a9532edc85739d70437cfcd 100755 (executable)
@@ -6,7 +6,7 @@
 # systemd-networkd tests
 
 # These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM,
-# simply run this file which can be found in the VM at /root/src/test/test-network/systemd-networkd-tests.py.
+# simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py.
 
 import argparse
 import errno
index 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 fab37960bc330fff04efd7b450a67ecc42321c3f..3bcc87561fd5b1ac97d20aae798b5de95e55d48d 100755 (executable)
@@ -4,112 +4,116 @@ set -e
 
 SYSUSERS="${1:-systemd-sysusers}"
 
-[ -e "$(dirname $0)/../systemd-runtest.env" ] && . "$(dirname $0)/../systemd-runtest.env"
+# shellcheck disable=SC1090
+[ -e "$(dirname "$0")/../systemd-runtest.env" ] && . "$(dirname "$0")/../systemd-runtest.env"
 SYSTEMD_TEST_DATA=${SYSTEMD_TEST_DATA:-@SYSTEMD_TEST_DATA@}
 SOURCE=$SYSTEMD_TEST_DATA/test-sysusers
 
 TESTDIR=$(mktemp --tmpdir --directory "test-sysusers.XXXXXXXXXX")
+# shellcheck disable=SC2064
 trap "rm -rf '$TESTDIR'" EXIT INT QUIT PIPE
 
 prepare_testdir() {
-    mkdir -p $TESTDIR/etc/sysusers.d/
-    mkdir -p $TESTDIR/usr/lib/sysusers.d/
-    rm -f $TESTDIR/etc/*{passwd,group,shadow}
+    mkdir -p "$TESTDIR/etc/sysusers.d/"
+    mkdir -p "$TESTDIR/usr/lib/sysusers.d/"
+    rm -f "$TESTDIR"/etc/*{passwd,group,shadow}
     for i in $1.initial-{passwd,group,shadow}; do
-        test -f $i && cp $i $TESTDIR/etc/${i#*.initial-}
+        test -f "$i" && cp "$i" "$TESTDIR/etc/${i#*.initial-}"
     done
     return 0
 }
 
+# shellcheck disable=SC2050
 [ @SYSTEM_UID_MAX@ -lt @SYSTEM_GID_MAX@ ] && system_guid_max=@SYSTEM_UID_MAX@ || system_guid_max=@SYSTEM_GID_MAX@
 
 preprocess() {
     m=${2:-$system_guid_max}
 
+    # shellcheck disable=SC2140
     sed -e "s/SYSTEM_UGID_MAX/$m/g;
             s#NOLOGIN#@NOLOGIN@#g" "$1"
 }
 
 compare() {
-    if ! diff -u $TESTDIR/etc/passwd <(preprocess $1.expected-passwd $3); then
+    if ! diff -u "$TESTDIR/etc/passwd" <(preprocess "$1.expected-passwd" "$3"); then
         echo "**** Unexpected output for $f $2"
         exit 1
     fi
 
-    if ! diff -u $TESTDIR/etc/group <(preprocess $1.expected-group $3); then
+    if ! diff -u "$TESTDIR/etc/group" <(preprocess "$1.expected-group" "$3"); then
         echo "**** Unexpected output for $f $2"
         exit 1
     fi
 }
 
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
 
 # happy tests
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
     echo "*** Running $f"
-    prepare_testdir ${f%.input}
-    cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
-    $SYSUSERS --root=$TESTDIR
+    prepare_testdir "${f%.input}"
+    cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+    $SYSUSERS --root="$TESTDIR"
 
-    compare ${f%.*} ""
+    compare "${f%.*}" ""
 done
 
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
     echo "*** Running $f on stdin"
-    prepare_testdir ${f%.input}
-    touch $TESTDIR/etc/sysusers.d/test.conf
-    cat $f | $SYSUSERS --root=$TESTDIR -
+    prepare_testdir "${f%.input}"
+    touch "$TESTDIR/etc/sysusers.d/test.conf"
+    $SYSUSERS --root="$TESTDIR" - <"$f"
 
-    compare ${f%.*} "on stdin"
+    compare "${f%.*}" "on stdin"
 done
 
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
     echo "*** Running $f on stdin with --replace"
-    prepare_testdir ${f%.input}
-    touch $TESTDIR/etc/sysusers.d/test.conf
+    prepare_testdir "${f%.input}"
+    touch "$TESTDIR/etc/sysusers.d/test.conf"
     # this overrides test.conf which is masked on disk
-    cat $f | $SYSUSERS --root=$TESTDIR --replace=/etc/sysusers.d/test.conf -
+    $SYSUSERS --root="$TESTDIR" --replace=/etc/sysusers.d/test.conf - <"$f"
     # this should be ignored
-    cat $SOURCE/test-1.input | $SYSUSERS --root=$TESTDIR --replace=/usr/lib/sysusers.d/test.conf -
+    $SYSUSERS --root="$TESTDIR" --replace=/usr/lib/sysusers.d/test.conf - <"$SOURCE/test-1.input"
 
-    compare ${f%.*} "on stdin with --replace"
+    compare "${f%.*}" "on stdin with --replace"
 done
 
 # test --inline
 echo "*** Testing --inline"
-prepare_testdir $SOURCE/inline
+prepare_testdir "$SOURCE/inline"
 # copy a random file to make sure it is ignored
-cp $f $TESTDIR/etc/sysusers.d/confuse.conf
-$SYSUSERS --root=$TESTDIR --inline \
+cp "$f" "$TESTDIR/etc/sysusers.d/confuse.conf"
+$SYSUSERS --root="$TESTDIR" --inline \
           "u     u1   222 -     - /bin/zsh" \
           "g     g1   111"
 
-compare $SOURCE/inline "(--inline)"
+compare "$SOURCE/inline" "(--inline)"
 
 # test --replace
 echo "*** Testing --inline with --replace"
-prepare_testdir $SOURCE/inline
+prepare_testdir "$SOURCE/inline"
 # copy a random file to make sure it is ignored
-cp $f $TESTDIR/etc/sysusers.d/confuse.conf
-$SYSUSERS --root=$TESTDIR \
+cp "$f" "$TESTDIR/etc/sysusers.d/confuse.conf"
+$SYSUSERS --root="$TESTDIR" \
           --inline \
           --replace=/etc/sysusers.d/confuse.conf \
           "u     u1   222 -     - /bin/zsh" \
           "g     g1   111"
 
-compare $SOURCE/inline "(--inline --replace=…)"
+compare "$SOURCE/inline" "(--inline --replace=…)"
 
 echo "*** Testing --inline with no /etc"
-rm -rf $TESTDIR/etc
-$SYSUSERS --root=$TESTDIR --inline \
+rm -rf "${TESTDIR:?}/etc"
+$SYSUSERS --root="$TESTDIR" --inline \
           "u     u1   222 -     - /bin/zsh" \
           "g     g1   111"
 
-compare $SOURCE/inline "(--inline)"
+compare "$SOURCE/inline" "(--inline)"
 
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
 
-cat >$TESTDIR/etc/login.defs <<EOF
+cat >"$TESTDIR/etc/login.defs" <<EOF
 SYS_UID_MIN abcd
 SYS_UID_MAX abcd
 SYS_GID_MIN abcd
@@ -128,42 +132,44 @@ SYS_GID_MIN999
 SYS_GID_MAX999
 EOF
 
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
     echo "*** Running $f (with login.defs)"
-    prepare_testdir ${f%.input}
-    cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
-    $SYSUSERS --root=$TESTDIR
+    prepare_testdir "${f%.input}"
+    cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+    $SYSUSERS --root="$TESTDIR"
 
+    # shellcheck disable=SC2050
     [ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
-    compare ${f%.*} "(with login.defs)" $bound
+    compare "${f%.*}" "(with login.defs)" $bound
 done
 
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
 
-mv $TESTDIR/etc/login.defs $TESTDIR/etc/login.defs.moved
-ln -s ../../../../../etc/login.defs.moved $TESTDIR/etc/login.defs
+mv "$TESTDIR/etc/login.defs" "$TESTDIR/etc/login.defs.moved"
+ln -s ../../../../../etc/login.defs.moved "$TESTDIR/etc/login.defs"
 
-for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
+for f in $(find "$SOURCE"/test-*.input | sort -V); do
     echo "*** Running $f (with login.defs symlinked)"
-    prepare_testdir ${f%.input}
-    cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
-    $SYSUSERS --root=$TESTDIR
+    prepare_testdir "${f%.input}"
+    cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+    $SYSUSERS --root="$TESTDIR"
 
+    # shellcheck disable=SC2050
     [ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
-    compare ${f%.*} "(with login.defs symlinked)" $bound
+    compare "${f%.*}" "(with login.defs symlinked)" $bound
 done
 
-rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
+rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/*
 
 # tests for error conditions
-for f in $(ls -1 $SOURCE/unhappy-*.input | sort -V); do
+for f in $(find "$SOURCE"/unhappy-*.input | sort -V); do
     echo "*** Running test $f"
-    prepare_testdir ${f%.input}
-    cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
-    $SYSUSERS --root=$TESTDIR 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >$TESTDIR/err
-    if ! diff -u $TESTDIR/err  ${f%.*}.expected-err; then
+    prepare_testdir "${f%.input}"
+    cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
+    $SYSUSERS --root="$TESTDIR" 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >"$TESTDIR/err"
+    if ! diff -u "$TESTDIR/err"  "${f%.*}.expected-err"; then
         echo "**** Unexpected error output for $f"
-        cat $TESTDIR/err
+        cat "$TESTDIR/err"
         exit 1
     fi
 done
diff --git a/test/testsuite-80.units/fdstore-nopin.service b/test/testsuite-80.units/fdstore-nopin.service
new file mode 100644 (file)
index 0000000..58a687a
--- /dev/null
@@ -0,0 +1,8 @@
+[Service]
+Type=notify
+NotifyAccess=all
+FileDescriptorStoreMax=10
+FileDescriptorStorePreserve=restart
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/fdstore-pin.sh 0
+StandardOutput=journal+console
+StandardError=journal+console
diff --git a/test/testsuite-80.units/fdstore-pin.service b/test/testsuite-80.units/fdstore-pin.service
new file mode 100644 (file)
index 0000000..bc78ee0
--- /dev/null
@@ -0,0 +1,8 @@
+[Service]
+Type=notify
+NotifyAccess=all
+FileDescriptorStoreMax=10
+FileDescriptorStorePreserve=yes
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/fdstore-pin.sh 1
+StandardOutput=journal+console
+StandardError=journal+console
diff --git a/test/testsuite-80.units/fdstore-pin.sh b/test/testsuite-80.units/fdstore-pin.sh
new file mode 100755 (executable)
index 0000000..4cb041a
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+PINNED="$1"
+COUNTER="/tmp/fdstore-invoked.$PINNED"
+FILE="/tmp/fdstore-data.$PINNED"
+
+# This script is called six times: thrice from a service unit where the fdstore
+# is pinned, and thrice where it isn't. The second iteration of each series is
+# a restart, the third a stop followed by a start
+
+if [ -e "$COUNTER" ] ; then
+    read -r N < "$COUNTER"
+else
+    N=0
+fi
+
+echo "Invocation #$N with PINNED=$PINNED."
+
+if [ "$N" -eq 0 ] ; then
+    # First iteration
+    test "${LISTEN_FDS:-0}" -eq 0
+    test ! -e "$FILE"
+    echo waldi > "$FILE"
+    systemd-notify --fd=3 --fdname="fd-$N-$PINNED" 3< "$FILE"
+elif [ "$N" -eq 1 ] || { [ "$N" -eq 2 ] && [ "$PINNED" -eq 1 ]; } ; then
+    # Second iteration, or iteration with pinning on
+    test "${LISTEN_FDS:-0}" -eq 1
+    # We reopen fd #3 here, so that the read offset is at zero each time (hence no <&3 here…)
+    read -r word < /proc/self/fd/3
+    test "$word" = "waldi"
+else
+    test "${LISTEN_FDS:-0}" -eq 0
+    test -e "$FILE"
+fi
+
+if [ "$N" -ge 2 ] ; then
+    rm "$COUNTER" "$FILE"
+else
+    echo $((N + 1)) > "$COUNTER"
+fi
+
+systemd-notify --ready --status="Ready"
+
+exec sleep infinity
diff --git a/test/testsuite-80.units/fdstore-pin.target b/test/testsuite-80.units/fdstore-pin.target
new file mode 100644 (file)
index 0000000..319b7e1
--- /dev/null
@@ -0,0 +1,3 @@
+[Unit]
+After=fdstore-pin.service fdstore-nopin.service
+Wants=fdstore-pin.service fdstore-nopin.service
diff --git a/test/testsuite-80.units/notify.service b/test/testsuite-80.units/notify.service
new file mode 100644 (file)
index 0000000..196b076
--- /dev/null
@@ -0,0 +1,4 @@
+[Service]
+Type=notify
+NotifyAccess=all
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-80.units/test.sh
diff --git a/test/testsuite-80.units/test.sh b/test/testsuite-80.units/test.sh
new file mode 100755 (executable)
index 0000000..565ed8d
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+sync_in() {
+    read -r x < /tmp/syncfifo2
+    test "$x" = "$1"
+}
+
+sync_out() {
+    echo "$1" > /tmp/syncfifo1
+}
+
+export SYSTEMD_LOG_LEVEL=debug
+
+echo "toplevel PID: $BASHPID"
+
+systemd-notify --status="Test starts"
+sync_out a
+sync_in b
+(
+    echo "subshell PID: $BASHPID"
+
+    # Make us main process
+    systemd-notify --pid="$BASHPID"
+
+    # Lock down access to just us
+    systemd-notify "NOTIFYACCESS=main"
+
+    # This should still work
+    systemd-notify --status="Sending READY=1 in an unprivileged process"
+
+    # Send as subprocess of the subshell, this should not work
+    systemd-notify --ready --pid=self --status "BOGUS1"
+
+    sync_out c
+    sync_in d
+
+    # Move main process back to toplevel
+    systemd-notify --pid=parent "MAINPID=$$"
+
+    # Should be dropped again
+    systemd-notify --status="BOGUS2" --pid=parent
+
+    # Apparently, bash will automatically invoke the last command in a subshell
+    # via a simple execve() rather than fork()ing first. But we want that the
+    # previous command uses the subshell's PID, hence let's insert a final,
+    # bogus redundant command as last command to run in the subshell, so that
+    # bash can't optimize things like that.
+    echo "bye"
+)
+
+echo "toplevel again: $BASHPID"
+
+systemd-notify --ready --status="OK"
+systemd-notify "NOTIFYACCESS=none"
+systemd-notify --status="BOGUS3"
+
+sync_out e
+
+exec sleep infinity
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..42c6a77
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+link_endswith() {
+    [[ -h "${1:?}" && "$(readlink "${1:?}")" =~ ${2:?}$ ]]
+}
+
+link_eq() {
+    [[ -h "${1:?}" && "$(readlink "${1:?}")" == "${2:?}" ]]
+}
+
+# Get the value from a 'key=value' assignment
+opt_get_arg() {
+    local arg
+
+    IFS="=" read -r _ arg <<< "${1:?}"
+    test -n "$arg"
+    echo "$arg"
+}
+
+in_initrd() {
+    [[ "${SYSTEMD_IN_INITRD:-0}" -ne 0 ]]
+}
+
+# Check if we're parsing host's fstab in initrd
+in_initrd_host() {
+    in_initrd && [[ "${SYSTEMD_SYSROOT_FSTAB:-/dev/null}" != /dev/null ]]
+}
+
+in_container() {
+    systemd-detect-virt -qc
+}
+
+# Filter out "unwanted" options, i.e. options that the fstab-generator doesn't
+# propagate to the final mount unit
+opt_filter_consumed() {(
+    set +x
+    local opt split_options filtered_options
+
+    IFS="," read -ra split_options <<< "${1:?}"
+    for opt in "${split_options[@]}"; do
+        if [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+            continue
+        fi
+
+        filtered_options+=("$opt")
+    done
+
+    IFS=","; printf "%s" "${filtered_options[*]}"
+)}
+
+# Run the given generator $1 with target directory $2 - clean the target
+# directory beforehand
+run_and_list() {
+    local generator="${1:?}"
+    local out_dir="${2:?}"
+    local environ
+
+    # If $PID1_ENVIRON is set temporarily overmount /proc/1/environ with
+    # a temporary file that contains contents of $PID1_ENVIRON. This is
+    # necessary in cases where the generator reads the environment through
+    # getenv_for_pid(1, ...) or similar like getty-generator does.
+    #
+    # Note: $PID1_ENVIRON should be a NUL separated list of env assignments
+    if [[ -n "${PID1_ENVIRON:-}" ]]; then
+        environ="$(mktemp)"
+        echo -ne "${PID1_ENVIRON}\0" >"${environ:?}"
+        mount -v --bind "$environ" /proc/1/environ
+    fi
+
+    rm -fr "${out_dir:?}"/*
+    mkdir -p "$out_dir"/{normal,early,late}
+    SYSTEMD_LOG_LEVEL="${SYSTEMD_LOG_LEVEL:-debug}" "$generator" "$out_dir/normal" "$out_dir/early" "$out_dir/late"
+    ls -lR "$out_dir"
+
+    if [[ -n "${environ:-}" ]]; then
+        umount /proc/1/environ
+        rm -f "$environ"
+    fi
+}
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..91dd47c
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Check if the colored --version output behaves correctly
+SYSTEMD_COLORS=256 systemctl --version
+
+# Check if we properly differentiate between a full systemd setup and a "light"
+# version of it that's done during daemon-reexec
+#
+# See: https://github.com/systemd/systemd/issues/27106
+if systemd-detect-virt -q --container; then
+    # We initialize /run/systemd/container only during a full setup
+    test -e /run/systemd/container
+    cp -afv /run/systemd/container /tmp/container
+    rm -fv /run/systemd/container
+    systemctl daemon-reexec
+    test ! -e /run/systemd/container
+    cp -afv /tmp/container /run/systemd/container
+else
+    # We bring the loopback netdev up only during a full setup, so it should
+    # not get brought back up during reexec if we disable it beforehand
+    [[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]]
+    ip link set lo down
+    [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+    systemctl daemon-reexec
+    [[ "$(ip -o link show lo)" =~ state\ DOWN ]]
+    ip link set lo up
+
+    # We also disable coredumps only during a full setup
+    sysctl -w kernel.core_pattern=dont-overwrite-me
+    systemctl daemon-reexec
+    diff <(echo dont-overwrite-me) <(sysctl --values kernel.core_pattern)
+fi
+
+# Collect failed units & do one daemon-reload to a basic sanity check
+systemctl --state=failed --no-legend --no-pager | tee /failed
+systemctl daemon-reload
+
+# Check that the early setup is actually skipped on reexec.
+# If the early setup is done more than once, then several timestamps,
+# e.g. SecurityStartTimestamp, are re-initialized, and causes an ABRT
+# of systemd-analyze blame. See issue #27187.
+systemd-analyze blame
+
+echo OK >/testok
index 8ebcc575ee7cab5c1fd8471a5102647f5620374a..61f6d06397012646a377048cecaade74188f872b 100755 (executable)
@@ -6,7 +6,7 @@ set -o pipefail
 NPROC=$(nproc)
 MAX_QUEUE_SIZE=${NPROC:-2}
 TESTS_GLOB=${TESTS_GLOB:-test-*}
-mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}")
+mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/unit-tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}")
 
 # reset state
 rm -fv /failed-tests /skipped-tests /skipped
index 1d4bf3aaaafd9ed541a29115d5459e8e43aa9ccb..289d8dac2849ed926fd9bb4ef8bc815446836095 100755 (executable)
@@ -34,11 +34,11 @@ grep 'hello\.service' /root/list-jobs.txt && exit 1
 systemctl stop sleep.service hello-after-sleep.target
 
 # Some basic testing that --show-transaction does something useful
-systemctl is-active systemd-importd && { echo 'unexpected success'; exit 1; }
+(! systemctl is-active systemd-importd)
 systemctl -T start systemd-importd
 systemctl is-active systemd-importd
 systemctl --show-transaction stop systemd-importd
-systemctl is-active systemd-importd && { echo 'unexpected success'; exit 1; }
+(! systemctl is-active systemd-importd)
 
 # Test for a crash when enqueuing a JOB_NOP when other job already exists
 systemctl start --no-block hello-after-sleep.target
@@ -95,7 +95,7 @@ ELAPSED=$((END_SEC-START_SEC))
 
 # wait5fail fails, so systemctl should fail
 START_SEC=$(date -u '+%s')
-systemctl start --wait wait2.service wait5fail.service && { echo 'unexpected success'; exit 1; }
+(! systemctl start --wait wait2.service wait5fail.service)
 END_SEC=$(date -u '+%s')
 ELAPSED=$((END_SEC-START_SEC))
 [[ "$ELAPSED" -ge 5 ]] && [[ "$ELAPSED" -le 7 ]] || exit 1
index 3f3d8bc9c89cdf3de5af57f709b725236bc5ebad..2a9b67949c3d6be9cc75c33176545322b0fa73f8 100755 (executable)
@@ -74,10 +74,10 @@ journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MES
 grep -q '^__CURSOR=' /output
 grep -q '^MESSAGE=foo$' /output
 grep -q '^PRIORITY=6$' /output
-grep '^FOO=' /output && { echo 'unexpected success'; exit 1; }
-grep '^SYSLOG_FACILITY=' /output && { echo 'unexpected success'; exit 1; }
+(! grep '^FOO=' /output)
+(! grep '^SYSLOG_FACILITY=' /output)
 
-# `-b all` negates earlier use of -b (-b and -m are otherwise exclusive)
+# '-b all' negates earlier use of -b (-b and -m are otherwise exclusive)
 journalctl -b -1 -b all -m >/dev/null
 
 # -b always behaves like -b0
@@ -175,6 +175,7 @@ sleep 3
 systemctl kill --signal=SIGKILL systemd-journald
 sleep 3
 [[ ! -f "/i-lose-my-logs" ]]
+systemctl stop forever-print-hola
 
 # https://github.com/systemd/systemd/issues/15528
 journalctl --follow --file=/var/log/journal/*/* | head -n1 || [[ $? -eq 1 ]]
@@ -211,11 +212,7 @@ function is_xattr_supported() {
     END=$(date '+%Y-%m-%d %T.%6N')
     systemctl stop text_xattr
 
-    if journalctl -q -u "text_xattr" -S "$START" -U "$END" --grep "Failed to set 'user.journald_log_filter_patterns' xattr.*not supported$"; then
-        return 1
-    fi
-
-    return 0
+    ! journalctl -q -u "text_xattr" -S "$START" -U "$END" --grep "Failed to set 'user.journald_log_filter_patterns' xattr.*not supported$"
 }
 
 if is_xattr_supported; then
@@ -278,9 +275,9 @@ test "$SEQNUM2" -gt "$SEQNUM1"
 JTMP="/var/tmp/jtmp-$RANDOM"
 mkdir "$JTMP"
 
-( cd /test-journals/1 && for f in *.zst ; do unzstd < "$f" > "$JTMP/${f%.zst}" ; done )
+( cd /test-journals/1 && for f in *.zst; do unzstd "$f" -o "$JTMP/${f%.zst}"; done )
 
-journalctl --directory="$JTMP" --list-boots --output=json > /tmp/lb1
+journalctl --directory="$JTMP" --list-boots --output=json >/tmp/lb1
 
 diff -u /tmp/lb1 - <<'EOF'
 [{"index":-3,"boot_id":"5ea5fc4f82a14186b5332a788ef9435e","first_entry":1666569600994371,"last_entry":1666584266223608},{"index":-2,"boot_id":"bea6864f21ad4c9594c04a99d89948b0","first_entry":1666584266731785,"last_entry":1666584347230411},{"index":-1,"boot_id":"4c708e1fd0744336be16f3931aa861fb","first_entry":1666584348378271,"last_entry":1666584354649355},{"index":0,"boot_id":"35e8501129134edd9df5267c49f744a4","first_entry":1666584356661527,"last_entry":1666584438086856}]
@@ -290,4 +287,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 1b8cd018bb110228e7993a21f7334c55bfc41bef..9b8a7bd6fa26a724b3c245033fb8c4833c688d3c 100755 (executable)
@@ -23,6 +23,25 @@ function wait_for()
     fi
 }
 
+function wait_for_timeout()
+{
+    local unit="$1"
+    local time="$2"
+
+    while [[ $time -gt 0 ]]; do
+        if [[ "$(systemctl show --property=Result "$unit")" == "Result=timeout" ]]; then
+            return 0
+        fi
+
+        sleep 1
+        time=$((time - 1))
+    done
+
+    journalctl -u "$unit" >>"$TESTLOG"
+
+    return 1
+}
+
 # This checks all stages, start, runtime and stop, can be extended by
 # EXTEND_TIMEOUT_USEC
 
@@ -44,6 +63,53 @@ wait_for fail_start startfail
 wait_for fail_stop stopfail
 wait_for fail_runtime runtimefail
 
+# These ensure that RuntimeMaxSec is honored for scope and service units
+# when they are created.
+runtime_max_sec=5
+
+systemd-run \
+    --property=RuntimeMaxSec=${runtime_max_sec}s \
+    -u runtime-max-sec-test-1.service \
+    /usr/bin/sh -c "while true; do sleep 1; done"
+wait_for_timeout runtime-max-sec-test-1.service $((runtime_max_sec + 2))
+
+systemd-run \
+    --property=RuntimeMaxSec=${runtime_max_sec}s \
+    --scope \
+    -u runtime-max-sec-test-2.scope \
+    /usr/bin/sh -c "while true; do sleep 1; done" &
+wait_for_timeout runtime-max-sec-test-2.scope $((runtime_max_sec + 2))
+
+# These ensure that RuntimeMaxSec is honored for scope and service
+# units if the value is changed and then the manager is reloaded.
+systemd-run \
+    -u runtime-max-sec-test-3.service \
+    /usr/bin/sh -c "while true; do sleep 1; done"
+mkdir -p /etc/systemd/system/runtime-max-sec-test-3.service.d/
+cat > /etc/systemd/system/runtime-max-sec-test-3.service.d/override.conf << EOF
+[Service]
+RuntimeMaxSec=${runtime_max_sec}s
+EOF
+systemctl daemon-reload
+wait_for_timeout runtime-max-sec-test-3.service $((runtime_max_sec + 2))
+
+systemd-run \
+    --scope \
+    -u runtime-max-sec-test-4.scope \
+    /usr/bin/sh -c "while true; do sleep 1; done" &
+
+# Wait until the unit is running to avoid race with creating the override.
+until systemctl is-active runtime-max-sec-test-4.scope; do
+    sleep 1
+done
+mkdir -p /etc/systemd/system/runtime-max-sec-test-4.scope.d/
+cat > /etc/systemd/system/runtime-max-sec-test-4.scope.d/override.conf << EOF
+[Scope]
+RuntimeMaxSec=${runtime_max_sec}s
+EOF
+systemctl daemon-reload
+wait_for_timeout runtime-max-sec-test-4.scope $((runtime_max_sec + 2))
+
 if [[ -f "$TESTLOG" ]]; then
     # no mv
     cp "$TESTLOG" /test.log
index 224063f6a6725c78727db1454bc95b17c6d0eade..4d452ff97c38ecb1847375b08759a09a9f965f9b 100755 (executable)
@@ -16,20 +16,18 @@ function check_validity() {
 }
 
 function check() {
-    local i j
-
-    for ((i = 0; i < 2; i++)); do
+    for _ in {1..2}; do
         systemctl restart systemd-udevd.service
         udevadm control --ping
         udevadm settle
         check_validity
 
-        for ((j = 0; j < 2; j++)); do
+        for _ in {1..2}; do
             udevadm trigger -w --action add --subsystem-match=block
             check_validity
         done
 
-        for ((j = 0; j < 2; j++)); do
+        for _ in {1..2}; do
             udevadm trigger -w --action change --subsystem-match=block
             check_validity
         done
index 549107af1016e90d5c1fcf6c8921463517d41e5b..b4dfd904e9982967f504d2cea534b6ed5d17a930 100755 (executable)
@@ -8,8 +8,8 @@ set -o pipefail
 
 wait_service_active() {(
     set +ex
-    for (( i = 0; i < 20; i++ )); do
-        if (( i != 0 )); then sleep 0.5; fi
+    for i in {1..20}; do
+        (( i > 1 )) && sleep 0.5
         if systemctl --quiet is-active "${1?}"; then
             return 0
         fi
@@ -19,8 +19,8 @@ wait_service_active() {(
 
 wait_service_inactive() {(
     set +ex
-    for (( i = 0; i < 20; i++ )); do
-        if (( i != 0 )); then sleep 0.5; fi
+    for i in {1..20}; do
+        (( i > 1 )) && sleep 0.5
         systemctl --quiet is-active "${1?}"
         if [[ "$?" == "3" ]]; then
             return 0
index f740b337f70a77217c7cd8d53708a5867d3053f8..a49a77dc44810e31d47ca735f8c5b92a73b241d8 100755 (executable)
@@ -18,8 +18,8 @@ EOF
 udevadm control --reload
 
 udevadm trigger --settle --action add /dev/null
-for ((i = 0; i < 20; i++)); do
-    ((i == 0)) || sleep .5
+for i in {1..20}; do
+    ((i > 1)) && sleep .5
 
     (
         systemctl -q is-active /dev/test/symlink-to-null-on-add
@@ -34,8 +34,8 @@ assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-add
 assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-change
 
 udevadm trigger --settle --action change /dev/null
-for ((i = 0; i < 20; i++)); do
-    ((i == 0)) || sleep .5
+for i in {1..20}; do
+    ((i > 1)) && sleep .5
 
     (
         ! systemctl -q is-active /dev/test/symlink-to-null-on-add
@@ -50,8 +50,8 @@ assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-add
 assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-change
 
 udevadm trigger --settle --action add /dev/null
-for ((i = 0; i < 20; i++)); do
-    ((i == 0)) || sleep .5
+for i in {1..20}; do
+    ((i > 1)) && sleep .5
 
     (
         systemctl -q is-active /dev/test/symlink-to-null-on-add
index 01ac5f17090543307d074ddb2a2d4f819d1661ef..31fc9d684233b889d17744ab15bb3f7e07a4553b 100755 (executable)
@@ -15,7 +15,7 @@ KERNEL!="null", GOTO="test-end"
 ACTION=="remove", GOTO="test-end"
 
 # add 100 * 100byte of properties
-$(for ((i = 0; i < 100; i++)); do printf 'ENV{XXX%03i}="0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"\n' "$i"; done)
+$(for i in {1..100}; do printf 'ENV{XXX%03i}="0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"\n' "$i"; done)
 
 LABEL="test-end"
 EOF
@@ -46,7 +46,7 @@ for _ in {1..40}; do
     fi
 
     FOUND=1
-    for ((i = 0; i < 100; i++)); do
+    for i in {1..100}; do
         if ! grep -F "$(printf 'XXX%03i=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' "$i")" "$TMPDIR"/monitor.txt; then
             FOUND=
             break
index 88172acde6d3686ea181e17ce6b25dea5ae66295..ce6f58077f2be73d738c8532299f0040f729d571 100755 (executable)
@@ -97,6 +97,8 @@ assert_1 --resolve-names=now
 # Failed to parse rules file .: Is a directory
 cp "${workdir}/default_output_1_fail" "${exo}"
 assert_1 .
+# Failed to parse rules file ./nosuchfile: No such file or directory
+assert_1 ./nosuchfile
 # Failed to parse rules file .: Is a directory
 cat >"${exo}" <<EOF
 
@@ -132,7 +134,7 @@ cp "${workdir}/default_output_1_fail" "${exo}"
 assert_1 "${rules}"
 
 {
-    printf 'RUN+="/bin/true"%8175s\\\n' ' '
+    printf 'RUN+="/bin/true",%8174s\\\n' ' '
     printf 'RUN+="/bin/false"%8174s\\\n' ' '
     echo
 } >"${rules}"
@@ -173,7 +175,7 @@ EOF
 test_syntax_error '=' 'Invalid key/value pair, ignoring.'
 test_syntax_error 'ACTION{a}=="b"' 'Invalid attribute for ACTION.'
 test_syntax_error 'ACTION:="b"' 'Invalid operator for ACTION.'
-test_syntax_error 'ACTION=="b"' 'The line takes no effect, ignoring.'
+test_syntax_error 'ACTION=="b"' 'The line has no effect, ignoring.'
 test_syntax_error 'DEVPATH{a}=="b"' 'Invalid attribute for DEVPATH.'
 test_syntax_error 'DEVPATH:="b"' 'Invalid operator for DEVPATH.'
 test_syntax_error 'KERNEL{a}=="b"' 'Invalid attribute for KERNEL.'
@@ -201,7 +203,7 @@ test_syntax_error 'TAGS{a}=="b"' 'Invalid attribute for TAGS.'
 test_syntax_error 'TAGS:="a"' 'Invalid operator for TAGS.'
 test_syntax_error 'SUBSYSTEM{a}=="b"' 'Invalid attribute for SUBSYSTEM.'
 test_syntax_error 'SUBSYSTEM:="b"' 'Invalid operator for SUBSYSTEM.'
-test_syntax_error 'SUBSYSTEM=="bus" NAME="b"' '"bus" must be specified as "subsystem".'
+test_syntax_error 'SUBSYSTEM=="bus", NAME="b"' '"bus" must be specified as "subsystem".'
 test_syntax_error 'SUBSYSTEMS{a}=="b"' 'Invalid attribute for SUBSYSTEMS.'
 test_syntax_error 'SUBSYSTEMS:="b"' 'Invalid operator for SUBSYSTEMS.'
 test_syntax_error 'DRIVER{a}=="b"' 'Invalid attribute for DRIVER.'
@@ -219,20 +221,20 @@ test_syntax_error 'SYSCTL{a}-="b"' 'Invalid operator for SYSCTL.'
 test_syntax_error 'SYSCTL{a}+="b"' "SYSCTL key takes '==', '!=', or '=' operator, assuming '='."
 test_syntax_error 'SYSCTL{a}="%?"' 'Invalid value "%?" for SYSCTL (char 1: invalid substitution type), ignoring.'
 test_syntax_error 'ATTRS=""' 'Invalid attribute for ATTRS.'
-test_syntax_error 'ATTRS{%}=="b" NAME="b"' 'Invalid attribute "%" for ATTRS (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'ATTRS{%}=="b", NAME="b"' 'Invalid attribute "%" for ATTRS (char 1: invalid substitution type), ignoring.'
 test_syntax_error 'ATTRS{a}-="b"' 'Invalid operator for ATTRS.'
-test_syntax_error 'ATTRS{device/}!="a" NAME="b"' "'device' link may not be available in future kernels."
-test_syntax_error 'ATTRS{../}!="a" NAME="b"' 'Direct reference to parent sysfs directory, may break in future kernels.'
+test_syntax_error 'ATTRS{device/}!="a", NAME="b"' "'device' link may not be available in future kernels."
+test_syntax_error 'ATTRS{../}!="a", NAME="b"' 'Direct reference to parent sysfs directory, may break in future kernels.'
 test_syntax_error 'TEST{a}=="b"' "Failed to parse mode 'a': Invalid argument"
-test_syntax_error 'TEST{0}=="%" NAME="b"' 'Invalid value "%" for TEST (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'TEST{0}=="%", NAME="b"' 'Invalid value "%" for TEST (char 1: invalid substitution type), ignoring.'
 test_syntax_error 'TEST{0644}="b"' 'Invalid operator for TEST.'
 test_syntax_error 'PROGRAM{a}=="b"' 'Invalid attribute for PROGRAM.'
 test_syntax_error 'PROGRAM-="b"' 'Invalid operator for PROGRAM.'
-test_syntax_error 'PROGRAM=="%" NAME="b"' 'Invalid value "%" for PROGRAM (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'PROGRAM=="%", NAME="b"' 'Invalid value "%" for PROGRAM (char 1: invalid substitution type), ignoring.'
 test_syntax_error 'IMPORT="b"' 'Invalid attribute for IMPORT.'
 test_syntax_error 'IMPORT{a}="b"' 'Invalid attribute for IMPORT.'
 test_syntax_error 'IMPORT{a}-="b"' 'Invalid operator for IMPORT.'
-test_syntax_error 'IMPORT{file}=="%" NAME="b"' 'Invalid value "%" for IMPORT (char 1: invalid substitution type), ignoring.'
+test_syntax_error 'IMPORT{file}=="%", NAME="b"' 'Invalid value "%" for IMPORT (char 1: invalid substitution type), ignoring.'
 test_syntax_error 'IMPORT{builtin}!="foo"' 'Unknown builtin command: foo'
 test_syntax_error 'RESULT{a}=="b"' 'Invalid attribute for RESULT.'
 test_syntax_error 'RESULT:="b"' 'Invalid operator for RESULT.'
@@ -241,7 +243,7 @@ test_syntax_error 'OPTIONS-="b"' 'Invalid operator for OPTIONS.'
 test_syntax_error 'OPTIONS!="b"' 'Invalid operator for OPTIONS.'
 test_syntax_error 'OPTIONS+="link_priority=a"' "Failed to parse link priority 'a': Invalid argument"
 test_syntax_error 'OPTIONS:="log_level=a"' "Failed to parse log level 'a': Invalid argument"
-test_syntax_error 'OPTIONS="a" NAME="b"' "Invalid value for OPTIONS key, ignoring: 'a'"
+test_syntax_error 'OPTIONS="a", NAME="b"' "Invalid value for OPTIONS key, ignoring: 'a'"
 test_syntax_error 'OWNER{a}="b"' 'Invalid attribute for OWNER.'
 test_syntax_error 'OWNER-="b"' 'Invalid operator for OWNER.'
 test_syntax_error 'OWNER!="b"' 'Invalid operator for OWNER.'
@@ -268,38 +270,55 @@ test_syntax_error 'RUN="%"' 'Invalid value "%" for RUN (char 1: invalid substitu
 test_syntax_error 'RUN{builtin}+="foo"' "Unknown builtin command 'foo', ignoring"
 test_syntax_error 'GOTO{a}="b"' 'Invalid attribute for GOTO.'
 test_syntax_error 'GOTO=="b"' 'Invalid operator for GOTO.'
-test_syntax_error 'NAME="a" GOTO="b"' 'GOTO="b" has no matching label, ignoring'
-test_syntax_error 'GOTO="a" GOTO="b"
+test_syntax_error 'NAME="a", GOTO="b"' 'GOTO="b" has no matching label, ignoring'
+test_syntax_error 'GOTO="a", GOTO="b"
 LABEL="a"' 'Contains multiple GOTO keys, ignoring GOTO="b".'
 test_syntax_error 'LABEL{a}="b"' 'Invalid attribute for LABEL.'
 test_syntax_error 'LABEL=="b"' 'Invalid operator for LABEL.'
 test_syntax_error 'LABEL="b"' 'LABEL="b" is unused.'
 test_syntax_error 'a="b"' "Invalid key 'a'"
-test_syntax_error 'KERNEL=="", KERNEL=="?*", NAME="a"' 'conflicting match expressions, the line takes no effect'
-test_syntax_error 'KERNEL=="abc", KERNEL!="abc", NAME="b"' 'conflicting match expressions, the line takes no effect'
-test_syntax_error 'KERNEL=="|a|b", KERNEL!="b|a|", NAME="c"' 'conflicting match expressions, the line takes no effect'
-test_syntax_error 'KERNEL=="a|b", KERNEL=="c|d|e", NAME="f"' 'conflicting match expressions, the line takes no effect'
+test_syntax_error 'KERNEL=="", KERNEL=="?*", NAME="a"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'KERNEL=="abc", KERNEL!="abc", NAME="b"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'KERNEL=="|a|b", KERNEL!="b|a|", NAME="c"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'KERNEL=="a|b", KERNEL=="c|d|e", NAME="f"' 'conflicting match expressions, the line has no effect'
 # shellcheck disable=SC2016
-test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}!="?*" ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
-                  'conflicting match expressions, the line takes no effect'
+test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}!="?*", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
+                  'conflicting match expressions, the line has no effect'
+test_syntax_error 'ACTION=="a*", ACTION=="bc*", NAME="d"' 'conflicting match expressions, the line has no effect'
+test_syntax_error 'ACTION=="a*|bc*", ACTION=="d*|ef*", NAME="g"' 'conflicting match expressions, the line has no effect'
 test_syntax_error 'KERNEL!="", KERNEL=="?*", NAME="a"' 'duplicate expressions'
 test_syntax_error 'KERNEL=="|a|b", KERNEL=="b|a|", NAME="c"' 'duplicate expressions'
 # shellcheck disable=SC2016
-test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}=="?*" ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
+test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}=="?*", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
                   'duplicate expressions'
+test_syntax_error ',ACTION=="a", NAME="b"' 'Stray leading comma.'
+test_syntax_error ' ,ACTION=="a", NAME="b"' 'Stray leading comma.'
+test_syntax_error ', ACTION=="a", NAME="b"' 'Stray leading comma.'
+test_syntax_error 'ACTION=="a", NAME="b",' 'Stray trailing comma.'
+test_syntax_error 'ACTION=="a", NAME="b", ' 'Stray trailing comma.'
+test_syntax_error 'ACTION=="a" NAME="b"' 'A comma between tokens is expected.'
+test_syntax_error 'ACTION=="a",, NAME="b"' 'More than one comma between tokens.'
+test_syntax_error 'ACTION=="a" , NAME="b"' 'Stray whitespace before comma.'
+test_syntax_error 'ACTION=="a",NAME="b"' 'Whitespace after comma is expected.'
+test_syntax_error 'RESULT=="a", PROGRAM="b"' 'Reordering RESULT check after PROGRAM assignment.'
+test_syntax_error 'RESULT=="a*", PROGRAM="b", RESULT=="*c", PROGRAM="d"' \
+        'Reordering RESULT check after PROGRAM assignment.'
 
 cat >"${rules}" <<'EOF'
 KERNEL=="a|b", KERNEL=="a|c", NAME="d"
 KERNEL=="a|b", KERNEL!="a|c", NAME="d"
 KERNEL!="a", KERNEL!="b", NAME="c"
 KERNEL=="|a", KERNEL=="|b", NAME="c"
+KERNEL=="*", KERNEL=="a*", NAME="b"
+KERNEL=="a*", KERNEL=="c*|ab*", NAME="d"
+PROGRAM="a", RESULT=="b"
 EOF
 assert_0 "${rules}"
 
 echo 'GOTO="a"' >"${rules}"
 cat >"${exp}" <<EOF
 ${rules}:1 GOTO="a" has no matching label, ignoring
-${rules}:1 The line takes no effect any more, dropping
+${rules}:1 The line has no effect any more, dropping.
 ${rules}: udev rules check failed
 EOF
 cp "${workdir}/default_output_1_fail" "${exo}"
@@ -330,7 +349,7 @@ EOF
 cat >"${exp}" <<EOF
 ${rules}:2 Contains multiple LABEL keys, ignoring LABEL="a".
 ${rules}:1 GOTO="a" has no matching label, ignoring
-${rules}:1 The line takes no effect any more, dropping
+${rules}:1 The line has no effect any more, dropping.
 ${rules}:2 LABEL="b" is unused.
 ${rules}: udev rules check failed
 EOF
@@ -342,7 +361,29 @@ KERNEL!="", KERNEL=="?*", KERNEL=="", NAME="a"
 EOF
 cat >"${exp}" <<EOF
 ${rules}:1 duplicate expressions
-${rules}:1 conflicting match expressions, the line takes no effect
+${rules}:1 conflicting match expressions, the line has no effect
+${rules}: udev rules check failed
+EOF
+cp "${workdir}/default_output_1_fail" "${exo}"
+assert_1 "${rules}"
+
+cat >"${rules}" <<'EOF'
+ACTION=="a"NAME="b"
+EOF
+cat >"${exp}" <<EOF
+${rules}:1 A comma between tokens is expected.
+${rules}:1 Whitespace between tokens is expected.
+${rules}: udev rules check failed
+EOF
+cp "${workdir}/default_output_1_fail" "${exo}"
+assert_1 "${rules}"
+
+cat >"${rules}" <<'EOF'
+ACTION=="a" ,NAME="b"
+EOF
+cat >"${exp}" <<EOF
+${rules}:1 Stray whitespace before comma.
+${rules}:1 Whitespace after comma is expected.
 ${rules}: udev rules check failed
 EOF
 cp "${workdir}/default_output_1_fail" "${exo}"
index e9d7c5bfc87f8477d85a745184c5ccfe441379a5..44b792f0031e7e5f578bd9039ff4229736b53e41 100755 (executable)
@@ -4,7 +4,7 @@ set -eux
 set -o pipefail
 
 systemd-run --wait -p FailureAction=poweroff true
-systemd-run --wait -p SuccessAction=poweroff false && { echo 'unexpected success'; exit 1; }
+(! systemd-run --wait -p SuccessAction=poweroff false)
 
 if ! test -f /firstphase ; then
     echo OK >/firstphase
index 6ce6d3d42918fea52e37170b018a6dd39f10f638..1e705ea72bf621637febb559b8931dd4c67d9c9b 100755 (executable)
@@ -7,37 +7,37 @@ test_scope_unpriv_delegation() {
     useradd test ||:
     trap "userdel -r test" RETURN
 
-    systemd-run --uid=test -p User=test -p Delegate=yes --slice workload.slice --unit workload0.scope --scope \
-            test -w /sys/fs/cgroup/workload.slice/workload0.scope -a \
-            -w /sys/fs/cgroup/workload.slice/workload0.scope/cgroup.procs -a \
-            -w /sys/fs/cgroup/workload.slice/workload0.scope/cgroup.subtree_control
+    systemd-run --uid=test -p User=test -p Delegate=yes --slice workload.slice --unit test-workload0.scope --scope \
+            test -w /sys/fs/cgroup/workload.slice/test-workload0.scope -a \
+            -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.procs -a \
+            -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control
 }
 
 if grep -q cgroup2 /proc/filesystems ; then
-    systemd-run --wait --unit=test0.service -p "DynamicUser=1" -p "Delegate=" \
-                test -w /sys/fs/cgroup/system.slice/test0.service/ -a \
-                -w /sys/fs/cgroup/system.slice/test0.service/cgroup.procs -a \
-                -w /sys/fs/cgroup/system.slice/test0.service/cgroup.subtree_control
+    systemd-run --wait --unit=test-0.service -p "DynamicUser=1" -p "Delegate=" \
+                test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \
+                -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.procs -a \
+                -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.subtree_control
 
-    systemd-run --wait --unit=test1.service -p "DynamicUser=1" -p "Delegate=memory pids" \
-                grep -q memory /sys/fs/cgroup/system.slice/test1.service/cgroup.controllers
+    systemd-run --wait --unit=test-1.service -p "DynamicUser=1" -p "Delegate=memory pids" \
+                grep -q memory /sys/fs/cgroup/system.slice/test-1.service/cgroup.controllers
 
-    systemd-run --wait --unit=test2.service -p "DynamicUser=1" -p "Delegate=memory pids" \
-                grep -q pids /sys/fs/cgroup/system.slice/test2.service/cgroup.controllers
+    systemd-run --wait --unit=test-2.service -p "DynamicUser=1" -p "Delegate=memory pids" \
+                grep -q pids /sys/fs/cgroup/system.slice/test-2.service/cgroup.controllers
 
     # "io" is not among the controllers enabled by default for all units, verify that
     grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
 
     # Run a service with "io" enabled, and verify it works
-    systemd-run --wait --unit=test3.service -p "IOAccounting=yes" -p "Slice=system-foo-bar-baz.slice"  \
-                grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test3.service/cgroup.controllers
+    systemd-run --wait --unit=test-3.service -p "IOAccounting=yes" -p "Slice=system-foo-bar-baz.slice"  \
+                grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test-3.service/cgroup.controllers
 
     # We want to check if "io" is removed again from the controllers
     # list. However, PID 1 (rightfully) does this asynchronously. In order
     # to force synchronization on this, let's start a short-lived service
     # which requires PID 1 to refresh the cgroup tree, so that we can
     # verify that this all works.
-    systemd-run --wait --unit=test4.service true
+    systemd-run --wait --unit=test-4.service true
 
     # And now check again, "io" should have vanished
     grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
index 338769aacc500902d49ab7660e18592d87222bbc..6ce992f41acf948133f18bb12d7bc737e4143110 100755 (executable)
@@ -13,8 +13,8 @@ INTERNALPID=$!
 disown
 
 # Start a test process outside of our own cgroup
-systemd-run -p DynamicUser=1 --unit=test20-sleep.service /bin/sleep infinity
-EXTERNALPID="$(systemctl show -P MainPID test20-sleep.service)"
+systemd-run -p DynamicUser=1 --unit=test-sleep.service /bin/sleep infinity
+EXTERNALPID="$(systemctl show -P MainPID test-sleep.service)"
 
 # Update our own main PID to the external test PID, this should work
 systemd-notify MAINPID="$EXTERNALPID"
@@ -54,7 +54,7 @@ test "$(systemctl show -P MainPID testsuite-20.service)" -eq "$INTERNALPID"
 systemd-notify --uid=1000 MAINPID=$$
 test "$(systemctl show -P MainPID testsuite-20.service)" -eq $$
 
-cat >/tmp/test20-mainpid.sh <<EOF
+cat >/tmp/test-mainpid.sh <<EOF
 #!/usr/bin/env bash
 
 set -eux
@@ -73,12 +73,12 @@ disown
 
 echo \$MAINPID >/run/mainpidsh/pid
 EOF
-chmod +x /tmp/test20-mainpid.sh
+chmod +x /tmp/test-mainpid.sh
 
-systemd-run --unit=test20-mainpidsh.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh -p PIDFile=/run/mainpidsh/pid /tmp/test20-mainpid.sh
-test "$(systemctl show -P MainPID test20-mainpidsh.service)" -eq "$(cat /run/mainpidsh/pid)"
+systemd-run --unit=test-mainpidsh.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh -p PIDFile=/run/mainpidsh/pid /tmp/test-mainpid.sh
+test "$(systemctl show -P MainPID test-mainpidsh.service)" -eq "$(cat /run/mainpidsh/pid)"
 
-cat >/tmp/test20-mainpid2.sh <<EOF
+cat >/tmp/test-mainpid2.sh <<EOF
 #!/usr/bin/env bash
 
 set -eux
@@ -98,12 +98,12 @@ disown
 echo \$MAINPID >/run/mainpidsh2/pid
 chown 1001:1001 /run/mainpidsh2/pid
 EOF
-chmod +x /tmp/test20-mainpid2.sh
+chmod +x /tmp/test-mainpid2.sh
 
-systemd-run --unit=test20-mainpidsh2.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh2 -p PIDFile=/run/mainpidsh2/pid /tmp/test20-mainpid2.sh
-test "$(systemctl show -P MainPID test20-mainpidsh2.service)" -eq "$(cat /run/mainpidsh2/pid)"
+systemd-run --unit=test-mainpidsh2.service -p StandardOutput=tty -p StandardError=tty -p Type=forking -p RuntimeDirectory=mainpidsh2 -p PIDFile=/run/mainpidsh2/pid /tmp/test-mainpid2.sh
+test "$(systemctl show -P MainPID test-mainpidsh2.service)" -eq "$(cat /run/mainpidsh2/pid)"
 
-cat >/dev/shm/test20-mainpid3.sh <<EOF
+cat >/dev/shm/test-mainpid3.sh <<EOF
 #!/usr/bin/env bash
 
 set -eux
@@ -124,40 +124,39 @@ ln -s ../mainpidsh/pid /run/mainpidsh3/pid
 # Quick assertion that the link isn't dead
 test -f /run/mainpidsh3/pid
 EOF
-chmod 755 /dev/shm/test20-mainpid3.sh
+chmod 755 /dev/shm/test-mainpid3.sh
 
 # This has to fail, as we shouldn't accept the dangerous PID file, and then
 # inotify-wait on it to be corrected which we never do.
-systemd-run --unit=test20-mainpidsh3.service \
-            -p StandardOutput=tty \
-            -p StandardError=tty \
-            -p Type=forking \
-            -p RuntimeDirectory=mainpidsh3 \
-            -p PIDFile=/run/mainpidsh3/pid \
-            -p DynamicUser=1 \
-            -p TimeoutStartSec=2s \
-            /dev/shm/test20-mainpid3.sh \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run \
+    --unit=test-mainpidsh3.service \
+    -p StandardOutput=tty \
+    -p StandardError=tty \
+    -p Type=forking \
+    -p RuntimeDirectory=mainpidsh3 \
+    -p PIDFile=/run/mainpidsh3/pid \
+    -p DynamicUser=1 \
+    -p TimeoutStartSec=2s \
+    /dev/shm/test-mainpid3.sh)
 
 # Test that this failed due to timeout, and not some other error
-test "$(systemctl show -P Result test20-mainpidsh3.service)" = timeout
+test "$(systemctl show -P Result test-mainpidsh3.service)" = timeout
 
 # Test that scope units work
-systemd-run --scope --unit test20-true.scope /bin/true
-test "$(systemctl show -P Result test20-true.scope)" = success
+systemd-run --scope --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
 
 # Test that user scope units work as well
 
 runas() {
     declare userid=$1
     shift
-    # shellcheck disable=SC2016
-    su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+    XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
 }
 
 systemctl start user@4711.service
-runas testuser systemd-run --scope --user --unit test20-true.scope /bin/true
-test "$(systemctl show -P Result test20-true.scope)" = success
+runas testuser systemd-run --scope --user --unit test-true.scope /bin/true
+test "$(systemctl show -P Result test-true.scope)" = success
 
 systemd-analyze log-level info
 
index d931e631678e2ff872288bb1c5284d4db49b078b..36f647ca5f69ffe7b9e6aba0350218fdb0ab6b18 100755 (executable)
@@ -7,6 +7,7 @@ set -o pipefail
 # on the fly by one of the fuzzers
 systemctl list-jobs | grep -F 'end.service' && SHUTDOWN_AT_EXIT=1 || SHUTDOWN_AT_EXIT=0
 
+# shellcheck disable=SC2317
 at_exit() {
     set +e
     # We have to call the end.service/poweroff explicitly even if it's specified on
@@ -24,6 +25,10 @@ trap at_exit EXIT
 
 systemctl log-level info
 
+# FIXME: systemd-run doesn't play well with daemon-reexec
+# See: https://github.com/systemd/systemd/issues/27204
+sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:Reexecute FIXME' /etc/dfuzzer.conf
+
 # TODO
 #   * check for possibly newly introduced buses?
 BUS_LIST=(
@@ -84,6 +89,8 @@ for bus in "${BUS_LIST[@]}"; do
 
     # Let's reload the systemd daemon to test (de)serialization as well
     systemctl daemon-reload
+    # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved
+    systemctl daemon-reexec
 done
 
 umount /var/lib/machines
@@ -95,6 +102,8 @@ for bus in "${SESSION_BUS_LIST[@]}"; do
 
     # Let's reload the systemd user daemon to test (de)serialization as well
     systemctl --machine 'testuser@.host' --user daemon-reload
+    # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved
+    systemctl --machine 'testuser@.host' --user daemon-reexec
 done
 
 echo OK >/testok
index a9df3d6bdd5a1a6e386f96d1383fddc8ff12b9be..6fce4c07053997756b143888264028c59972f490 100755 (executable)
@@ -38,7 +38,7 @@ test "$(stat -c %U:%G:%a /tmp/f/1)" = "daemon:daemon:666"
 mkfifo /tmp/f/fifo
 chmod 644 /tmp/f/fifo
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/f/fifo    0666 daemon daemon - This string should not be written
 EOF
 
@@ -49,7 +49,7 @@ test "$(stat -c %U:%G:%a /tmp/f/fifo)" = "root:root:644"
 ln -s missing /tmp/f/dangling
 ln -s /tmp/file-owned-by-root /tmp/f/symlink
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/f/dangling    0644 daemon daemon - -
 f     /tmp/f/symlink     0644 daemon daemon - -
 EOF
@@ -71,12 +71,12 @@ f     /tmp/f/ro-fs/foo    0644 - - - - This string should not be written
 EOF
 test -f /tmp/f/ro-fs/foo; test ! -s /tmp/f/ro-fs/foo
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/f/ro-fs/foo    0666 - - - -
 EOF
 test "$(stat -c %U:%G:%a /tmp/f/fifo)" = "root:root:644"
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/f/ro-fs/bar    0644 - - - -
 EOF
 test ! -e /tmp/f/ro-fs/bar
@@ -86,7 +86,7 @@ mkdir /tmp/f/daemon
 ln -s /root /tmp/f/daemon/unsafe-symlink
 chown -R --no-dereference daemon:daemon /tmp/f/daemon
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/f/daemon/unsafe-symlink/exploit    0644 daemon daemon - -
 EOF
 test ! -e /tmp/f/daemon/unsafe-symlink/exploit
@@ -116,7 +116,7 @@ test "$(stat -c %U:%G:%a /tmp/F/truncated-with-content)" = "daemon:daemon:666"
 ### unspecified in the other cases.
 mkfifo /tmp/F/fifo
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 F     /tmp/F/fifo                0644 - - - -
 EOF
 
@@ -126,7 +126,7 @@ test -p /tmp/F/fifo
 ln -s missing /tmp/F/dangling
 ln -s /tmp/file-owned-by-root /tmp/F/symlink
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/F/dangling    0644 daemon daemon - -
 f     /tmp/F/symlink     0644 daemon daemon - -
 EOF
@@ -149,11 +149,11 @@ EOF
 test -f /tmp/F/ro-fs/foo; test ! -s /tmp/F/ro-fs/foo
 
 echo "truncating is not allowed anymore" >/tmp/F/rw-fs/foo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 F     /tmp/F/ro-fs/foo    0644 - - - -
 EOF
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 F     /tmp/F/ro-fs/foo    0644 - - - - This string should not be written
 EOF
 test -f /tmp/F/ro-fs/foo
@@ -161,13 +161,13 @@ grep -q 'truncating is not allowed' /tmp/F/ro-fs/foo
 
 # Trying to change the perms should fail.
 : >/tmp/F/rw-fs/foo
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 F     /tmp/F/ro-fs/foo    0666 - - - -
 EOF
 test "$(stat -c %U:%G:%a /tmp/F/ro-fs/foo)" = "root:root:644"
 
 ### Try to create a new file.
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 F     /tmp/F/ro-fs/bar    0644 - - - -
 EOF
 test ! -e /tmp/F/ro-fs/bar
@@ -177,7 +177,7 @@ mkdir /tmp/F/daemon
 ln -s /root /tmp/F/daemon/unsafe-symlink
 chown -R --no-dereference daemon:daemon /tmp/F/daemon
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 F     /tmp/F/daemon/unsafe-symlink/exploit    0644 daemon daemon - -
 EOF
 test ! -e /tmp/F/daemon/unsafe-symlink/exploit
@@ -195,7 +195,7 @@ EOF
 test ! -e /tmp/w/unexistent
 
 ### no argument given -> fails.
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 w     /tmp/w/unexistent    0644 - - - -
 EOF
 
@@ -240,7 +240,7 @@ mkdir /tmp/w/daemon
 ln -s /root /tmp/w/daemon/unsafe-symlink
 chown -R --no-dereference daemon:daemon /tmp/w/daemon
 
-systemd-tmpfiles --create - <<EOF && { echo 'unexpected success'; exit 1; }
+(! systemd-tmpfiles --create -) <<EOF
 f     /tmp/w/daemon/unsafe-symlink/exploit    0644 daemon daemon - -
 EOF
 test ! -e /tmp/w/daemon/unsafe-symlink/exploit
index 65f1832adc815899ce7d9cd64bd7e51c98893093..40fafd33bfe6c6be07a2490f8a4f57b4aeab54aa 100755 (executable)
@@ -22,11 +22,9 @@ test -d /tmp/root/test2
 # Verify the command fails to write to a root-owned subdirectory under an
 # unprivileged user's directory when it's not part of the prefix, as expected
 # by the unsafe_transition function.
-echo 'd /tmp/user/root/test' | systemd-tmpfiles --create - \
-    && { echo 'unexpected success'; exit 1; }
+echo 'd /tmp/user/root/test' | (! systemd-tmpfiles --create -)
 test ! -e /tmp/user/root/test
-echo 'd /user/root/test' | systemd-tmpfiles --root=/tmp --create - \
-    && { echo 'unexpected success'; exit 1; }
+echo 'd /user/root/test' | (! systemd-tmpfiles --root=/tmp --create -)
 test ! -e /tmp/user/root/test
 
 # Verify the above works when all user-owned directories are in the prefix.
index e8f99ff60d0de2d7852bd6e7d09ad0e37a784bd7..4ce205fae6863bc5ce0a625b68817fd5eae9a7d0 100755 (executable)
@@ -16,8 +16,8 @@ systemd-run --unit=three -p Type=simple /tmp/brokenbinary
 
 # And now, do the same with Type=exec, where the latter two should fail
 systemd-run --unit=four -p Type=exec /bin/sleep infinity
-systemd-run --unit=five -p Type=exec -p User=idontexist /bin/sleep infinity && { echo 'unexpected success'; exit 1; }
-systemd-run --unit=six -p Type=exec /tmp/brokenbinary && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=five -p Type=exec -p User=idontexist /bin/sleep infinity)
+(! systemd-run --unit=six -p Type=exec /tmp/brokenbinary)
 
 systemd-run --unit=seven -p KillSignal=SIGTERM -p RestartKillSignal=SIGINT -p Type=exec /bin/sleep infinity
 # Both TERM and SIGINT happen to have the same number on all architectures
@@ -31,32 +31,32 @@ systemctl stop seven.service
 
 # Should work normally
 busctl call \
-  org.freedesktop.systemd1 /org/freedesktop/systemd1 \
-  org.freedesktop.systemd1.Manager StartTransientUnit \
-  "ssa(sv)a(sa(sv))" test-20933-ok.service replace 1 \
-    ExecStart "a(sasb)" 1 \
-      /usr/bin/sleep 2 /usr/bin/sleep 1 true \
-  0
+    org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+    org.freedesktop.systemd1.Manager StartTransientUnit \
+    "ssa(sv)a(sa(sv))" test-20933-ok.service replace 1 \
+      ExecStart "a(sasb)" 1 \
+        /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+    0
 
 # DBus call should fail but not crash systemd
-busctl call \
-  org.freedesktop.systemd1 /org/freedesktop/systemd1 \
-  org.freedesktop.systemd1.Manager StartTransientUnit \
-  "ssa(sv)a(sa(sv))" test-20933-bad.service replace 1 \
-    ExecStart "a(sasb)" 1 \
-      /usr/bin/sleep 0 true \
-  0 && { echo 'unexpected success'; exit 1; }
+(! busctl call \
+    org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+    org.freedesktop.systemd1.Manager StartTransientUnit \
+    "ssa(sv)a(sa(sv))" test-20933-bad.service replace 1 \
+      ExecStart "a(sasb)" 1 \
+        /usr/bin/sleep 0 true \
+    0)
 
 # Same but with the empty argv in the middle
-busctl call \
-  org.freedesktop.systemd1 /org/freedesktop/systemd1 \
-  org.freedesktop.systemd1.Manager StartTransientUnit \
-  "ssa(sv)a(sa(sv))" test-20933-bad-middle.service replace 1 \
-    ExecStart "a(sasb)" 3 \
-      /usr/bin/sleep 2 /usr/bin/sleep 1 true \
-      /usr/bin/sleep 0                  true \
-      /usr/bin/sleep 2 /usr/bin/sleep 1 true \
-  0 && { echo 'unexpected success'; exit 1; }
+(! busctl call \
+    org.freedesktop.systemd1 /org/freedesktop/systemd1 \
+    org.freedesktop.systemd1.Manager StartTransientUnit \
+    "ssa(sv)a(sa(sv))" test-20933-bad-middle.service replace 1 \
+      ExecStart "a(sasb)" 3 \
+        /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+        /usr/bin/sleep 0                  true \
+        /usr/bin/sleep 2 /usr/bin/sleep 1 true \
+    0)
 
 systemd-analyze log-level info
 
index 4119d777b37a5e7b0f3bb8681f074060cb8968a3..860dc5bfdc70ab87c68352f4dd5ada8ab4c64cb1 100755 (executable)
@@ -36,7 +36,7 @@ cmp /var/tmp/testimage.raw /var/lib/machines/testimage3.raw
 # Test removal
 machinectl remove testimage
 test ! -f /var/lib/machines/testimage.raw
-machinectl image-status testimage && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status testimage)
 
 # Test export of clone
 machinectl export-raw testimage3 /var/tmp/testimage3.raw
@@ -48,7 +48,7 @@ machinectl rename testimage3 testimage4
 test -f /var/lib/machines/testimage4.raw
 machinectl image-status testimage4
 test ! -f /var/lib/machines/testimage3.raw
-machinectl image-status testimage3 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status testimage3)
 cmp /var/tmp/testimage.raw /var/lib/machines/testimage4.raw
 
 # Test export of rename
@@ -59,7 +59,7 @@ rm /var/tmp/testimage4.raw
 # Test removal
 machinectl remove testimage4
 test ! -f /var/lib/machines/testimage4.raw
-machinectl image-status testimage4 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status testimage4)
 
 # → And now, let's test directory trees ← #
 
@@ -92,7 +92,7 @@ diff -r /var/tmp/scratch/ /var/lib/machines/scratch2
 # Test removal
 machinectl remove scratch
 test ! -f /var/lib/machines/scratch
-machinectl image-status scratchi && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch)
 
 # Test clone
 machinectl clone scratch2 scratch3
@@ -105,20 +105,20 @@ diff -r /var/tmp/scratch/ /var/lib/machines/scratch3
 # Test removal
 machinectl remove scratch2
 test ! -f /var/lib/machines/scratch2
-machinectl image-status scratch2 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch2)
 
 # Test rename
 machinectl rename scratch3 scratch4
 test -d /var/lib/machines/scratch4
 machinectl image-status scratch4
 test ! -f /var/lib/machines/scratch3
-machinectl image-status scratch3 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch3)
 diff -r /var/tmp/scratch/ /var/lib/machines/scratch4
 
 # Test removal
 machinectl remove scratch4
 test ! -f /var/lib/machines/scratch4
-machinectl image-status scratch4 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch4)
 
 # Test import-tar hyphen/stdin pipe behavior
 # shellcheck disable=SC2002
@@ -138,7 +138,7 @@ rm -rf /var/tmp/scratch
 # Test removal
 machinectl remove scratch5
 test ! -f /var/lib/machines/scratch5
-machinectl image-status scratch5 && { echo 'unexpected success'; exit 1; }
+(! machinectl image-status scratch5)
 
 echo OK >/testok
 
index 7c046932fb920c637fd358755db1545eb67c8689..dc8e1e47c69d1de73f22a22e9ac5773a945e4b47 100755 (executable)
@@ -92,8 +92,6 @@ systemctl list-sockets --legend=no -a "*journal*"
 systemctl list-sockets --show-types
 systemctl list-sockets --state=listening
 systemctl list-timers -a -l
-systemctl list-unit-files
-systemctl list-unit-files "*journal*"
 systemctl list-jobs
 systemctl list-jobs --after
 systemctl list-jobs --before
@@ -105,6 +103,14 @@ systemctl list-dependencies sysinit.target --state=mounted --all
 systemctl list-paths
 systemctl list-paths --legend=no -a "systemd*"
 
+test_list_unit_files() {
+    systemctl list-unit-files "$@"
+    systemctl list-unit-files "$@" "*journal*"
+}
+
+test_list_unit_files
+test_list_unit_files --root=/
+
 # is-* verbs
 # Should return 4 for a missing unit file
 assert_rc 4 systemctl --quiet is-active not-found.service
@@ -132,45 +138,55 @@ systemctl stop "$UNIT_NAME"
 (! systemctl is-active "$UNIT_NAME")
 
 # enable/disable/preset
-(! systemctl is-enabled "$UNIT_NAME")
-systemctl enable "$UNIT_NAME"
-systemctl is-enabled -l "$UNIT_NAME"
-# We created a preset file for this unit above with a "disable" policy
-systemctl preset "$UNIT_NAME"
-(! systemctl is-enabled "$UNIT_NAME")
-systemctl reenable "$UNIT_NAME"
-systemctl is-enabled "$UNIT_NAME"
-systemctl preset --preset-mode=enable-only "$UNIT_NAME"
-systemctl is-enabled "$UNIT_NAME"
-systemctl preset --preset-mode=disable-only "$UNIT_NAME"
-(! systemctl is-enabled "$UNIT_NAME")
-systemctl enable --runtime "$UNIT_NAME"
-[[ -e "/run/systemd/system/multi-user.target.wants/$UNIT_NAME" ]]
-systemctl is-enabled "$UNIT_NAME"
-systemctl disable "$UNIT_NAME"
-# The unit should be still enabled, as we didn't use the --runtime switch
-systemctl is-enabled "$UNIT_NAME"
-systemctl disable --runtime "$UNIT_NAME"
-(! systemctl is-enabled "$UNIT_NAME")
+test_enable_disable_preset() {
+    (! systemctl is-enabled "$@" "$UNIT_NAME")
+    systemctl enable "$@" "$UNIT_NAME"
+    systemctl is-enabled "$@" -l "$UNIT_NAME"
+    # We created a preset file for this unit above with a "disable" policy
+    systemctl preset "$@" "$UNIT_NAME"
+    (! systemctl is-enabled "$@" "$UNIT_NAME")
+    systemctl reenable "$@" "$UNIT_NAME"
+    systemctl is-enabled "$@" "$UNIT_NAME"
+    systemctl preset "$@" --preset-mode=enable-only "$UNIT_NAME"
+    systemctl is-enabled "$@" "$UNIT_NAME"
+    systemctl preset "$@" --preset-mode=disable-only "$UNIT_NAME"
+    (! systemctl is-enabled "$@" "$UNIT_NAME")
+    systemctl enable "$@" --runtime "$UNIT_NAME"
+    [[ -e "/run/systemd/system/multi-user.target.wants/$UNIT_NAME" ]]
+    systemctl is-enabled "$@" "$UNIT_NAME"
+    systemctl disable "$@" "$UNIT_NAME"
+    # The unit should be still enabled, as we didn't use the --runtime switch
+    systemctl is-enabled "$@" "$UNIT_NAME"
+    systemctl disable "$@" --runtime "$UNIT_NAME"
+    (! systemctl is-enabled "$@" "$UNIT_NAME")
+}
+
+test_enable_disable_preset
+test_enable_disable_preset --root=/
 
 # mask/unmask/revert
-systemctl disable "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
-systemctl mask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked ]]
-systemctl unmask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
-systemctl mask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked ]]
-systemctl revert "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
-systemctl mask --runtime "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked-runtime ]]
-# This should be a no-op without the --runtime switch
-systemctl unmask "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == masked-runtime ]]
-systemctl unmask --runtime "$UNIT_NAME"
-[[ "$(systemctl is-enabled "$UNIT_NAME")" == disabled ]]
+test_mask_unmask_revert() {
+    systemctl disable "$@" "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+    systemctl mask "$@" "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked ]]
+    systemctl unmask "$@" "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+    systemctl mask "$@" "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked ]]
+    systemctl revert "$@" "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+    systemctl mask "$@" --runtime "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked-runtime ]]
+    # This should be a no-op without the --runtime switch
+    systemctl unmask "$@" "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked-runtime ]]
+    systemctl unmask "$@" --runtime "$UNIT_NAME"
+    [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]]
+}
+
+test_mask_unmask_revert
+test_mask_unmask_revert --root=/
 
 # add-wants/add-requires
 (! systemctl show -P Wants "$UNIT_NAME" | grep "systemd-journald.service")
@@ -229,11 +245,16 @@ for value in pretty us µs utc us+utc µs+utc; do
 done
 
 # set-default/get-default
-target="$(systemctl get-default)"
-systemctl set-default emergency.target
-[[ "$(systemctl get-default)" == emergency.target ]]
-systemctl set-default "$target"
-[[ "$(systemctl get-default)" == "$target" ]]
+test_get_set_default() {
+    target="$(systemctl get-default "$@")"
+    systemctl set-default "$@" emergency.target
+    [[ "$(systemctl get-default "$@")" == emergency.target ]]
+    systemctl set-default "$@" "$target"
+    [[ "$(systemctl get-default "$@")" == "$target" ]]
+}
+
+test_get_set_default
+test_get_set_default --root=/
 
 # show/status
 systemctl show --property ""
index 343fd9eb22112b3a10a6f9b3d85af6df0f84c061..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'
@@ -96,12 +96,20 @@ systemctl is-active app0.service
 status="$(portablectl is-attached --extension app0 minimal_0)"
 [[ "${status}" == "running-runtime" ]]
 
+grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
+
 timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
 
 systemctl is-active app0.service
 status="$(portablectl is-attached --extension app0 minimal_1)"
 [[ "${status}" == "running-runtime" ]]
 
+grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_1.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
+
 portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
 
 portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
@@ -189,6 +197,20 @@ portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 |
 portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/systemd/system/app1.service
 portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/systemd/system/app0.service
 
+grep -q -F "LogExtraFields=PORTABLE=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app0.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app0.service.d/20-portable.conf
+
+grep -q -F "LogExtraFields=PORTABLE=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
+grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app1.service.d/20-portable.conf
+
 portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
 
 # Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce.
index b951eef88b7bf95f6e5a17d9634f37abf0138b16..c9bd66e2688b246ec0c81d12b8949aafc3c8c2bb 100755 (executable)
@@ -5,13 +5,13 @@
 set -eux
 set -o pipefail
 
-cat >/etc/systemd/system/testservice.service <<EOF
+cat >/etc/systemd/system/test-service.service <<EOF
 [Service]
-ConfigurationDirectory=testservice
-RuntimeDirectory=testservice
-StateDirectory=testservice
-CacheDirectory=testservice
-LogsDirectory=testservice
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
 RuntimeDirectoryPreserve=yes
 ExecStart=/bin/sleep infinity
 Type=exec
@@ -19,70 +19,70 @@ EOF
 
 systemctl daemon-reload
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
 
-systemctl start testservice
+systemctl start test-service
 
-test -d /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice && { echo 'unexpected success'; exit 1; }
+(! systemctl clean test-service)
 
-systemctl stop testservice
+systemctl stop test-service
 
-test -d /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test -d /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice --what=configuration
+systemctl clean test-service --what=configuration
 
-test ! -e /etc/testservice
-test -d /run/testservice
-test -d /var/lib/testservice
-test -d /var/cache/testservice
-test -d /var/log/testservice
+test ! -e /etc/test-service
+test -d /run/test-service
+test -d /var/lib/test-service
+test -d /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice
+systemctl clean test-service
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test -d /var/lib/testservice
-test ! -e /var/cache/testservice
-test -d /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test -d /var/log/test-service
 
-systemctl clean testservice --what=logs
+systemctl clean test-service --what=logs
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test -d /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test -d /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
 
-systemctl clean testservice --what=all
+systemctl clean test-service --what=all
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
 
-cat >/etc/systemd/system/testservice.service <<EOF
+cat >/etc/systemd/system/test-service.service <<EOF
 [Service]
 DynamicUser=yes
-ConfigurationDirectory=testservice
-RuntimeDirectory=testservice
-StateDirectory=testservice
-CacheDirectory=testservice
-LogsDirectory=testservice
+ConfigurationDirectory=test-service
+RuntimeDirectory=test-service
+StateDirectory=test-service
+CacheDirectory=test-service
+LogsDirectory=test-service
 RuntimeDirectoryPreserve=yes
 ExecStart=/bin/sleep infinity
 Type=exec
@@ -90,85 +90,85 @@ EOF
 
 systemctl daemon-reload
 
-test ! -e /etc/testservice
-test ! -e /run/testservice
-test ! -e /var/lib/testservice
-test ! -e /var/cache/testservice
-test ! -e /var/log/testservice
-
-systemctl restart testservice
-
-test -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice && { echo 'unexpected success'; exit 1; }
-
-systemctl stop testservice
-
-test -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice --what=configuration
-
-test ! -d /etc/testservice
-test -d /run/private/testservice
-test -d /var/lib/private/testservice
-test -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test -L /run/testservice
-test -L /var/lib/testservice
-test -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test -d /var/log/private/testservice
-test ! -L /run/testservice
-test -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test -L /var/log/testservice
-
-systemctl clean testservice --what=logs
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test ! -d /var/log/private/testservice
-test ! -L /run/testservice
-test -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test ! -L /var/log/testservice
-
-systemctl clean testservice --what=all
-
-test ! -d /etc/testservice
-test ! -d /run/private/testservice
-test ! -d /var/lib/private/testservice
-test ! -d /var/cache/private/testservice
-test ! -d /var/log/private/testservice
-test ! -L /run/testservice
-test ! -L /var/lib/testservice
-test ! -L /var/cache/testservice
-test ! -L /var/log/testservice
+test ! -e /etc/test-service
+test ! -e /run/test-service
+test ! -e /var/lib/test-service
+test ! -e /var/cache/test-service
+test ! -e /var/log/test-service
+
+systemctl restart test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+(! systemctl clean test-service)
+
+systemctl stop test-service
+
+test -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=configuration
+
+test ! -d /etc/test-service
+test -d /run/private/test-service
+test -d /var/lib/private/test-service
+test -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test -L /run/test-service
+test -L /var/lib/test-service
+test -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test -L /var/log/test-service
+
+systemctl clean test-service --what=logs
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
+
+systemctl clean test-service --what=all
+
+test ! -d /etc/test-service
+test ! -d /run/private/test-service
+test ! -d /var/lib/private/test-service
+test ! -d /var/cache/private/test-service
+test ! -d /var/log/private/test-service
+test ! -L /run/test-service
+test ! -L /var/lib/test-service
+test ! -L /var/cache/test-service
+test ! -L /var/log/test-service
 
 cat >/etc/systemd/system/tmp-hoge.mount <<EOF
 [Mount]
@@ -197,7 +197,7 @@ test -d /var/lib/hoge
 test -d /var/cache/hoge
 test -d /var/log/hoge
 
-systemctl clean tmp-hoge.mount && { echo 'unexpected success'; exit 1; }
+(! systemctl clean tmp-hoge.mount)
 
 test -d /etc/hoge
 test -d /run/hoge
@@ -245,75 +245,75 @@ test ! -d /var/lib/hoge
 test ! -d /var/cache/hoge
 test ! -d /var/log/hoge
 
-cat >/etc/systemd/system/testservice.socket <<EOF
+cat >/etc/systemd/system/test-service.socket <<EOF
 [Socket]
-ListenSequentialPacket=/run/testservice.socket
+ListenSequentialPacket=/run/test-service.socket
 RemoveOnStop=yes
 ExecStartPre=true
-ConfigurationDirectory=testsocket
-RuntimeDirectory=testsocket
-StateDirectory=testsocket
-CacheDirectory=testsocket
-LogsDirectory=testsocket
+ConfigurationDirectory=test-socket
+RuntimeDirectory=test-socket
+StateDirectory=test-socket
+CacheDirectory=test-socket
+LogsDirectory=test-socket
 EOF
 
 systemctl daemon-reload
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test ! -e /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
 
-systemctl start testservice.socket
+systemctl start test-service.socket
 
-test -d /etc/testsocket
-test -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test -d /etc/test-socket
+test -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket && { echo 'unexpected success'; exit 1; }
+(! systemctl clean test-service.socket)
 
-systemctl stop testservice.socket
+systemctl stop test-service.socket
 
-test -d /etc/testsocket
-test ! -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test -d /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket --what=configuration
+systemctl clean test-service.socket --what=configuration
 
-test ! -e /etc/testsocket
-test ! -d /run/testsocket
-test -d /var/lib/testsocket
-test -d /var/cache/testsocket
-test -d /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -d /run/test-socket
+test -d /var/lib/test-socket
+test -d /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket
+systemctl clean test-service.socket
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test -d /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test -d /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test -d /var/log/test-socket
 
-systemctl clean testservice.socket --what=logs
+systemctl clean test-service.socket --what=logs
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test -d /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test -d /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
 
-systemctl clean testservice.socket --what=all
+systemctl clean test-service.socket --what=all
 
-test ! -e /etc/testsocket
-test ! -e /run/testsocket
-test ! -e /var/lib/testsocket
-test ! -e /var/cache/testsocket
-test ! -e /var/log/testsocket
+test ! -e /etc/test-socket
+test ! -e /run/test-socket
+test ! -e /var/lib/test-socket
+test ! -e /var/cache/test-socket
+test ! -e /var/log/test-socket
 
 echo OK >/testok
 
index 2172f7434b82536861c50a94a1dff849805edc08..0bc3adc9b40d9f61a2ea302404959bfb33712603 100755 (executable)
@@ -22,8 +22,7 @@ test_directory() {
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
-    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
-        && { echo 'unexpected success'; exit 1; }
+    (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
 
     test -d "${path}"/zzz
     test ! -L "${path}"/zzz
@@ -47,8 +46,7 @@ test_directory() {
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}=zzz:xxx zzz:xxx2" \
                 -p TemporaryFileSystem="${path}" -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
-    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
-        && { echo 'unexpected success'; exit 1; }
+    (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
 
     test -L "${path}"/zzz
     test -d "${path}"/private/zzz
@@ -71,8 +69,7 @@ test_directory() {
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
     systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
-    systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
-        && { echo 'unexpected success'; exit 1; }
+    (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing)
 
     test -d "${path}"/zzz
     test ! -L "${path}"/zzz
index 02f22cf2a2355f0d674fb351173c5ffee5e17b51..bfd11658d6257c4e7cd3aa6621d22af79a740adf 100755 (executable)
@@ -330,8 +330,8 @@ EOF
     systemctl restart getty@tty2.service
 
     # check session
-    for ((i = 0; i < 30; i++)); do
-        (( i != 0 )) && sleep 1
+    for i in {1..30}; do
+        (( i > 1 )) && sleep 1
         check_session && break
     done
     check_session
@@ -517,7 +517,7 @@ test_session_properties() {
     create_session
 
     s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
-    /usr/lib/systemd/tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}"
+    /usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}"
 }
 
 test_list_users() {
@@ -591,7 +591,7 @@ test_ambient_caps() {
     # CAP_CHOWN | CAP_KILL
     MASK=$(((1 << 0) | (1 << 5)))
 
-    if [ $(("$BND" & "$MASK")) -ne "$MASK" ] ; then
+    if [ $((BND & MASK)) -ne "$MASK" ] ; then
         echo "CAP_CHOWN or CAP_KILL not available in bounding set, skipping test." >&2
         return
     fi
index 5b77bbbaf173ae0c4e156635b07ef4f8bc66af85..dbeb1df8947f4a028ac4a925adc38d12335fdc2b 100755 (executable)
@@ -20,7 +20,7 @@ systemctl daemon-reload
 systemctl start "$SERVICE_NAME"
 systemctl status "$SERVICE_NAME"
 # The reload SHOULD fail but SHOULD NOT affect the service state
-systemctl reload "$SERVICE_NAME" && { echo 'unexpected success'; exit 1; }
+(! systemctl reload "$SERVICE_NAME")
 systemctl status "$SERVICE_NAME"
 systemctl stop "$SERVICE_NAME"
 
@@ -38,7 +38,7 @@ systemctl daemon-reload
 systemctl start "$SERVICE_NAME"
 systemctl status "$SERVICE_NAME"
 # The reload SHOULD fail but SHOULD NOT affect the service state
-systemctl reload "$SERVICE_NAME" && { echo 'unexpected success'; exit 1; }
+(! systemctl reload "$SERVICE_NAME")
 systemctl status "$SERVICE_NAME"
 systemctl stop "$SERVICE_NAME"
 
index a82258821f4dccbb8fabc9ce1b2f189a4f4c6065..a6ae7bc0107db6d3991b7d8fbd9eda29adfe5c30 100755 (executable)
@@ -9,15 +9,14 @@ MAX_SECS=60
 systemd-analyze log-level debug
 
 # test one: Restart=on-failure should restart the service
-systemd-run --unit=one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1" \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1")
 
 for ((secs = 0; secs < MAX_SECS; secs++)); do
-  [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]] || break
-  sleep 1
+    [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]] || break
+    sleep 1
 done
 if [[ "$(systemctl show one.service -P NRestarts)" -le 0 ]]; then
-  exit 1
+    exit 1
 fi
 
 TMP_FILE="/tmp/test-41-oneshot-restart-test"
@@ -26,27 +25,26 @@ TMP_FILE="/tmp/test-41-oneshot-restart-test"
 
 # test two: make sure StartLimitBurst correctly limits the number of restarts
 # and restarts execution of the unit from the first ExecStart=
-systemd-run --unit=two \
-            -p StartLimitIntervalSec=120 \
-            -p StartLimitBurst=3 \
-            -p Type=oneshot \
-            -p Restart=on-failure \
-            -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1" \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=two \
+        -p StartLimitIntervalSec=120 \
+        -p StartLimitBurst=3 \
+        -p Type=oneshot \
+        -p Restart=on-failure \
+        -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1")
 
 # wait for at least 3 restarts
 for ((secs = 0; secs < MAX_SECS; secs++)); do
-  [[ $(cat $TMP_FILE) != "aaa" ]] || break
-  sleep 1
+    [[ $(cat $TMP_FILE) != "aaa" ]] || break
+    sleep 1
 done
 if [[ $(cat $TMP_FILE) != "aaa" ]]; then
-  exit 1
+    exit 1
 fi
 
 # wait for 5 more seconds to make sure there aren't excess restarts
 sleep 5
 if [[ $(cat $TMP_FILE) != "aaa" ]]; then
-  exit 1
+    exit 1
 fi
 
 systemd-analyze log-level info
index 9476df86dde9037629570ba4156db4fa9698e2d0..b78d5b7a435ade2b8b5cc58c473e072116b44252 100755 (executable)
@@ -4,18 +4,20 @@ set -eux
 
 systemd-analyze log-level debug
 
-systemd-run --unit=simple1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple -p ExecStopPost='/bin/touch /run/simple1' true
+systemd-run --unit=simple1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple \
+    -p ExecStopPost='/bin/touch /run/simple1' true
 test -f /run/simple1
 
-systemd-run --unit=simple2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple -p ExecStopPost='/bin/touch /run/simple2' false \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=simple2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=simple \
+    -p ExecStopPost='/bin/touch /run/simple2' false)
 test -f /run/simple2
 
-systemd-run --unit=exec1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec -p ExecStopPost='/bin/touch /run/exec1' sleep 1
+systemd-run --unit=exec1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec \
+    -p ExecStopPost='/bin/touch /run/exec1' sleep 1
 test -f /run/exec1
 
-systemd-run --unit=exec2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec -p ExecStopPost='/bin/touch /run/exec2' sh -c 'sleep 1; false' \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=exec2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=exec \
+   -p ExecStopPost='/bin/touch /run/exec2' sh -c 'sleep 1; false')
 test -f /run/exec2
 
 cat >/tmp/forking1.sh <<EOF
@@ -31,7 +33,8 @@ systemd-notify MAINPID=\$MAINPID
 EOF
 chmod +x /tmp/forking1.sh
 
-systemd-run --unit=forking1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec -p ExecStopPost='/bin/touch /run/forking1' /tmp/forking1.sh
+systemd-run --unit=forking1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec \
+        -p ExecStopPost='/bin/touch /run/forking1' /tmp/forking1.sh
 test -f /run/forking1
 
 cat >/tmp/forking2.sh <<EOF
@@ -39,7 +42,7 @@ cat >/tmp/forking2.sh <<EOF
 
 set -eux
 
-( sleep 4; exit 1 ) &
+(sleep 4; exit 1) &
 MAINPID=\$!
 disown
 
@@ -47,28 +50,30 @@ systemd-notify MAINPID=\$MAINPID
 EOF
 chmod +x /tmp/forking2.sh
 
-systemd-run --unit=forking2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec -p ExecStopPost='/bin/touch /run/forking2' /tmp/forking2.sh \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=forking2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=forking -p NotifyAccess=exec \
+    -p ExecStopPost='/bin/touch /run/forking2' /tmp/forking2.sh)
 test -f /run/forking2
 
-systemd-run --unit=oneshot1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot -p ExecStopPost='/bin/touch /run/oneshot1' true
+systemd-run --unit=oneshot1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot \
+    -p ExecStopPost='/bin/touch /run/oneshot1' true
 test -f /run/oneshot1
 
-systemd-run --unit=oneshot2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot -p ExecStopPost='/bin/touch /run/oneshot2' false \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=oneshot2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=oneshot \
+    -p ExecStopPost='/bin/touch /run/oneshot2' false)
 test -f /run/oneshot2
 
-systemd-run --unit=dbus1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost -p ExecStopPost='/bin/touch /run/dbus1' \
-    busctl call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus RequestName su systemd.test.ExecStopPost 4 \
-    || :
+systemd-run --unit=dbus1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost \
+    -p ExecStopPost='/bin/touch /run/dbus1' \
+    busctl call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus RequestName su systemd.test.ExecStopPost 4 || :
 test -f /run/dbus1
 
-systemd-run --unit=dbus2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost -p ExecStopPost='/bin/touch /run/dbus2' true
+systemd-run --unit=dbus2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p BusName=systemd.test.ExecStopPost \
+     -p ExecStopPost='/bin/touch /run/dbus2' true
 test -f /run/dbus2
 
 # https://github.com/systemd/systemd/issues/19920
-systemd-run --unit=dbus3.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus -p ExecStopPost='/bin/touch /run/dbus3' true \
-     && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=dbus3.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=dbus \
+    -p ExecStopPost='/bin/touch /run/dbus3' true)
 
 cat >/tmp/notify1.sh <<EOF
 #!/usr/bin/env bash
@@ -79,18 +84,19 @@ systemd-notify --ready
 EOF
 chmod +x /tmp/notify1.sh
 
-systemd-run --unit=notify1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify -p ExecStopPost='/bin/touch /run/notify1' /tmp/notify1.sh
+systemd-run --unit=notify1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify \
+    -p ExecStopPost='/bin/touch /run/notify1' /tmp/notify1.sh
 test -f /run/notify1
 
-systemd-run --unit=notify2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify -p ExecStopPost='/bin/touch /run/notify2' true \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=notify2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=notify \
+    -p ExecStopPost='/bin/touch /run/notify2' true)
 test -f /run/notify2
 
 systemd-run --unit=idle1.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle -p ExecStopPost='/bin/touch /run/idle1' true
 test -f /run/idle1
 
-systemd-run --unit=idle2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle -p ExecStopPost='/bin/touch /run/idle2' false \
-     && { echo 'unexpected success'; exit 1; }
+(! systemd-run --unit=idle2.service --wait -p StandardOutput=tty -p StandardError=tty -p Type=idle \
+     -p ExecStopPost='/bin/touch /run/idle2' false)
 test -f /run/idle2
 
 systemd-analyze log-level info
index 40b94fdb48635d35aaf8b645d5fef5c814b1408c..ae979d734b718ab7d6ebfffbaf85a2bc7fbdf67c 100755 (executable)
@@ -8,8 +8,7 @@ systemd-analyze log-level debug
 runas() {
     declare userid=$1
     shift
-    # shellcheck disable=SC2016
-    su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+    XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
 }
 
 runas testuser systemd-run --wait --user --unit=test-private-users \
@@ -18,7 +17,7 @@ runas testuser systemd-run --wait --user --unit=test-private-users \
 runas testuser systemctl --user log-level debug
 
 runas testuser systemd-run --wait --user --unit=test-private-tmp-innerfile \
-    -p PrivateUsers=yes -p PrivateTmp=yes \
+    -p PrivateTmp=yes \
     -P touch /tmp/innerfile.txt
 # File should not exist outside the job's tmp directory.
 test ! -e /tmp/innerfile.txt
@@ -26,7 +25,7 @@ test ! -e /tmp/innerfile.txt
 touch /tmp/outerfile.txt
 # File should not appear in unit's private tmp.
 runas testuser systemd-run --wait --user --unit=test-private-tmp-outerfile \
-    -p PrivateUsers=yes -p PrivateTmp=yes \
+    -p PrivateTmp=yes \
     -P test ! -e /tmp/outerfile.txt
 
 # Confirm that creating a file in home works
@@ -35,24 +34,23 @@ runas testuser systemd-run --wait --user --unit=test-unprotected-home \
 test -e /home/testuser/works.txt
 
 # Confirm that creating a file in home is blocked under read-only
-runas testuser systemd-run --wait --user --unit=test-protect-home-read-only \
-    -p PrivateUsers=yes -p ProtectHome=read-only \
+(! runas testuser systemd-run --wait --user --unit=test-protect-home-read-only \
+    -p ProtectHome=read-only \
     -P bash -c '
         test -e /home/testuser/works.txt || exit 10
         touch /home/testuser/blocked.txt && exit 11
-    ' \
-        && { echo 'unexpected success'; exit 1; }
+    ')
 test ! -e /home/testuser/blocked.txt
 
 # Check that tmpfs hides the whole directory
 runas testuser systemd-run --wait --user --unit=test-protect-home-tmpfs \
-    -p PrivateUsers=yes -p ProtectHome=tmpfs \
+    -p ProtectHome=tmpfs \
     -P test ! -e /home/testuser
 
 # Confirm that home, /root, and /run/user are inaccessible under "yes"
 # shellcheck disable=SC2016
 runas testuser systemd-run --wait --user --unit=test-protect-home-yes \
-    -p PrivateUsers=yes -p ProtectHome=yes \
+    -p ProtectHome=yes \
     -P bash -c '
         test "$(stat -c %a /home)" = "0"
         test "$(stat -c %a /root)" = "0"
@@ -63,19 +61,18 @@ runas testuser systemd-run --wait --user --unit=test-protect-home-yes \
 # namespace (no CAP_SETGID in the parent namespace to write the additional
 # mapping of the user supplied group and thus cannot change groups to an
 # unmapped group ID)
-runas testuser systemd-run --wait --user --unit=test-group-fail \
+(! runas testuser systemd-run --wait --user --unit=test-group-fail \
     -p PrivateUsers=yes -p Group=daemon \
-    -P true \
-    && { echo 'unexpected success'; exit 1; }
+    -P true)
 
 # Check that with a new user namespace we can bind mount
 # files and use a different root directory
 runas testuser systemd-run --wait --user --unit=test-bind-mount \
-    -p PrivateUsers=yes -p BindPaths=/dev/null:/etc/os-release \
+    -p BindPaths=/dev/null:/etc/os-release \
     test ! -s /etc/os-release
 
 runas testuser systemd-run --wait --user --unit=test-read-write \
-    -p PrivateUsers=yes -p ReadOnlyPaths=/ \
+    -p ReadOnlyPaths=/ \
     -p ReadWritePaths="/var /run /tmp" \
     -p NoExecPaths=/ -p ExecPaths=/usr \
     test ! -w /etc/os-release
@@ -86,50 +83,45 @@ runas testuser systemd-run --wait --user --unit=test-caps \
     test -s /etc/os-release
 
 runas testuser systemd-run --wait --user --unit=test-devices \
-    -p PrivateUsers=yes -p PrivateDevices=yes -p PrivateIPC=yes \
+    -p PrivateDevices=yes -p PrivateIPC=yes \
     sh -c "ls -1 /dev/ | wc -l | grep -q -F 18"
 
 # Same check as test/test-execute/exec-privatenetwork-yes.service
 runas testuser systemd-run --wait --user --unit=test-network \
-    -p PrivateUsers=yes -p PrivateNetwork=yes \
+    -p PrivateNetwork=yes \
     /bin/sh -x -c '! ip link | grep -E "^[0-9]+: " | grep -Ev ": (lo|(erspan|gre|gretap|ip_vti|ip6_vti|ip6gre|ip6tnl|sit|tunl)0@.*):"'
 
-runas testuser systemd-run --wait --user --unit=test-hostname \
-    -p PrivateUsers=yes -p ProtectHostname=yes \
-    hostnamectl hostname foo \
-    && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-hostname \
+    -p ProtectHostname=yes \
+    hostnamectl hostname foo)
 
-runas testuser systemd-run --wait --user --unit=test-clock \
-    -p PrivateUsers=yes -p ProtectClock=yes \
-    timedatectl set-time "2012-10-30 18:17:16" \
-    && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-clock \
+    -p ProtectClock=yes \
+    timedatectl set-time "2012-10-30 18:17:16")
 
-runas testuser systemd-run --wait --user --unit=test-kernel-tunable \
-    -p PrivateUsers=yes -p ProtectKernelTunables=yes \
-    sh -c "echo 0 >/proc/sys/user/max_user_namespaces" \
-    && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-kernel-tunable \
+    -p ProtectKernelTunables=yes \
+    sh -c "echo 0 >/proc/sys/user/max_user_namespaces")
 
-runas testuser systemd-run --wait --user --unit=test-kernel-mod \
-    -p PrivateUsers=yes -p ProtectKernelModules=yes \
-    sh -c "modprobe -r overlay && modprobe overlay" \
-    && { echo 'unexpected success'; exit 1; }
+(! runas testuser systemd-run --wait --user --unit=test-kernel-mod \
+    -p ProtectKernelModules=yes \
+    sh -c "modprobe -r overlay && modprobe overlay")
 
 if sysctl kernel.dmesg_restrict=0; then
-    runas testuser systemd-run --wait --user --unit=test-kernel-log \
-        -p PrivateUsers=yes -p ProtectKernelLogs=yes -p LogNamespace=yes \
-        dmesg \
-        && { echo 'unexpected success'; exit 1; }
+    (! runas testuser systemd-run --wait --user --unit=test-kernel-log \
+        -p ProtectKernelLogs=yes -p LogNamespace=yes \
+        dmesg)
 fi
 
 unsquashfs -no-xattrs -d /tmp/img /usr/share/minimal_0.raw
 runas testuser systemd-run --wait --user --unit=test-root-dir \
-    -p PrivateUsers=yes -p RootDirectory=/tmp/img \
+    -p RootDirectory=/tmp/img \
     grep MARKER=1 /etc/os-release
 
 mkdir /tmp/img_bind
 mount --bind /tmp/img /tmp/img_bind
 runas testuser systemd-run --wait --user --unit=test-root-dir-bind \
-    -p PrivateUsers=yes -p RootDirectory=/tmp/img_bind -p MountFlags=private \
+    -p RootDirectory=/tmp/img_bind -p MountFlags=private \
     grep MARKER=1 /etc/os-release
 umount /tmp/img_bind
 
@@ -138,7 +130,7 @@ mkdir -p /tmp/a /tmp/b /tmp/c
 if unshare --mount --user --map-root-user mount -t overlay overlay /tmp/c -o lowerdir=/tmp/a:/tmp/b; then
     unsquashfs -no-xattrs -d /tmp/app2 /usr/share/app1.raw
     runas testuser systemd-run --wait --user --unit=test-extension-dir \
-        -p PrivateUsers=yes -p ExtensionDirectories=/tmp/app2 \
+        -p ExtensionDirectories=/tmp/app2 \
         -p TemporaryFileSystem=/run -p RootDirectory=/tmp/img \
         -p MountAPIVFS=yes \
         grep PORTABLE_PREFIXES=app1 /usr/lib/extension-release.d/extension-release.app2
index 49c240ff8e45b43026b8a061879f8cf9055b2821..da779a6901c97de92e41a0b2f59ee6a754eaba2d 100755 (executable)
@@ -11,7 +11,7 @@ journalctl -o cat --namespace=foobar >/tmp/hello-world
 journalctl -o cat >/tmp/no-hello-world
 
 grep "^hello world$" /tmp/hello-world
-grep "^hello world$" /tmp/no-hello-world && { echo 'unexpected success'; exit 1; }
+(! grep "^hello world$" /tmp/no-hello-world)
 
 systemd-analyze log-level info
 
index ebc6c7c1444252f252f659ecfb8adb30808d2dc9..74b3a2c6a97f17edfee92263f932bb8484deb771 100755 (executable)
@@ -7,6 +7,27 @@ set -o pipefail
 # shellcheck source=test/units/assert.sh
 . "$(dirname "$0")"/assert.sh
 
+test_timedatectl() {
+    timedatectl --no-pager --help
+    timedatectl --version
+
+    timedatectl
+    timedatectl --no-ask-password
+    timedatectl status --machine=testuser@.host
+    timedatectl status
+    timedatectl show
+    timedatectl show --all
+    timedatectl show -p NTP
+    timedatectl show -p NTP --value
+    timedatectl list-timezones
+
+    if ! systemd-detect-virt -qc; then
+        systemctl enable --runtime --now systemd-timesyncd
+        timedatectl timesync-status
+        timedatectl show-timesync
+    fi
+}
+
 restore_timezone() {
     if [[ -f /tmp/timezone.bak ]]; then
         mv /tmp/timezone.bak /etc/timezone
@@ -191,8 +212,8 @@ start_mon() {
 }
 
 wait_mon() {
-    for ((i = 0; i < 10; i++)); do
-        if (( i != 0 )); then sleep 1; fi
+    for i in {1..10}; do
+        (( i > 1 )) && sleep 1
         if grep -q "$1" "$mon"; then break; fi
     done
     assert_in "$2" "$(cat "$mon")"
@@ -222,8 +243,8 @@ EOF
 
     echo 'disable NTP'
     timedatectl set-ntp false
-    for ((i = 0; i < 10; i++)); do
-        if (( i != 0 )); then sleep 1; fi
+    for i in {1..10}; do
+        (( i > 1 )) && sleep 1
         if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=inactive" ]]; then
             break;
         fi
@@ -237,8 +258,8 @@ EOF
     timedatectl set-ntp true
     wait_mon "NTP" "BOOLEAN true"
     assert_ntp "true"
-    for ((i = 0; i < 10; i++)); do
-        if (( i != 0 )); then sleep 1; fi
+    for i in {1..10}; do
+        (( i > 1 )) && sleep 1
         if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=active" ]]; then
             break;
         fi
@@ -256,6 +277,7 @@ EOF
 
 : >/failed
 
+test_timedatectl
 test_timezone
 test_adjtime
 test_ntp
index 26b3350b51508757b72298fa17aa093ee13831ba..5efb9cc38366d699d00b5c4dfd3b7c996a3bcc0f 100644 (file)
@@ -3,8 +3,8 @@
 Description=TEST-46-HOMED
 Wants=getty-pre.target
 Before=getty-pre.target
-Wants=systemd-homed.service
-After=systemd-homed.service
+Requires=systemd-homed.service systemd-userdbd.socket
+After=systemd-homed.service systemd-userdbd.socket
 
 [Service]
 ExecStartPre=rm -f /failed /testok
index 9d090654bc1f0cdc0123371d04c285864ca9cad0..ec80b7147f990c9d49df12cdc7572f059ba0055f 100755 (executable)
@@ -27,9 +27,9 @@ inspect() {
 }
 
 wait_for_state() {
-    for ((i = 0; i < 10; i++)) ; do
+    for i in {1..10}; do
+        (( i > 1 )) && sleep 0.5
         homectl inspect "$1" | grep -qF "State: $2" && break
-        sleep .5
     done
 }
 
@@ -153,14 +153,12 @@ if ! systemd-detect-virt -cq ; then
 fi
 
 PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
-PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz \
-    && { echo 'unexpected success'; exit 1; }
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
 PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz
 PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
 PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz
 PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
-PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz \
-    && { echo 'unexpected success'; exit 1; }
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
 
 wait_for_state test-user inactive
 homectl remove test-user
@@ -172,6 +170,142 @@ if ! systemd-detect-virt -cq ; then
     homectl remove test-user2
 fi
 
+# userdbctl tests
+export PAGER=
+
+# Create a couple of user/group records to test io.systemd.DropIn
+# See docs/USER_RECORD.md and docs/GROUP_RECORD.md
+mkdir -p /run/userdb/
+cat >"/run/userdb/dropingroup.group" <<\EOF
+{
+    "groupName" : "dropingroup",
+    "gid"       : 1000000
+}
+EOF
+cat >"/run/userdb/dropinuser.user" <<\EOF
+{
+    "userName" : "dropinuser",
+    "uid"      : 2000000,
+    "realName" : "🐱",
+    "memberOf" : [
+        "dropingroup"
+    ]
+}
+EOF
+cat >"/run/userdb/dropinuser.user-privileged" <<\EOF
+{
+    "privileged" : {
+        "hashedPassword" : [
+            "$6$WHBKvAFFT9jKPA4k$OPY4D4TczKN/jOnJzy54DDuOOagCcvxxybrwMbe1SVdm.Bbr.zOmBdATp.QrwZmvqyr8/SafbbQu.QZ2rRvDs/"
+        ],
+        "sshAuthorizedKeys" : [
+            "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA//dxI2xLg4MgxIKKZv1nqwTEIlE/fdakii2Fb75pG+ foo@bar.tld",
+            "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMlaqG2rTMje5CQnfjXJKmoSpEVJ2gWtx4jBvsQbmee2XbU/Qdq5+SRisssR9zVuxgg5NA5fv08MgjwJQMm+csc= hello@world.tld"
+        ]
+    }
+}
+EOF
+# Set permissions and create necessary symlinks as described in nss-systemd(8)
+chmod 0600 "/run/userdb/dropinuser.user-privileged"
+ln -svrf "/run/userdb/dropingroup.group" "/run/userdb/1000000.group"
+ln -svrf "/run/userdb/dropinuser.user" "/run/userdb/2000000.user"
+ln -svrf "/run/userdb/dropinuser.user-privileged" "/run/userdb/2000000.user-privileged"
+
+userdbctl
+userdbctl --version
+userdbctl --help --no-pager
+userdbctl --no-legend
+userdbctl --output=classic
+userdbctl --output=friendly
+userdbctl --output=table
+userdbctl --output=json | jq
+userdbctl -j --json=pretty | jq
+userdbctl -j --json=short | jq
+userdbctl --with-varlink=no
+
+userdbctl user
+userdbctl user testuser
+userdbctl user root
+userdbctl user testuser root
+userdbctl user -j testuser root | jq
+# Check only UID for the nobody user, since the name is build-configurable
+userdbctl user --with-nss=no --synthesize=yes
+userdbctl user --with-nss=no --synthesize=yes 0 root 65534
+userdbctl user dropinuser
+userdbctl user 2000000
+userdbctl user --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropinuser
+userdbctl user --with-nss=no 2000000
+(! userdbctl user '')
+(! userdbctl user 🐱)
+(! userdbctl user 🐱 '' bar)
+(! userdbctl user i-do-not-exist)
+(! userdbctl user root i-do-not-exist testuser)
+(! userdbctl user --with-nss=no --synthesize=no 0 root 65534)
+(! userdbctl user -N root nobody)
+(! userdbctl user --with-dropin=no dropinuser)
+(! userdbctl user --with-dropin=no 2000000)
+
+userdbctl group
+userdbctl group testuser
+userdbctl group root
+userdbctl group testuser root
+userdbctl group -j testuser root | jq
+# Check only GID for the nobody group, since the name is build-configurable
+userdbctl group --with-nss=no --synthesize=yes
+userdbctl group --with-nss=no --synthesize=yes 0 root 65534
+userdbctl group dropingroup
+userdbctl group 1000000
+userdbctl group --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropingroup
+userdbctl group --with-nss=no 1000000
+(! userdbctl group '')
+(! userdbctl group 🐱)
+(! userdbctl group 🐱 '' bar)
+(! userdbctl group i-do-not-exist)
+(! userdbctl group root i-do-not-exist testuser)
+(! userdbctl group --with-nss=no --synthesize=no 0 root 65534)
+(! userdbctl group --with-dropin=no dropingroup)
+(! userdbctl group --with-dropin=no 1000000)
+
+userdbctl users-in-group
+userdbctl users-in-group testuser
+userdbctl users-in-group testuser root
+userdbctl users-in-group -j testuser root | jq
+userdbctl users-in-group 🐱
+(! userdbctl users-in-group '')
+(! userdbctl users-in-group foo '' bar)
+
+userdbctl groups-of-user
+userdbctl groups-of-user testuser
+userdbctl groups-of-user testuser root
+userdbctl groups-of-user -j testuser root | jq
+userdbctl groups-of-user 🐱
+(! userdbctl groups-of-user '')
+(! userdbctl groups-of-user foo '' bar)
+
+userdbctl services
+userdbctl services -j | jq
+
+userdbctl ssh-authorized-keys dropinuser | tee /tmp/authorized-keys
+grep "ssh-ed25519" /tmp/authorized-keys
+grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+echo "my-top-secret-key 🐱" >/tmp/my-top-secret-key
+userdbctl ssh-authorized-keys dropinuser --chain /bin/cat /tmp/my-top-secret-key | tee /tmp/authorized-keys
+grep "ssh-ed25519" /tmp/authorized-keys
+grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+grep "my-top-secret-key 🐱" /tmp/authorized-keys
+(! userdbctl ssh-authorized-keys 🐱)
+(! userdbctl ssh-authorized-keys dropin-user --chain)
+(! userdbctl ssh-authorized-keys dropin-user --chain '')
+(! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /bin/false)
+
+(! userdbctl '')
+for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do
+    (! userdbctl "--$opt=''")
+    (! userdbctl "--$opt='🐱'")
+    (! userdbctl "--$opt=foo")
+    (! userdbctl "--$opt=foo" "--$opt=''" "--$opt=🐱")
+done
+
 systemd-analyze log-level info
 
 echo OK >/testok
index af7d3511546a7ae0d9ecc03879ebb5d569ea4a57..1b703dc18c6b94283d64b79c8e8d981812687804 100755 (executable)
@@ -231,6 +231,33 @@ fi
 systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1"
 systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release")
 
+# Test image policies
+systemd-dissect --validate "${image}.gpt"
+systemd-dissect --validate "${image}.gpt" --image-policy='*'
+(! systemd-dissect --validate "${image}.gpt" --image-policy='~')
+(! systemd-dissect --validate "${image}.gpt" --image-policy='-')
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=absent)
+(! systemd-dissect --validate "${image}.gpt" --image-policy=swap=unprotected+encrypted+verity)
+systemd-dissect --validate "${image}.gpt" --image-policy=root=unprotected
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity-sig=unused+absent
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent
+systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent+unprotected
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity=unused+absent)
+systemd-dissect --validate "${image}.gpt" --image-policy=root=signed
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity-sig=unused+absent)
+(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity=unused+absent)
+
+# Test RootImagePolicy= unit file setting
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='*' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='~' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='-' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=absent' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=verity' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=signed' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
+(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=encrypted' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
+
 systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/mount"
 grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release"
 grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release"
@@ -371,8 +398,8 @@ systemctl is-active testservice-50e.service
 
 # ExtensionDirectories will set up an overlay
 mkdir -p "${image_dir}/app0" "${image_dir}/app1" "${image_dir}/app-nodistro"
-systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistent" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
-systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh && { echo 'unexpected success'; exit 1; }
+(! systemd-run -P --property ExtensionDirectories="${image_dir}/nonexistent" --property RootImage="${image}.raw" cat /opt/script0.sh)
+(! systemd-run -P --property ExtensionDirectories="${image_dir}/app0" --property RootImage="${image}.raw" cat /opt/script0.sh)
 systemd-dissect --mount /usr/share/app0.raw "${image_dir}/app0"
 systemd-dissect --mount /usr/share/app1.raw "${image_dir}/app1"
 systemd-dissect --mount /usr/share/app-nodistro.raw "${image_dir}/app-nodistro"
@@ -413,6 +440,17 @@ systemd-sysext merge
 test ! -e /usr/lib/systemd/system/some_file
 systemd-sysext unmerge
 rmdir /etc/extensions/app-nodistro
+
+# Check that extensions cannot contain os-release
+mkdir -p /run/extensions/app-reject/usr/lib/{extension-release.d/,systemd/system}
+echo "ID=_any" >/run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject
+echo "ID=_any" >/run/extensions/app-reject/usr/lib/os-release
+touch /run/extensions/app-reject/usr/lib/systemd/system/other_file
+(! systemd-sysext merge)
+test ! -e /usr/lib/systemd/system/some_file
+test ! -e /usr/lib/systemd/system/other_file
+systemd-sysext unmerge
+rm -rf /run/extensions/app-reject
 rm /var/lib/extensions/app-nodistro.raw
 
 mkdir -p /run/machines /run/portables /run/extensions
@@ -451,7 +489,7 @@ systemd-dissect --detach "$LOOP"
 # Note, sizeof_field(struct loop_info64, lo_file_name) == 64,
 # and --loop-ref accepts upto 63 characters, and udev creates symlink
 # based on the name when it has upto _62_ characters.
-name="$(for (( i = 0; i < 62; i++ )); do echo -n 'x'; done)"
+name="$(for _ in {1..62}; do echo -n 'x'; done)"
 LOOP="$(systemd-dissect --attach --loop-ref="$name" "${image}.raw")"
 udevadm trigger -w "$LOOP"
 
@@ -461,7 +499,7 @@ test "/dev/loop/by-ref/$name" -ef "$LOOP"
 # Detach by the /dev/loop/by-ref symlink
 systemd-dissect --detach "/dev/loop/by-ref/$name"
 
-name="$(for (( i = 0; i < 63; i++ )); do echo -n 'x'; done)"
+name="$(for _ in {1..63}; do echo -n 'x'; done)"
 LOOP="$(systemd-dissect --attach --loop-ref="$name" "${image}.raw")"
 udevadm trigger -w "$LOOP"
 
@@ -472,6 +510,23 @@ 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
+cat <<EOF >/run/confexts/test/etc/testscript
+#!/bin/bash
+echo "This should not happen"
+EOF
+chmod +x /run/confexts/test/etc/testscript
+systemd-confext merge
+grep -q -F "MARKER_CONFEXT_123" /etc/testfile
+(! /etc/testscript)
+systemd-confext status
+systemd-confext unmerge
+rm -rf /run/confexts/
+
 echo OK >/testok
 
 exit 0
index dd7e28392d24cfa8338a9d665ac36d8f1000eabd..d243d93d8132e5b57cddb31c136e94ab5476865e 100755 (executable)
@@ -5,15 +5,163 @@ set -eux
 
 systemd-analyze log-level debug
 
+run_with_cred_compare() {
+    local cred="${1:?}"
+    local exp="${2?}"
+    shift 2
+
+    diff <(systemd-run -p SetCredential="$cred" --wait --pipe -- systemd-creds "$@") <(echo -ne "$exp")
+}
+
+# Sanity checks
+#
+# Create a dummy "full" disk (similar to /dev/full) to check out-of-space
+# scenarios
+mkdir /tmp/full
+mount -t tmpfs -o size=1,nr_inodes=1 tmpfs /tmp/full
+
+# verb: setup
+# Run this first, otherwise any encrypted credentials wouldn't be decryptable
+# as we regnerate the host key
+rm -fv /var/lib/systemd/credential.secret
+systemd-creds setup
+test -e /var/lib/systemd/credential.secret
+rm -fv /var/lib/systemd/credential.secret
+
+# Prepare a couple of dummy credentials for the cat/list verbs
+CRED_DIR="$(mktemp -d)"
+ENC_CRED_DIR="$(mktemp -d)"
+echo foo >"$CRED_DIR/secure-or-weak"
+echo foo >"$CRED_DIR/insecure"
+echo foo | systemd-creds --name="encrypted" encrypt - - | base64 -d >"$ENC_CRED_DIR/encrypted"
+echo foo | systemd-creds encrypt - - | base64 -d >"$ENC_CRED_DIR/encrypted-unnamed"
+chmod -R 0400 "$CRED_DIR" "$ENC_CRED_DIR"
+chmod -R 0444 "$CRED_DIR/insecure"
+mkdir /tmp/empty/
+
+systemd-creds --system
+systemd-creds --no-pager --help
+systemd-creds --version
+systemd-creds has-tpm2 || :
+systemd-creds has-tpm2 -q || :
+
+# verb: list
+systemd-creds list --system
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --no-legend
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=pretty | jq
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=short | jq
+ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=off
+ENCRYPTED_CREDENTIALS_DIRECTORY="/tmp/empty/" CREDENTIALS_DIRECTORY="/tmp/empty/" systemd-creds list
+
+# verb: cat
+for cred in secure-or-weak insecure encrypted encrypted-unnamed; do
+    ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds cat "$cred"
+done
+run_with_cred_compare "mycred:" "" cat mycred
+run_with_cred_compare "mycred:\n" "\n" cat mycred
+run_with_cred_compare "mycred:foo" "foo" cat mycred
+run_with_cred_compare "mycred:foo" "foofoofoo" cat mycred mycred mycred
+# Note: --newline= does nothing when stdout is not a tty, which is the case here
+run_with_cred_compare "mycred:foo" "foo" --newline=yes cat mycred
+run_with_cred_compare "mycred:foo" "foo" --newline=no cat mycred
+run_with_cred_compare "mycred:foo" "foo" --newline=auto cat mycred
+run_with_cred_compare "mycred:foo" "foo" --transcode=no cat mycred
+run_with_cred_compare "mycred:foo" "foo" --transcode=0 cat mycred
+run_with_cred_compare "mycred:foo" "foo" --transcode=false cat mycred
+run_with_cred_compare "mycred:foo" "Zm9v" --transcode=base64 cat mycred
+run_with_cred_compare "mycred:Zm9v" "foo" --transcode=unbase64 cat mycred
+run_with_cred_compare "mycred:Zm9v" "foofoofoo" --transcode=unbase64 cat mycred mycred mycred
+run_with_cred_compare "mycred:Zm9vCg==" "foo\n" --transcode=unbase64 cat mycred
+run_with_cred_compare "mycred:hello world" "68656c6c6f20776f726c64" --transcode=hex cat mycred
+run_with_cred_compare "mycred:68656c6c6f20776f726c64" "hello world" --transcode=unhex cat mycred
+run_with_cred_compare "mycred:68656c6c6f20776f726c64" "hello worldhello world" --transcode=unhex cat mycred mycred
+run_with_cred_compare "mycred:68656c6c6f0a776f726c64" "hello\nworld" --transcode=unhex cat mycred
+run_with_cred_compare 'mycred:{ "foo" : "bar", "baz" : [ 3, 4 ] }' '{"foo":"bar","baz":[3,4]}\n' --json=short cat mycred
+systemd-run -p SetCredential='mycred:{ "foo" : "bar", "baz" : [ 3, 4 ] }' --wait --pipe -- systemd-creds --json=pretty cat mycred | jq
+
+# verb: encrypt/decrypt
+echo "According to all known laws of aviation..." >/tmp/cred.orig
+systemd-creds --with-key=host encrypt /tmp/cred.orig /tmp/cred.enc
+systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.{enc,dec}
+# --pretty
+cred_name="fo'''o''bar"
+cred_option="$(systemd-creds --pretty --name="$cred_name" encrypt /tmp/cred.orig -)"
+mkdir -p /run/systemd/system
+cat >/run/systemd/system/test-54-pretty-cred.service <<EOF
+[Service]
+Type=oneshot
+${cred_option:?}
+ExecStart=bash -c "diff <(systemd-creds cat \"$cred_name\") /tmp/cred.orig"
+EOF
+systemctl daemon-reload
+systemctl start test-54-pretty-cred
+rm /run/systemd/system/test-54-pretty-cred.service
+# Credential validation: name
+systemd-creds --name="foo" -H encrypt /tmp/cred.orig /tmp/cred.enc
+(! systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec)
+(! systemd-creds --name="bar" decrypt /tmp/cred.enc /tmp/cred.dec)
+systemd-creds --name="" decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.dec
+systemd-creds --name="foo" decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.{enc,dec}
+# Credential validation: time
+systemd-creds --not-after="+1d" encrypt /tmp/cred.orig /tmp/cred.enc
+(! systemd-creds --timestamp="+2d" decrypt /tmp/cred.enc /tmp/cred.dec)
+systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec
+diff /tmp/cred.orig /tmp/cred.dec
+rm -f /tmp/cred.{enc,dec}
+
+(! unshare -m bash -exc "mount -t tmpfs tmpfs /run/credentials && systemd-creds list")
+(! unshare -m bash -exc "mount -t tmpfs tmpfs /run/credentials && systemd-creds --system list")
+(! CREDENTIALS_DIRECTORY="" systemd-creds list)
+(! systemd-creds --system --foo)
+(! systemd-creds --system -@)
+(! systemd-creds --system --json=)
+(! systemd-creds --system --json="")
+(! systemd-creds --system --json=foo)
+(! systemd-creds --system cat)
+(! systemd-creds --system cat "")
+(! systemd-creds --system cat this-should-not-exist)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --transcode= cat mycred)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --transcode="" cat mycred)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --transcode=foo cat mycred)
+(! systemd-run -p SetCredential=mycred:foo --wait --pipe -- systemd-creds --newline=foo cat mycred)
+(! systemd-run -p SetCredential=mycred:notbase64 --wait --pipe -- systemd-creds --transcode=unbase64 cat mycred)
+(! systemd-run -p SetCredential=mycred:nothex --wait --pipe -- systemd-creds --transcode=unhex cat mycred)
+(! systemd-run -p SetCredential=mycred:a --wait --pipe -- systemd-creds --transcode=unhex cat mycred)
+(! systemd-run -p SetCredential=mycred:notjson --wait --pipe -- systemd-creds --json=short cat mycred)
+(! systemd-run -p SetCredential=mycred:notjson --wait --pipe -- systemd-creds --json=pretty cat mycred)
+(! systemd-creds encrypt /foo/bar/baz -)
+(! systemd-creds decrypt /foo/bar/baz -)
+(! systemd-creds decrypt / -)
+(! systemd-creds encrypt / -)
+(! echo foo | systemd-creds --with-key=foo encrypt - -)
+(! echo {0..20} | systemd-creds decrypt - -)
+(! systemd-creds --not-after= encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --not-after="" encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --not-after="-1d" encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --timestamp= encrypt /tmp/cred.orig /tmp/cred.enc)
+(! systemd-creds --timestamp="" encrypt /tmp/cred.orig /tmp/cred.enc)
+(! dd if=/dev/zero count=2M | systemd-creds --with-key=tpm2-absent encrypt - /dev/null)
+(! dd if=/dev/zero count=2M | systemd-creds --with-key=tpm2-absent decrypt - /dev/null)
+(! echo foo | systemd-creds encrypt - /tmp/full/foo)
+(! echo foo | systemd-creds encrypt - - | systemd-creds decrypt - /tmp/full/foo)
+
 # Verify that the creds are properly loaded and we can read them from the service's unpriv user
 systemd-run -p LoadCredential=passwd:/etc/passwd \
-            -p LoadCredential=shadow:/etc/shadow \
-            -p SetCredential=dog:wuff \
-            -p DynamicUser=1 \
-            --wait \
-            --pipe \
-            cat '${CREDENTIALS_DIRECTORY}/passwd' '${CREDENTIALS_DIRECTORY}/shadow' '${CREDENTIALS_DIRECTORY}/dog' >/tmp/ts54-concat
-( cat /etc/passwd /etc/shadow && echo -n wuff ) | cmp /tmp/ts54-concat
+    -p LoadCredential=shadow:/etc/shadow \
+    -p SetCredential=dog:wuff \
+    -p DynamicUser=1 \
+    --unit=test-54-unpriv.service \
+    --wait \
+    --pipe \
+    cat '${CREDENTIALS_DIRECTORY}/passwd' '${CREDENTIALS_DIRECTORY}/shadow' '${CREDENTIALS_DIRECTORY}/dog' \
+    >/tmp/ts54-concat
+(cat /etc/passwd /etc/shadow && echo -n wuff) | cmp /tmp/ts54-concat
 rm /tmp/ts54-concat
 
 # Test that SetCredential= acts as fallback for LoadCredential=
@@ -71,20 +219,20 @@ if [ "$expected_credential" != "" ] ; then
     systemd-run -p AssertCredential="$expected_credential" -p Type=oneshot true
 
     # And this should fail
-    systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true && { echo 'unexpected success'; exit 1; }
+    (! systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true)
 fi
 
 # Verify that the creds are immutable
-systemd-run -p LoadCredential=passwd:/etc/passwd \
-            -p DynamicUser=1 \
-            --wait \
-            touch '${CREDENTIALS_DIRECTORY}/passwd' \
-    && { echo 'unexpected success'; exit 1; }
-systemd-run -p LoadCredential=passwd:/etc/passwd \
-            -p DynamicUser=1 \
-            --wait \
-            rm '${CREDENTIALS_DIRECTORY}/passwd' \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run -p LoadCredential=passwd:/etc/passwd \
+    -p DynamicUser=1 \
+    --unit=test-54-immutable-touch.service \
+    --wait \
+    touch '${CREDENTIALS_DIRECTORY}/passwd')
+(! systemd-run -p LoadCredential=passwd:/etc/passwd \
+    -p DynamicUser=1 \
+    --unit=test-54-immutable-rm.service \
+    --wait \
+    rm '${CREDENTIALS_DIRECTORY}/passwd')
 
 # Check directory-based loading
 mkdir -p /tmp/ts54-creds/sub
@@ -93,14 +241,15 @@ echo -n b >/tmp/ts54-creds/bar
 echo -n c >/tmp/ts54-creds/baz
 echo -n d >/tmp/ts54-creds/sub/qux
 systemd-run -p LoadCredential=cred:/tmp/ts54-creds \
-            -p DynamicUser=1 \
-            --wait \
-            --pipe \
-            cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
-                '${CREDENTIALS_DIRECTORY}/cred_bar' \
-                '${CREDENTIALS_DIRECTORY}/cred_baz' \
-                '${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat
-( echo -n abcd ) | cmp /tmp/ts54-concat
+    -p DynamicUser=1 \
+    --unit=test-54-dir.service \
+    --wait \
+    --pipe \
+    cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
+        '${CREDENTIALS_DIRECTORY}/cred_bar' \
+        '${CREDENTIALS_DIRECTORY}/cred_baz' \
+        '${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat
+cmp /tmp/ts54-concat <(echo -n abcd)
 rm /tmp/ts54-concat
 rm -rf /tmp/ts54-creds
 
@@ -111,22 +260,31 @@ if systemctl --version | grep -q -- +OPENSSL ; then
     systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
 
     systemd-run -p LoadCredentialEncrypted=test-54:/tmp/test-54-ciphertext \
-                --wait \
-                --pipe \
-                cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
+        --wait \
+        --pipe \
+        cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
 
     echo -n $RANDOM >/tmp/test-54-plaintext
     systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
     systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext
 
     systemd-run -p SetCredentialEncrypted=test-54:"$(cat /tmp/test-54-ciphertext)" \
-                --wait \
-                --pipe \
-                cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
+        --wait \
+        --pipe \
+        cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext
 
     rm /tmp/test-54-plaintext /tmp/test-54-ciphertext
 fi
 
+# https://github.com/systemd/systemd/issues/27275
+systemd-run -p DynamicUser=yes -p 'LoadCredential=os:/etc/os-release' \
+            -p 'ExecStartPre=true' \
+            -p 'ExecStartPre=systemd-creds cat os' \
+            --unit=test-54-exec-start.service \
+            --wait \
+            --pipe \
+            true | cmp /etc/os-release
+
 systemd-analyze log-level info
 
 echo OK >/testok
index ffdd35287253be6affd105c9a00e90d3ddf0b3bc..f81c6ddf1f76a93344e84a46f2a0709a0b522dec 100755 (executable)
@@ -35,15 +35,13 @@ systemd-run --wait --unit=two -p Type=notify -p ExitType=cgroup \
     /tmp/test56-exit-cgroup.sh 'systemctl stop two'
 
 # false exec condition: systemd-run should exit immediately with status code: 1
-systemd-run --wait --unit=three -p Type=notify -p ExitType=cgroup \
+(! systemd-run --wait --unit=three -p Type=notify -p ExitType=cgroup \
     -p ExecCondition=false \
-    /tmp/test56-exit-cgroup.sh \
-    && { echo 'unexpected success'; exit 1; }
+    /tmp/test56-exit-cgroup.sh)
 
 # service should exit uncleanly (main process exits with SIGKILL)
-systemd-run --wait --unit=four -p Type=notify -p ExitType=cgroup \
-    /tmp/test56-exit-cgroup.sh 'systemctl kill --signal 9 four' \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-run --wait --unit=four -p Type=notify -p ExitType=cgroup \
+    /tmp/test56-exit-cgroup.sh 'systemctl kill --signal 9 four')
 
 
 # Multiple level process tree, parent process exits quickly
diff --git a/test/units/testsuite-57-retry-fail.service b/test/units/testsuite-57-retry-fail.service
new file mode 100644 (file)
index 0000000..67f3407
--- /dev/null
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Failed Dependency Unit
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/sh -c "if [ -f /tmp/testsuite-57-retry-fail ]; then exit 0; else exit 1; fi"
+Restart=no
diff --git a/test/units/testsuite-57-retry-upheld.service b/test/units/testsuite-57-retry-upheld.service
new file mode 100644 (file)
index 0000000..2f718a6
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upheld Unit
+Requires=testsuite-57-retry-fail.service
+After=testsuite-57-retry-fail.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/echo ok
diff --git a/test/units/testsuite-57-retry-uphold.service b/test/units/testsuite-57-retry-uphold.service
new file mode 100644 (file)
index 0000000..a01b131
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Upholding Unit
+Upholds=testsuite-57-retry-upheld.service
+
+[Service]
+ExecStart=/bin/sleep infinity
index 115b0d56f66c53acd45ea91973e3fb02f5c5ad40..e596392204ef2a562a358c7646024b6483e3c87d 100755 (executable)
@@ -4,7 +4,7 @@ set -ex
 
 if [ -f /tmp/testsuite-57.counter ] ; then
     read -r counter < /tmp/testsuite-57.counter
-    counter=$(("$counter" + 1))
+    counter=$((counter + 1))
 else
     counter=0
 fi
index 66d946bebc5d0c9e831fd4b8e43afb4bbbca82ad..24040c31892f9fc38afdd71c78766db5bc1ecadd 100755 (executable)
@@ -26,6 +26,33 @@ done
 
 systemctl stop testsuite-57-uphold.service
 
+# Idea is this:
+#    1. we start testsuite-57-retry-uphold.service
+#    2. which through Uphold= starts testsuite-57-retry-upheld.service
+#    3. which through Requires= starts testsuite-57-retry-fail.service
+#    4. which fails as /tmp/testsuite-57-retry-fail does not exist, so testsuite-57-retry-upheld.service
+#       is no longer restarted
+#    5. we create /tmp/testsuite-57-retry-fail
+#    6. now testsuite-57-retry-upheld.service will be restarted since upheld, and its dependency will
+#       be satisfied
+
+rm -f /tmp/testsuite-57-retry-fail
+systemctl start testsuite-57-retry-uphold.service
+
+while ! systemctl is-failed testsuite-57-retry-fail.service ; do
+    sleep .5
+done
+
+systemctl is-active testsuite-57-retry-upheld.service && { echo 'unexpected success'; exit 1; }
+
+touch /tmp/testsuite-57-retry-fail
+
+while ! systemctl is-active testsuite-57-retry-upheld.service ; do
+    sleep .5
+done
+
+systemctl stop testsuite-57-retry-uphold.service testsuite-57-retry-fail.service testsuite-57-retry-upheld.service
+
 # Idea is this:
 #    1. we start testsuite-57-prop-stop-one.service
 #    2. which through Wants=/After= pulls in testsuite-57-prop-stop-two.service as well
index 20c2c5707366d48508805423d07985172eb14c2a..83d7757a13c35db224673dc98723e9b5cae8da11 100755 (executable)
@@ -6,8 +6,7 @@ set -o pipefail
 runas() {
     declare userid=$1
     shift
-    # shellcheck disable=SC2016
-    su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@"
+    XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@"
 }
 
 if ! command -v systemd-repart &>/dev/null; then
index db8dc27ab9d9faff2efd5b30c5c4c166c4cce565..aabebb2bb60ba65bd107d345fa8f1032ab26ed1b 100755 (executable)
@@ -116,13 +116,13 @@ LEAVE=0
 
 function reload() {
     systemd-notify --reloading --status="Adding 11 to exit status"
-    EXIT_STATUS=\$((\$EXIT_STATUS + 11))
+    EXIT_STATUS=\$((EXIT_STATUS + 11))
     systemd-notify --ready --status="Back running"
 }
 
 function leave() {
     systemd-notify --stopping --status="Adding 7 to exit status"
-    EXIT_STATUS=\$((\$EXIT_STATUS + 7))
+    EXIT_STATUS=\$((EXIT_STATUS + 7))
     LEAVE=1
     return 0
 }
@@ -138,7 +138,7 @@ while [ \$LEAVE = 0 ] ; do
 done
 
 systemd-notify --status="Adding 3 to exit status"
-EXIT_STATUS=\$((\$EXIT_STATUS + 3))
+EXIT_STATUS=\$((EXIT_STATUS + 3))
 exit \$EXIT_STATUS
 EOF
 
index 1b45ba21fbad72485c134b398512b7f37146a9d0..5795d6bbeb72287658070a91a9f9a8e39c091f08 100755 (executable)
@@ -181,7 +181,7 @@ EOF
     # Trigger the mount ratelimiting
     cd "$(mktemp -d)"
     mkdir foo
-    for ((i = 0; i < 50; i++)); do
+    for _ in {1..50}; do
         mount --bind foo foo
         umount foo
     done
@@ -225,7 +225,7 @@ EOF
     # shellcheck disable=SC2064
     trap "rm -f /run/systemd/system/tmp-hoge.mount '$mount_mytmpfs'" RETURN
 
-    for ((i = 0; i < 10; i++)); do
+    for _ in {1..10}; do
         systemctl --no-block start tmp-hoge.mount
         sleep ".$RANDOM"
         systemctl daemon-reexec
index fbab282334e38bb28b98228fdf8906cbb605ba2f..ad502a229c108eaee7f1bb2b620f3e0f03ccec66 100755 (executable)
@@ -158,11 +158,11 @@ helper_check_device_units() {(
 
     local i
 
-    for (( i = 0; i < 20; i++ )); do
+    for i in {1..20}; do
+        (( i > 1 )) && sleep 0.5
         if check_device_units 0 "$@"; then
             return 0
         fi
-        sleep .5
     done
 
     check_device_units 1 "$@"
@@ -174,14 +174,62 @@ 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() {
     local expected_symlinks=(
         # Controller(s)
         /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef
+        /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_16
+        /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_17
         # Shared namespaces
         /dev/disk/by-path/pci-*-nvme-16
         /dev/disk/by-path/pci-*-nvme-17
index edaf667107156686c7036b4422224f2793b44ed0..2ad7b9a4394983dc1b2a47f74361928f9a4565d1 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 || :
@@ -148,6 +150,11 @@ systemd-analyze cat-config /etc/systemd/system.conf >/dev/null
 systemd-analyze cat-config systemd/system.conf systemd/journald.conf >/dev/null
 systemd-analyze cat-config systemd/system.conf foo/bar systemd/journald.conf >/dev/null
 systemd-analyze cat-config foo/bar
+# security
+systemd-analyze security
+systemd-analyze security --json=off
+systemd-analyze security --json=pretty | jq
+systemd-analyze security --json=short | jq
 
 if [[ ! -v ASAN_OPTIONS ]]; then
     # check that systemd-analyze cat-config paths work in a chroot
@@ -172,16 +179,13 @@ EOF
 
 set +e
 # Default behaviour is to recurse through all dependencies when unit is loaded
-systemd-analyze verify --root=/tmp/img/ testfile.service \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --root=/tmp/img/ testfile.service)
 
 # As above, recurses through all dependencies when unit is loaded
-systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service)
 
 # Recurses through unit file and its direct dependencies when unit is loaded
-systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service)
 
 set -e
 
@@ -211,8 +215,7 @@ systemd-analyze verify --recursive-errors=no /tmp/testfile2.service
 
 set +e
 # Non-zero exit status since all associated dependencies are recursively loaded when the unit file is loaded
-systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service)
 set -e
 
 rm /tmp/testfile.service
@@ -234,19 +237,15 @@ rm /tmp/.testfile.service
 # Alias a unit file's name on disk (see #20061)
 cp /tmp/testfile.service /tmp/testsrvc
 
-systemd-analyze verify /tmp/testsrvc \
-    && { echo 'unexpected success'; exit 1; }
+(! systemd-analyze verify /tmp/testsrvc)
 
 systemd-analyze verify /tmp/testsrvc:alias.service
 
 # Zero exit status since the value used for comparison determine exposure to security threats is by default 100
 systemd-analyze security --offline=true /tmp/testfile.service
 
-set +e
 #The overall exposure level assigned to the unit is greater than the set threshold
-systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service \
-    && { echo 'unexpected success'; exit 1; }
-set -e
+(! systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service)
 
 # Ensure we print the list of ACLs, see https://github.com/systemd/systemd/issues/23185
 systemd-analyze security --offline=true /tmp/testfile.service | grep -q -F "/dev/sda"
@@ -737,19 +736,15 @@ systemd-analyze security --threshold=25 --offline=true \
                            --profile=strict \
                            --root=/tmp/img/ testfile.service
 
-set +e
 # The trusted profile doesn't add any sanboxing options
-systemd-analyze security --threshold=25 --offline=true \
+(! systemd-analyze security --threshold=25 --offline=true \
                            --security-policy=/tmp/testfile.json \
                            --profile=/usr/lib/systemd/portable/profile/trusted/service.conf \
-                           --root=/tmp/img/ testfile.service \
-    && { echo 'unexpected success'; exit 1; }
+                           --root=/tmp/img/ testfile.service)
 
-systemd-analyze security --threshold=50 --offline=true \
+(! systemd-analyze security --threshold=50 --offline=true \
                            --security-policy=/tmp/testfile.json \
-                           --root=/tmp/img/ testfile.service \
-    && { echo 'unexpected success'; exit 1; }
-set -e
+                           --root=/tmp/img/ testfile.service)
 
 rm /tmp/img/usr/lib/systemd/system/testfile.service
 
@@ -816,6 +811,18 @@ name=$(echo "$output" | awk '{ print $4 }')
 check deny yes /run/systemd/transient/"$name"
 check deny no "$name"
 
+# Let's also test the "image-policy" verb
+
+systemd-analyze image-policy '*' 2>&1 | grep -q -F "Long form: =verity+signed+encrypted+unprotected+unused+absent"
+systemd-analyze image-policy '-' 2>&1 | grep -q -F "Long form: =unused+absent"
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -F "Long form: usr=verity:home=encrypted:=unused+absent"
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^home \+encrypted \+'
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr \+verity \+'
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^root \+ignore \+'
+systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr-verity \+unprotected \+'
+
+(! systemd-analyze image-policy 'doedel')
+
 systemd-analyze log-level info
 
 echo OK >/testok
index a8d3d2422b9aef522ca36ee974654f1e7cd2736d..16a1edfd9cb3060493c1cc4887a40bb865bccaa8 100755 (executable)
@@ -15,42 +15,42 @@ cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-uran
 systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto $img
 
 # Enroll unlock with default PCR policy
-env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img
 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
 /usr/lib/systemd/systemd-cryptsetup detach test-volume
 
 # Check with wrong PCR
 tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
-/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
+(! /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
 
 # Enroll unlock with PCR+PIN policy
 systemd-cryptenroll --wipe-slot=tpm2 $img
-env PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img
-env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
+PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img
+PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
 /usr/lib/systemd/systemd-cryptsetup detach test-volume
 
 # Check failure with wrong PIN
-env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
+(! PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
 
 # Check LUKS2 token plugin unlock (i.e. without specifying tpm2-device=auto)
 if cryptsetup --help | grep -q 'LUKS2 external token plugin support is compiled-in' && \
          [ -f "$(cryptsetup --help | sed -n -r 's/.*LUKS2 external token plugin path: (.*)\./\1/p')/libcryptsetup-token-systemd-tpm2.so" ]; then
-    env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1
+    PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1
     /usr/lib/systemd/systemd-cryptsetup detach test-volume
 
     # Check failure with wrong PIN
-    env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1 && { echo 'unexpected success'; exit 1; }
+    (! PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - headless=1)
 else
     echo 'cryptsetup has no LUKS2 token plugin support, skipping'
 fi
 
 # Check failure with wrong PCR (and correct PIN)
 tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000
-env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; }
+(! PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1)
 
 # Enroll unlock with PCR 0+7
 systemd-cryptenroll --wipe-slot=tpm2 $img
-env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img
 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1
 /usr/lib/systemd/systemd-cryptsetup detach test-volume
 
@@ -119,7 +119,7 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
 
     # Invalidate PCR, decrypting should fail now
     tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
-    systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" >/dev/null && { echo 'unexpected success'; exit 1; }
+    (! systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" >/dev/null)
 
     # Sign new PCR state, decrypting should work now.
     /usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: >"/tmp/pcrsign.sig2"
@@ -146,8 +146,8 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
 
     # After extending the PCR things should fail
     tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
-    SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
-    SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
+    (! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1)
+    (! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1)
 
     # But once we sign the current PCRs, we should be able to unlock again
     /usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: >"/tmp/pcrsign.sig3"
@@ -162,7 +162,7 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
 
     # Sign one more phase, this should
     /usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig4" >"/tmp/pcrsign.sig5"
-    ( ! cmp "/tmp/pcrsign.sig4" "/tmp/pcrsign.sig5" )
+    (! cmp "/tmp/pcrsign.sig4" "/tmp/pcrsign.sig5")
 
     # Should still be good to unlock, given the old entry still exists
     SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig5",headless=1
@@ -217,6 +217,73 @@ systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:
 systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata
 rm /tmp/testdata
 
+# negative tests for cryptenroll
+
+# Prepare a new disk image
+img_2="/var/tmp/file_enroll.txt"
+truncate -s 20M $img_2
+echo -n password >/tmp/password
+cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img_2 /tmp/password
+
+#boolean_arguments
+(! systemd-cryptenroll --fido2-with-client-pin=false)
+
+(! systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo)
+
+(! systemd-cryptenroll --fido2-with-client-pin=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-client-pin=false $img_2
+
+(! systemd-cryptenroll --fido2-with-user-presence=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-user-presence=false $img_2
+
+(! systemd-cryptenroll --fido2-with-user-verification=1234 $img_2)
+
+(! systemd-cryptenroll --tpm2-with-pin=1234 $img_2)
+
+systemd-cryptenroll --fido2-with-user-verification=false $img_2
+
+#arg_enroll_type
+(! systemd-cryptenroll --recovery-key --password $img_2)
+
+(! systemd-cryptenroll --password --recovery-key $img_2)
+
+(! systemd-cryptenroll --password --fido2-device=auto $img_2)
+
+(! systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2)
+
+(! systemd-cryptenroll --password --tpm2-device=auto $img_2)
+
+#arg_unlock_type
+(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2)
+
+(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2)
+
+#fido2_cred_algorithm
+(! systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2)
+
+#tpm2_errors
+(! systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2)
+
+(! systemd-cryptenroll --tpm2-pcrs=key $img_2)
+
+(! systemd-cryptenroll --tpm2-pcrs=44+8 $img_2)
+
+systemd-cryptenroll --tpm2-pcrs=8 $img_2
+
+(! systemd-cryptenroll --tpm2-pcrs=hello $img_2)
+
+systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config $img_2
+
+#wipe_slots
+(! systemd-cryptenroll --wipe-slot $img_2)
+
+(! systemd-cryptenroll --wipe-slot=10240000 $img_2)
+
+#fido2_multiple_auto
+(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2)
+
 echo OK >/testok
 
 exit 0
index 4f33cab8482feb9be9aa04cd501b8fe195807cb6..7be400d43e249f46e88de6dc63b361fb43e9bd34 100755 (executable)
@@ -206,8 +206,8 @@ restore_keymap() {
 
 wait_vconsole_setup() {
     local i ss
-    for ((i = 0; i < 20; i++)); do
-        if (( i != 0 )); then sleep .5; fi
+    for i in {1..20}; do
+        (( i > 1 )) && sleep 0.5
         ss="$(systemctl --property SubState --value show systemd-vconsole-setup.service)"
         if [[ "$ss" == "exited" || "$ss" == "dead" || "$ss" == "condition" ]]; then
             return 0
@@ -552,13 +552,115 @@ test_convert() {
     assert_not_in "X11 Options:"   "$output"
 }
 
+test_validate() {
+    if [[ -z "$(localectl list-keymaps)" ]]; then
+        echo "No vconsole keymap installed, skipping test."
+        return
+    fi
+
+    if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then
+        echo "No x11 keymap installed, skipping test."
+        return
+    fi
+
+    backup_keymap
+    trap restore_keymap RETURN
+
+    # clear previous settings
+    systemctl stop systemd-localed.service
+    wait_vconsole_setup
+    rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+    # create invalid configs
+    cat >/etc/vconsole.conf <<EOF
+KEYMAP=foobar
+XKBLAYOUT=hogehoge
+EOF
+
+    # confirm that the invalid settings are not shown
+    output=$(localectl)
+    assert_in "VC Keymap: .unset."  "$output"
+    if [[ "$output" =~ "X11 Layout: hogehoge" ]]; then
+        # Debian/Ubuntu build systemd without xkbcommon.
+        echo "systemd built without xkbcommon, skipping test."
+        return
+    fi
+    assert_in "X11 Layout: .unset." "$output"
+
+    # only update the virtual console keymap
+    assert_rc 0 localectl --no-convert set-keymap us
+
+    output=$(localectl)
+    assert_in "VC Keymap: us"       "$output"
+    assert_in "X11 Layout: .unset." "$output"
+
+    output=$(cat /etc/vconsole.conf)
+    assert_in     "KEYMAP=us"  "$output"
+    assert_not_in "XKBLAYOUT=" "$output"
+
+    # clear previous settings
+    systemctl stop systemd-localed.service
+    wait_vconsole_setup
+    rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+    # create invalid configs
+    cat >/etc/vconsole.conf <<EOF
+KEYMAP=foobar
+XKBLAYOUT=hogehoge
+EOF
+
+    # confirm that the invalid settings are not shown
+    output=$(localectl)
+    assert_in "VC Keymap: .unset."  "$output"
+    assert_in "X11 Layout: .unset." "$output"
+
+    # only update the X11 keyboard layout
+    assert_rc 0 localectl --no-convert set-x11-keymap us
+
+    output=$(localectl)
+    assert_in "VC Keymap: .unset."  "$output"
+    assert_in "X11 Layout: us"      "$output"
+
+    output=$(cat /etc/vconsole.conf)
+    assert_not_in "KEYMAP="      "$output"
+    assert_in     "XKBLAYOUT=us" "$output"
+
+    # clear previous settings
+    systemctl stop systemd-localed.service
+    wait_vconsole_setup
+    rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard
+
+    # create invalid configs
+    cat >/etc/vconsole.conf <<EOF
+KEYMAP=foobar
+XKBLAYOUT=hogehoge
+EOF
+
+    # update the virtual console keymap with conversion
+    assert_rc 0 localectl set-keymap us
+
+    output=$(localectl)
+    assert_in "VC Keymap: us"  "$output"
+    assert_in "X11 Layout: us" "$output"
+
+    output=$(cat /etc/vconsole.conf)
+    assert_in "KEYMAP=us"    "$output"
+    assert_in "XKBLAYOUT=us" "$output"
+}
+
 : >/failed
 
+# Make sure the content of kbd-model-map is the one that the tests expect
+# regardless of the version installed on the distro where the testsuite is
+# running on.
+export SYSTEMD_KBD_MODEL_MAP=/usr/lib/systemd/tests/testdata/test-keymap-util/kbd-model-map
+
 enable_debug
 test_locale
 test_vc_keymap
 test_x11_keymap
 test_convert
+test_validate
 
 touch /testok
 rm /failed
diff --git a/test/units/testsuite-74.coredump.sh b/test/units/testsuite-74.coredump.sh
new file mode 100755 (executable)
index 0000000..3910abe
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Make sure the binary name fits into 15 characters
+CORE_TEST_BIN="/tmp/test-dump"
+CORE_TEST_UNPRIV_BIN="/tmp/test-usr-dump"
+MAKE_DUMP_SCRIPT="/tmp/make-dump"
+# Unset $PAGER so we don't have to use --no-pager everywhere
+export PAGER=
+
+at_exit() {
+    rm -fv -- "$CORE_TEST_BIN" "$CORE_TEST_UNPRIV_BIN" "$MAKE_DUMP_SCRIPT"
+}
+
+trap at_exit EXIT
+
+if systemd-detect-virt -cq; then
+    echo "Running in a container, skipping the systemd-coredump test..."
+    exit 0
+fi
+
+# Check that we're the ones to receive coredumps
+sysctl kernel.core_pattern | grep systemd-coredump
+
+# Prepare "fake" binaries for coredumps, so we can properly exercise
+# the matching stuff too
+cp -vf /bin/sleep "${CORE_TEST_BIN:?}"
+cp -vf /bin/sleep "${CORE_TEST_UNPRIV_BIN:?}"
+# Simple script that spawns given "fake" binary and then kills it with
+# given signal
+cat >"${MAKE_DUMP_SCRIPT:?}" <<\EOF
+#!/bin/bash -ex
+
+bin="${1:?}"
+sig="${2:?}"
+
+ulimit -c unlimited
+"$bin" infinity &
+pid=$!
+sleep 1
+kill -s "$sig" "$pid"
+# This should always fail
+! wait "$pid"
+EOF
+chmod +x "$MAKE_DUMP_SCRIPT"
+
+# Privileged stuff
+[[ "$(id -u)" -eq 0 ]]
+# Trigger a couple of coredumps
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP"
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT"
+# In the tests we store the coredumps in journals, so let's generate a couple
+# with Storage=external as well
+mkdir -p /run/systemd/coredump.conf.d/
+printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP"
+"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT"
+rm -fv /run/systemd/coredump.conf.d/99-external.conf
+# Wait a bit for the coredumps to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_BIN | wc -l) -lt 4 ]]; do sleep 1; done"
+
+coredumpctl
+SYSTEMD_LOG_LEVEL=debug coredumpctl
+coredumpctl --help
+coredumpctl --version
+coredumpctl --no-pager --no-legend
+coredumpctl --all
+coredumpctl -1
+coredumpctl -n 1
+coredumpctl --reverse
+coredumpctl -F COREDUMP_EXE
+coredumpctl --json=short | jq
+coredumpctl --json=pretty | jq
+coredumpctl --json=off
+coredumpctl --root=/
+coredumpctl --directory=/var/log/journal
+coredumpctl --file="/var/log/journal/$(</etc/machine-id)/system.journal"
+coredumpctl --since=@0
+coredumpctl --since=yesterday --until=tomorrow
+# We should have a couple of externally stored coredumps
+coredumpctl --field=COREDUMP_FILENAME | tee /tmp/coredumpctl.out
+grep "/var/lib/systemd/coredump/core" /tmp/coredumpctl.out
+rm -f /tmp/coredumpctl.out
+
+coredumpctl info
+coredumpctl info "$CORE_TEST_BIN"
+coredumpctl info /foo /bar/ /baz "$CORE_TEST_BIN"
+coredumpctl info "${CORE_TEST_BIN##*/}"
+coredumpctl info foo bar baz "${CORE_TEST_BIN##*/}"
+coredumpctl info COREDUMP_EXE="$CORE_TEST_BIN"
+coredumpctl info COREDUMP_EXE=aaaaa COREDUMP_EXE= COREDUMP_EXE="$CORE_TEST_BIN"
+
+coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN"
+SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$CORE_TEST_BIN"
+coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_BIN##*/}"
+
+coredumpctl dump "$CORE_TEST_BIN" >/tmp/core.redirected
+test -s /tmp/core.redirected
+coredumpctl dump -o /tmp/core.output "${CORE_TEST_BIN##*/}"
+test -s /tmp/core.output
+rm -f /tmp/core.{output,redirected}
+
+# Unprivileged stuff
+# Related issue: https://github.com/systemd/systemd/issues/26912
+UNPRIV_CMD=(systemd-run --user --wait --pipe -M "testuser@.host" --)
+# Trigger a couple of coredumps as an unprivileged user
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP"
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT"
+# In the tests we store the coredumps in journals, so let's generate a couple
+# with Storage=external as well
+mkdir -p /run/systemd/coredump.conf.d/
+printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP"
+"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT"
+rm -fv /run/systemd/coredump.conf.d/99-external.conf
+# Wait a bit for the coredumps to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_UNPRIV_BIN | wc -l) -lt 4 ]]; do sleep 1; done"
+
+# root should see coredumps from both binaries
+coredumpctl info "$CORE_TEST_UNPRIV_BIN"
+coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}"
+# The test user should see only their own coredumps
+"${UNPRIV_CMD[@]}" coredumpctl
+"${UNPRIV_CMD[@]}" coredumpctl info "$CORE_TEST_UNPRIV_BIN"
+"${UNPRIV_CMD[@]}" coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}"
+(! "${UNPRIV_CMD[@]}" coredumpctl info --all "$CORE_TEST_BIN")
+(! "${UNPRIV_CMD[@]}" coredumpctl info --all "${CORE_TEST_BIN##*/}")
+# We should have a couple of externally stored coredumps
+"${UNPRIV_CMD[@]}" coredumpctl --field=COREDUMP_FILENAME | tee /tmp/coredumpctl.out
+grep "/var/lib/systemd/coredump/core" /tmp/coredumpctl.out
+rm -f /tmp/coredumpctl.out
+
+"${UNPRIV_CMD[@]}" coredumpctl debug --debugger=/bin/true "$CORE_TEST_UNPRIV_BIN"
+"${UNPRIV_CMD[@]}" coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_UNPRIV_BIN##*/}"
+
+"${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_UNPRIV_BIN" >/tmp/core.redirected
+test -s /tmp/core.redirected
+"${UNPRIV_CMD[@]}" coredumpctl dump -o /tmp/core.output "${CORE_TEST_UNPRIV_BIN##*/}"
+test -s /tmp/core.output
+rm -f /tmp/core.{output,redirected}
+(! "${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_BIN" >/dev/null)
+
+# --backtrace mode
+# Pass one of the existing journal coredump records to systemd-coredump and
+# use our PID as the source to make matching the coredump later easier
+# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT HOSTNAME
+journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
+    /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509994 12345 mymachine
+# Wait a bit for the coredump to get processed
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -eq 0 ]]; do sleep 1; done"
+coredumpctl info "$$"
+coredumpctl info COREDUMP_HOSTNAME="mymachine"
+
+
+(! coredumpctl --hello-world)
+(! coredumpctl -n 0)
+(! coredumpctl -n -1)
+(! coredumpctl --file=/dev/null)
+(! coredumpctl --since=0)
+(! coredumpctl --until='')
+(! coredumpctl --since=today --until=yesterday)
+(! coredumpctl --directory=/ --root=/)
+(! coredumpctl --json=foo)
+(! coredumpctl -F foo -F bar)
+(! coredumpctl list 0)
+(! coredumpctl list -- -1)
+(! coredumpctl list '')
+(! coredumpctl info /../.~=)
+(! coredumpctl info '')
+(! coredumpctl dump --output=/dev/full "$CORE_TEST_BIN")
+(! coredumpctl dump --output=/dev/null --output=/dev/null "$CORE_TEST_BIN")
+(! coredumpctl debug --debugger=/bin/false)
+(! coredumpctl debug --debugger=/bin/true --debugger-arguments='"')
index 92a607501b7a1f9c9165e66d7f71319f4fd6aeb1..be08575c9e69d49b34bc646ffe96699bc488292e 100755 (executable)
@@ -119,6 +119,15 @@ grep -q "^root:x:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd"
 grep -q "^root:$ROOT_HASHED_PASSWORD2:" "$ROOT/etc/shadow"
 grep -q "hello.world=0" "$ROOT/etc/kernel/cmdline"
 
+# Test that --reset removes all files configured by firstboot.
+systemd-firstboot --root="$ROOT" --reset
+[[ ! -e "$ROOT/etc/locale.conf" ]]
+[[ ! -e "$ROOT/etc/vconsole.conf" ]]
+[[ ! -e "$ROOT/etc/localtime" ]]
+[[ ! -e "$ROOT/etc/hostname" ]]
+[[ ! -e "$ROOT/etc/machine-id" ]]
+[[ ! -e "$ROOT/etc/kernel/cmdline" ]]
+
 # --copy-* options
 rm -fr "$ROOT"
 mkdir "$ROOT"
index 4adb1cd6373154df50b5baa6b1bd0f1429eddc13..3d8d07c87da9fcfc188fb619819e277eab78a942 100755 (executable)
@@ -7,14 +7,10 @@ set -o pipefail
 # shellcheck source=test/units/assert.sh
 . "$(dirname "$0")"/assert.sh
 
-: >/failed
-
 at_exit() {
     if [[ -v NSPAWN_NAME && -e "/var/lib/machines/$NSPAWN_NAME" ]]; then
         rm -fvr "/var/lib/machines/$NSPAWN_NAME" "/etc/systemd/nspawn/$NSPAWN_NAME" "new"
     fi
-
-    return 0
 }
 
 trap at_exit EXIT
@@ -38,6 +34,3 @@ script -ec 'machinectl cat "$PWD/new"' /dev/null
 
 EDITOR='mv new' script -ec 'machinectl edit "$NSPAWN_NAME"' /dev/null
 printf '%s\n' '[Exec]' 'Boot=false' | cmp - "/etc/systemd/nspawn/$NSPAWN_NAME"
-
-touch /testok
-rm /failed
diff --git a/test/units/testsuite-74.modules-load.sh b/test/units/testsuite-74.modules-load.sh
new file mode 100755 (executable)
index 0000000..3d00e07
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+MODULES_LOAD_BIN="/usr/lib/systemd/systemd-modules-load"
+CONFIG_FILE="/run/modules-load.d/99-test.conf"
+
+at_exit() {
+    rm -rfv "${CONFIG_FILE:?}"
+}
+
+trap at_exit EXIT
+
+if systemd-detect-virt -cq; then
+    echo "Running in a container, skipping the systemd-modules-load test..."
+    exit 0
+fi
+
+# Check if we have required kernel modules
+modprobe --all --resolve-alias loop dummy
+
+mkdir -p /run/modules-load.d/
+
+"$MODULES_LOAD_BIN"
+"$MODULES_LOAD_BIN" --help
+"$MODULES_LOAD_BIN" --version
+
+# Explicit config file
+modprobe -v --all --remove loop dummy
+printf "loop\ndummy" >"$CONFIG_FILE"
+"$MODULES_LOAD_BIN" "$CONFIG_FILE" |& tee /tmp/out.log
+grep -E "Inserted module .*loop" /tmp/out.log
+grep -E "Inserted module .*dummy" /tmp/out.log
+
+# Implicit config file
+modprobe -v --all --remove loop dummy
+printf "loop\ndummy" >"$CONFIG_FILE"
+"$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "Inserted module .*loop" /tmp/out.log
+grep -E "Inserted module .*dummy" /tmp/out.log
+
+# Valid & invalid data mixed together
+modprobe -v --all --remove loop dummy
+cat >"$CONFIG_FILE" <<EOF
+
+loop
+loop
+loop
+    loop
+dummy
+    \\n\n\n\\\\\\
+loo!@@123##2455
+# This is a comment
+$(printf "%.0sx" {0..4096})
+dummy
+loop
+foo-bar-baz
+1
+"
+'
+EOF
+"$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "^Inserted module .*loop" /tmp/out.log
+grep -E "^Inserted module .*dummy" /tmp/out.log
+grep -E "^Failed to find module .*foo-bar-baz" /tmp/out.log
+(! grep -E "This is a comment" /tmp/out.log)
+# Each module should be loaded only once, even if specified multiple times
+[[ "$(grep -Ec "^Inserted module" /tmp/out.log)" -eq 2 ]]
+[[ "$(grep -Ec "^Failed to find module" /tmp/out.log)" -eq 7 ]]
+
+# Command line arguments
+modprobe -v --all --remove loop dummy
+# Make sure we have no config files left over that might interfere with
+# following tests
+rm -fv "$CONFIG_FILE"
+[[ -z "$(systemd-analyze cat-config modules-load.d)" ]]
+CMDLINE="ro root= modules_load= modules_load=, / = modules_load=foo-bar-baz,dummy modules_load=loop,loop,loop"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" "$MODULES_LOAD_BIN" |& tee /tmp/out.log
+grep -E "^Inserted module .*loop" /tmp/out.log
+grep -E "^Inserted module .*dummy" /tmp/out.log
+grep -E "^Failed to find module .*foo-bar-baz" /tmp/out.log
+# Each module should be loaded only once, even if specified multiple times
+[[ "$(grep -Ec "^Inserted module" /tmp/out.log)" -eq 2 ]]
+
+(! "$MODULES_LOAD_BIN" --nope)
+(! "$MODULES_LOAD_BIN" /foo/bar/baz)
index 4890911fd00f3d6b008a1944357de4dddb7cdf32..bfe472bef7a82d047743b495c4d989b9fcc196b4 100755 (executable)
@@ -114,7 +114,7 @@ systemctl status "$(systemd-escape --path "$WORK_DIR/mnt").automount"
 losetup -d "$LOOP"
 unset LOOP
 # The automount unit should disappear once the underlying blockdev is gone
-timeout 10s bash -c "while systemctl status '$(systemd-escape --path "$WORK_DIR/mnt").automount)'; do sleep .2; done"
+timeout 10s bash -c "while systemctl status '$(systemd-escape --path "$WORK_DIR/mnt".automount)'; do sleep .2; done"
 
 # Mount a disk image
 systemd-mount --discover "$WORK_DIR/simple.img"
@@ -122,7 +122,7 @@ systemd-mount --discover "$WORK_DIR/simple.img"
 test -e /run/media/system/simple.img/foo.bar
 # systemd-mount --list and systemd-umount require the loopback block device is initialized by udevd.
 udevadm settle --timeout 30
-assert_in "/dev/loop.* ext4 sd-mount-test" "$(systemd-mount --list --full)"
+assert_in "/dev/loop.* ext4 +sd-mount-test" "$(systemd-mount --list --full)"
 systemd-umount "$WORK_DIR/simple.img"
 
 # --owner + vfat
index 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
diff --git a/test/units/testsuite-80.service b/test/units/testsuite-80.service
new file mode 100644 (file)
index 0000000..82b08a1
--- /dev/null
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=TEST-80-NOTIFYACCESS
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
+StandardOutput=journal+console
+StandardError=journal+console
diff --git a/test/units/testsuite-80.sh b/test/units/testsuite-80.sh
new file mode 100755 (executable)
index 0000000..7440b24
--- /dev/null
@@ -0,0 +1,131 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+: >/failed
+
+mkfifo /tmp/syncfifo1 /tmp/syncfifo2
+
+sync_in() {
+    read -r x < /tmp/syncfifo1
+    test "$x" = "$1"
+}
+
+sync_out() {
+    echo "$1" > /tmp/syncfifo2
+}
+
+export SYSTEMD_LOG_LEVEL=debug
+
+systemctl --no-block start notify.service
+
+sync_in a
+
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts"
+
+sync_out b
+sync_in c
+
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "main"
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unprivileged process"
+assert_rc 3 systemctl --quiet is-active notify.service
+
+sync_out d
+sync_in e
+
+systemctl --quiet is-active notify.service
+assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK"
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none"
+
+systemctl stop notify.service
+assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
+
+rm /tmp/syncfifo1 /tmp/syncfifo2
+
+# Now test basic fdstore behaviour
+
+MYSCRIPT="/tmp/myscript$RANDOM.sh"
+cat >> "$MYSCRIPT" <<'EOF'
+#!/usr/bin/env bash
+set -eux
+set -o pipefail
+test "$FDSTORE" -eq 7
+N="/tmp/$RANDOM"
+echo $RANDOM > "$N"
+systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N"
+rm "$N"
+systemd-notify --ready
+exec sleep infinity
+EOF
+
+chmod +x "$MYSCRIPT"
+
+MYUNIT="myunit$RANDOM.service"
+systemd-run -u "$MYUNIT" -p Type=notify -p StandardOutput=journal+console -p StandardError=journal+console -p FileDescriptorStoreMax=7 "$MYSCRIPT"
+
+test "$(systemd-analyze fdstore "$MYUNIT" | wc -l)" -eq 2
+systemd-analyze fdstore "$MYUNIT" --json=short
+systemd-analyze fdstore "$MYUNIT" --json=short | grep -P -q '\[{"fdname":"quux","type":.*,"devno":\[.*\],"inode":.*,"rdevno":null,"path":"/tmp/.*","flags":"ro"}\]'
+
+systemctl stop "$MYUNIT"
+rm "$MYSCRIPT"
+
+systemd-analyze log-level debug
+
+# Test fdstore pinning (this will pull in fdstore-pin.service fdstore-nopin.service)
+systemctl start fdstore-pin.target
+
+assert_eq "$(systemctl show fdstore-pin.service -P FileDescriptorStorePreserve)" yes
+assert_eq "$(systemctl show fdstore-nopin.service -P FileDescriptorStorePreserve)" restart
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
+
+# The file descriptor store should survive service restarts
+systemctl restart fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
+
+# It should not survive the service stop plus a later start (unless pinned)
+systemctl stop fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
+
+systemctl start fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running
+
+systemctl stop fdstore-pin.service fdstore-nopin.service
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
+
+systemctl clean fdstore-pin.service --what=fdstore
+
+assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0
+assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead
+assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead
+
+touch /testok
+rm /failed
+
+exit 0
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..fddf85a
--- /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/early/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/early/masked.service" /dev/null
+link_eq "$OUT_DIR/early/masked.socket" /dev/null
+link_endswith "$OUT_DIR/early/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/early/default.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/early/default.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+# Following stuff should be ignored, as it's prefixed with rd.
+test ! -h "$OUT_DIR/early/masked-initrd.service"
+test ! -h "$OUT_DIR/early/default.target.wants/wants-initrd.service"
+test ! -h "$OUT_DIR/early/default.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/early/initrd.target.wants"
+
+# Let's re-run the generator with systemd.debug_shell that should be honored
+: "debug-shell: regular + systemd.debug_shell"
+CMDLINE="$CMDLINE systemd.debug_shell"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/early/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Same thing, but with custom tty
+: "debug-shell: regular + systemd.debug_shell=/dev/tty666"
+CMDLINE="$CMDLINE systemd.debug_shell=/dev/tty666"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/early/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+grep -F "/dev/tty666" "$OUT_DIR/early/debug-shell.service.d/50-tty.conf"
+
+# Now override the default target via systemd.unit=
+: "debug-shell: regular + systemd.unit="
+CMDLINE="$CMDLINE systemd.unit=my-fancy.target"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/early/masked-no-suffix.service" /dev/null
+link_eq "$OUT_DIR/early/masked.service" /dev/null
+link_eq "$OUT_DIR/early/masked.socket" /dev/null
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount
+link_endswith "$OUT_DIR/early/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+test ! -d "$OUT_DIR/early/default.target.wants"
+
+
+# Initrd scenario
+: "debug-shell: initrd"
+CMDLINE="ro root=/ ${ARGS[*]} systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/early/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/early/initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+# The non-initrd stuff (i.e. without the rd. suffix) should be ignored in
+# this case
+test ! -h "$OUT_DIR/early/masked-no-suffix.service"
+test ! -h "$OUT_DIR/early/masked.service"
+test ! -h "$OUT_DIR/early/masked.socket"
+test ! -h "$OUT_DIR/early/initrd.target.wants/debug-shell.service"
+test ! -d "$OUT_DIR/early/default.target.wants"
+
+# Again, but with rd.systemd.debug_shell
+: "debug-shell: initrd + rd.systemd.debug_shell"
+CMDLINE="$CMDLINE rd.systemd.debug_shell"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/early/initrd.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service
+
+# Override the default target
+: "debug-shell: initrd + rd.systemd.unit"
+CMDLINE="$CMDLINE rd.systemd.unit=my-fancy-initrd.target"
+SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_eq "$OUT_DIR/early/masked-initrd.service" /dev/null
+link_endswith "$OUT_DIR/early/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service
+test ! -d "$OUT_DIR/early/initrd.target.wants"
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..d772f8d
--- /dev/null
@@ -0,0 +1,401 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235,SC2233
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-fstab-generator"
+NETWORK_FS_RX="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$"
+OUT_DIR="$(mktemp -d /tmp/fstab-generator.XXX)"
+FSTAB="$(mktemp)"
+
+at_exit() {
+    rm -fr "${OUT_DIR:?}" "${FSTAB:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+FSTAB_GENERAL=(
+    # Valid entries
+    "/dev/test2     /nofail                             ext4        nofail 0 0"
+    "/dev/test3     /regular                            btrfs       defaults 0 0"
+    "/dev/test4     /x-systemd.requires                 xfs         x-systemd.requires=foo.service 0 0"
+    "/dev/test5     /x-systemd.before-after             xfs         x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0"
+    "/dev/test6     /x-systemd.wanted-required-by       xfs         x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0"
+    "/dev/test7     /x-systemd.requires-mounts-for      xfs         x-systemd.requires-mounts-for=/foo/bar/baz 0 0"
+    "/dev/test8     /x-systemd.automount-idle-timeout   vfat        x-systemd.automount,x-systemd.idle-timeout=50s 0 0"
+    "/dev/test9     /x-systemd.makefs                   xfs         x-systemd.makefs 0 0"
+    "/dev/test10    /x-systemd.growfs                   xfs         x-systemd.growfs 0 0"
+    "/dev/test11    /_netdev                            ext4        defaults,_netdev 0 0"
+    "/dev/test12    /_rwonly                            ext4        x-systemd.rw-only 0 0"
+    "/dev/test13    /chaos1                             zfs         x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0"
+    "/dev/test14    /chaos2                             zfs         x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0"
+    "/dev/test15    /fstype/auto                        auto        defaults 0 0"
+    "/dev/test16    /fsck/me                            ext4        defaults 0 1"
+    "/dev/test17    /also/fsck/me                       ext4        defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99"
+    "/dev/test18    /swap                               swap        defaults 0 0"
+    "/dev/test19    /swap/makefs                        swap        defaults,x-systemd.makefs 0 0"
+    "/dev/test20    /var                                xfs         defaults,x-systemd.device-timeout=1h 0 0"
+    "/dev/test21    /usr                                ext4        defaults 0 1"
+    "/dev/test22    /initrd/mount                       ext2        defaults,x-systemd.rw-only,x-initrd.mount 0 1"
+    "/dev/test23    /initrd/mount/nofail                ext3        defaults,nofail,x-initrd.mount 0 1"
+    "/dev/test24    /initrd/mount/deps                  ext4        x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1"
+
+    # Incomplete, but valid entries
+    "/dev/incomplete1 /incomplete1"
+    "/dev/incomplete2 /incomplete2                      ext4"
+    "/dev/incomplete3 /incomplete3                      ext4        defaults"
+    "/dev/incomplete4 /incomplete4                      ext4        defaults 0"
+
+    # Remote filesystems
+    "/dev/remote1   /nfs                                nfs         bg 0 0"
+    "/dev/remote2   /nfs4                               nfs4        bg 0 0"
+    "bar.tld:/store /remote/storage                     nfs         ro,x-systemd.wanted-by=store.service 0 0"
+    "user@host.tld:/remote/dir /remote/top-secret       sshfs       rw,x-systemd.before=naughty.service 0 0"
+    "foo.tld:/hello /hello/world                        ceph        defaults 0 0"
+    "//192.168.0.1/storage /cifs-storage                cifs        automount,nofail 0 0"
+)
+
+FSTAB_GENERAL_ROOT=(
+    # rootfs with bunch of options we should ignore and fsck enabled
+    "/dev/test1     /                                   ext4        noauto,nofail,x-systemd.automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1"
+    "${FSTAB_GENERAL[@]}"
+)
+
+FSTAB_MINIMAL=(
+    "/dev/loop1     /foo/bar                            ext3        defaults 0 0"
+)
+
+FSTAB_DUPLICATE=(
+    "/dev/dup1     /       ext4 defaults 0 1"
+    "/dev/dup2     /       ext4 defaults,x-systemd.requires=foo.mount 0 2"
+)
+
+FSTAB_INVALID=(
+    # Ignored entries
+    "/dev/ignored1  /sys/fs/cgroup/foo                  ext4        defaults    0 0"
+    "/dev/ignored2  /sys/fs/selinux                     ext4        defaults    0 0"
+    "/dev/ignored3  /dev/console                        ext4        defaults    0 0"
+    "/dev/ignored4  /proc/kmsg                          ext4        defaults    0 0"
+    "/dev/ignored5  /proc/sys                           ext4        defaults    0 0"
+    "/dev/ignored6  /proc/sys/kernel/random/boot_id     ext4        defaults    0 0"
+    "/dev/ignored7  /run/host                           ext4        defaults    0 0"
+    "/dev/ignored8  /run/host/foo                       ext4        defaults    0 0"
+    "/dev/ignored9  /autofs                             autofs      defaults    0 0"
+    "/dev/invalid1  not-a-path                          ext4        defaults    0 0"
+    ""
+    "/dev/invalid1"
+    "                  "
+    "\\"
+    "$"
+)
+
+check_fstab_mount_units() {
+    local what where fstype opts passno unit
+    local item opt split_options filtered_options supp service device arg
+    local array_name="${1:?}"
+    local out_dir="${2:?}/normal"
+    # Get a reference to the array from its name
+    local -n fstab_entries="$array_name"
+
+    # Running the checks in a container is pretty much useless, since we don't
+    # generate any mounts, but don't skip the whole test to test the "skip"
+    # paths as well
+    in_container && return 0
+
+    for item in "${fstab_entries[@]}"; do
+        # Don't use a pipe here, as it would make the variables out of scope
+        read -r what where fstype opts _ passno <<< "$item"
+
+        # Skip non-initrd mounts in initrd
+        if in_initrd_host && ! [[ "$opts" =~ x-initrd.mount ]]; then
+            continue
+        fi
+
+        if [[ "$fstype" == swap ]]; then
+            unit="$(systemd-escape --suffix=swap --path "${what:?}")"
+            cat "$out_dir/$unit"
+
+            grep -qE "^What=$what$" "$out_dir/$unit"
+            if [[ "$opts" != defaults ]]; then
+                grep -qE "^Options=$opts$" "$out_dir/$unit"
+            fi
+
+            if [[ "$opts" =~ x-systemd.makefs ]]; then
+                service="$(systemd-escape --template=systemd-mkswap@.service --path "$what")"
+                test -e "$out_dir/$service"
+            fi
+
+            continue
+        fi
+
+        # If we're parsing host's fstab in initrd, prefix all mount targets
+        # with /sysroot
+        in_initrd_host && where="/sysroot${where:?}"
+        unit="$(systemd-escape --suffix=mount --path "${where:?}")"
+        cat "$out_dir/$unit"
+
+        # Check the general stuff
+        grep -qE "^What=$what$" "$out_dir/$unit"
+        grep -qE "^Where=$where$" "$out_dir/$unit"
+        if [[ -n "$fstype" ]] && [[ "$fstype" != auto ]]; then
+            grep -qE "^Type=$fstype$" "$out_dir/$unit"
+        fi
+        if [[ -n "$opts" ]] && [[ "$opts" != defaults ]]; then
+            # Some options are not propagated to the generated unit
+            filtered_options="$(opt_filter_consumed "$opts")"
+            if [[ "${filtered_options[*]}" != defaults ]]; then
+                grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit"
+            fi
+        fi
+
+        if ! [[ "$opts" =~ (noauto|x-systemd.(wanted-by=|required-by=|automount)) ]]; then
+            # We don't create the Requires=/Wants= symlinks for noauto/automount mounts
+            # and for mounts that use x-systemd.wanted-by=/required-by=
+            if in_initrd_host; then
+                if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+                    link_eq "$out_dir/initrd-fs.target.requires/$unit" "../$unit"
+                else
+                    link_eq "$out_dir/initrd-fs.target.wants/$unit" "../$unit"
+                fi
+            elif [[ "$fstype" =~ $NETWORK_FS_RX || "$opts" =~ _netdev ]]; then
+                # Units with network filesystems should have a Requires= dependency
+                # on the remote-fs.target, unless they use nofail or are an nfs "bg"
+                # mounts, in which case the dependency is downgraded to Wants=
+                if [[ "$opts" =~ nofail ]] || [[ "$fstype" =~ ^(nfs|nfs4) && "$opts" =~ bg ]]; then
+                    link_eq "$out_dir/remote-fs.target.wants/$unit" "../$unit"
+                else
+                    link_eq "$out_dir/remote-fs.target.requires/$unit" "../$unit"
+                fi
+            else
+                # Similarly, local filesystems should have a Requires= dependency on
+                # the local-fs.target, unless they use nofail, in which case the
+                # dependency is downgraded to Wants=. Rootfs is a special case,
+                # since we always ignore nofail there
+                if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then
+                    link_eq "$out_dir/local-fs.target.requires/$unit" "../$unit"
+                else
+                    link_eq "$out_dir/local-fs.target.wants/$unit" "../$unit"
+                fi
+            fi
+        fi
+
+        if [[ "${passno:=0}" -ne 0 ]]; then
+            # Generate systemd-fsck@.service dependencies, if applicable
+            if in_initrd && [[ "$where" == / || "$where" == /usr ]]; then
+                continue
+            fi
+
+            if [[ "$where" == / ]]; then
+                link_endswith "$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service"
+            else
+                service="$(systemd-escape --template=systemd-fsck@.service --path "$what")"
+                grep -qE "^After=$service$" "$out_dir/$unit"
+                if [[ "$where" == /usr ]]; then
+                    grep -qE "^Wants=$service$" "$out_dir/$unit"
+                else
+                    grep -qE "^Requires=$service$" "$out_dir/$unit"
+                fi
+            fi
+        fi
+
+        # Check various x-systemd options
+        #
+        # First, split them into an array to make splitting them even further
+        # easier
+        IFS="," read -ra split_options <<< "$opts"
+        # and process them one by one.
+        #
+        # Note: the "machinery" below might (and probably does) miss some
+        #       combinations of supported options, so tread carefully
+        for opt in "${split_options[@]}"; do
+            if [[ "$opt" =~ ^x-systemd.requires= ]]; then
+                service="$(opt_get_arg "$opt")"
+                grep -qE "^Requires=$service$" "$out_dir/$unit"
+                grep -qE "^After=$service$" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd.before= ]]; then
+                service="$(opt_get_arg "$opt")"
+                grep -qE "^Before=$service$" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd.after= ]]; then
+                service="$(opt_get_arg "$opt")"
+                grep -qE "^After=$service$" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd.wanted-by= ]]; then
+                service="$(opt_get_arg "$opt")"
+                if [[ "$where" == / ]]; then
+                    # This option is ignored for rootfs mounts
+                    (! link_eq "$out_dir/$service.wants/$unit" "../$unit")
+                else
+                    link_eq "$out_dir/$service.wants/$unit" "../$unit"
+                fi
+            elif [[ "$opt" =~ ^x-systemd.required-by= ]]; then
+                service="$(opt_get_arg "$opt")"
+                if [[ "$where" == / ]]; then
+                    # This option is ignored for rootfs mounts
+                    (! link_eq "$out_dir/$service.requires/$unit" "../$unit")
+                else
+                    link_eq "$out_dir/$service.requires/$unit" "../$unit"
+                fi
+            elif [[ "$opt" =~ ^x-systemd.requires-mounts-for= ]]; then
+                arg="$(opt_get_arg "$opt")"
+                grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit"
+            elif [[ "$opt" == x-systemd.device-bound ]]; then
+                # This is implied for fstab mounts
+                :
+            elif [[ "$opt" == x-systemd.automount ]]; then
+                # The $unit should have an accompanying automount unit
+                supp="$(systemd-escape --suffix=automount --path "$where")"
+                if [[ "$where" == / ]]; then
+                    # This option is ignored for rootfs mounts
+                    test ! -e "$out_dir/$supp"
+                    (! link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp")
+                else
+                    test -e "$out_dir/$supp"
+                    link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp"
+                fi
+            elif [[ "$opt" =~ ^x-systemd.idle-timeout= ]]; then
+                # The timeout applies to the automount unit, not the original
+                # mount one
+                arg="$(opt_get_arg "$opt")"
+                supp="$(systemd-escape --suffix=automount --path "$where")"
+                grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp"
+            elif [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then
+                arg="$(opt_get_arg "$opt")"
+                device="$(systemd-escape --suffix=device --path "$what")"
+                grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf"
+            elif [[ "$opt" == x-systemd.makefs ]]; then
+                service="$(systemd-escape --template=systemd-makefs@.service --path "$what")"
+                test -e "$out_dir/$service"
+                link_eq "$out_dir/${unit}.requires/$service" "../$service"
+            elif [[ "$opt" == x-systemd.rw-only ]]; then
+                grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit"
+            elif [[ "$opt" == x-systemd.growfs ]]; then
+                service="$(systemd-escape --template=systemd-growfs@.service --path "$where")"
+                link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service"
+            elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then
+                # We "convert" nfs bg mounts to fg, so we can do the job-control
+                # ourselves
+                grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit"
+                grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit"
+            elif [[ "$opt" =~ ^x-systemd\. ]]; then
+                echo >&2 "Unhandled mount option: $opt"
+                exit 1
+            fi
+        done
+    done
+}
+
+: "fstab-generator: regular"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Skip the rest when running in a container, as it makes little sense to check
+# initrd-related stuff there and fstab-generator might have a bit strange
+# behavior during certain tests, like https://github.com/systemd/systemd/issues/27156
+if in_container; then
+    echo "Running in a container, skipping the rest of the fstab-generator tests..."
+    exit 0
+fi
+
+# In this mode we treat the entries as "regular" ones
+: "fstab-generator: initrd - initrd fstab"
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null check_fstab_mount_units FSTAB_GENERAL "$OUT_DIR"
+
+# In this mode we prefix the mount target with /sysroot and ignore all mounts
+# that don't have the x-initrd.mount flag
+: "fstab-generator: initrd - host fstab"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR"
+
+# Check the default stuff that we (almost) always create in initrd
+: "fstab-generator: initrd default"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+test -e "$OUT_DIR/normal/sysroot.mount"
+test -e "$OUT_DIR/normal/systemd-fsck-root.service"
+link_eq "$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+link_eq "$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount"
+
+: "fstab-generator: run as systemd-sysroot-fstab-check in initrd"
+ln -svf "$GENERATOR_BIN" /tmp/systemd-sysroot-fstab-check
+(! /tmp/systemd-sysroot-fstab-check foo)
+(! SYSTEMD_IN_INITRD=0 /tmp/systemd-sysroot-fstab-check)
+printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB"
+SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB="$FSTAB" /tmp/systemd-sysroot-fstab-check
+
+: "fstab-generator: duplicate"
+printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB"
+cat "$FSTAB"
+(! SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR")
+
+: "fstab-generator: invalid"
+printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB"
+cat "$FSTAB"
+# Don't care about the exit code here
+SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" || :
+# No mounts should get created here
+[[ "$(find "$OUT_DIR" -name "*.mount" | wc -l)" -eq 0 ]]
+
+: "fstab-generator: kernel args - fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+
+: "fstab-generator: kernel args - rd.fstab=0"
+printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR")
+
+: "fstab-generator: kernel args - systemd.swap=0"
+printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB"
+cat "$FSTAB"
+SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="systemd.swap=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+# No swap units should get created here
+[[ "$(find "$OUT_DIR" -name "*.swap" | wc -l)" -eq 0 ]]
+
+# Possible TODO
+#   - combine the rootfs & usrfs arguments and mix them with fstab entries
+#   - systemd.volatile=
+: "fstab-generator: kernel args - root= + rootfstype= + rootflags="
+# shellcheck disable=SC2034
+EXPECTED_FSTAB=(
+    "/dev/disk/by-label/rootfs  /    ext4    noexec,ro   0 1"
+)
+CMDLINE="root=LABEL=rootfs rootfstype=ext4 rootflags=noexec"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+# The /proc/cmdline here is a dummy value to tell the in_initrd_host() function
+# we're parsing host's fstab, but it's all on the kernel cmdline instead
+SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB=/proc/cmdline check_fstab_mount_units EXPECTED_FSTAB "$OUT_DIR"
+
+# This is a very basic sanity test that involves manual checks, since adding it
+# to the check_fstab_mount_units() function would make it way too complex
+# (yet another possible TODO)
+: "fstab-generator: kernel args - mount.usr= + mount.usrfstype= + mount.usrflags="
+CMDLINE="mount.usr=UUID=be780f43-8803-4a76-9732-02ceda6e9808 mount.usrfstype=ext4 mount.usrflags=noexec,nodev"
+SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+cat "$OUT_DIR/normal/sysroot-usr.mount" "$OUT_DIR/normal/sysusr-usr.mount"
+# The general idea here is to mount the device to /sysusr/usr and then
+# bind-mount /sysusr/usr to /sysroot/usr
+grep -qE "^What=/dev/disk/by-uuid/be780f43-8803-4a76-9732-02ceda6e9808$" "$OUT_DIR/normal/sysusr-usr.mount"
+grep -qE "^Where=/sysusr/usr$" "$OUT_DIR/normal/sysusr-usr.mount"
+grep -qE "^Type=ext4$" "$OUT_DIR/normal/sysusr-usr.mount"
+grep -qE "^Options=noexec,nodev,ro$" "$OUT_DIR/normal/sysusr-usr.mount"
+link_eq "$OUT_DIR/normal/initrd-usr-fs.target.requires/sysusr-usr.mount" "../sysusr-usr.mount"
+grep -qE "^What=/sysusr/usr$" "$OUT_DIR/normal/sysroot-usr.mount"
+grep -qE "^Where=/sysroot/usr$" "$OUT_DIR/normal/sysroot-usr.mount"
+grep -qE "^Options=bind$" "$OUT_DIR/normal/sysroot-usr.mount"
+link_eq "$OUT_DIR/normal/initrd-fs.target.requires/sysroot-usr.mount" "../sysroot-usr.mount"
diff --git a/test/units/testsuite-81.getty-generator.sh b/test/units/testsuite-81.getty-generator.sh
new file mode 100755 (executable)
index 0000000..103e966
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+# Disable history expansion so we don't have to escape ! in strings below
+set +o histexpand
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-getty-generator"
+OUT_DIR="$(mktemp -d /tmp/getty-generator.XXX)"
+
+at_exit() {
+    rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+if in_container; then
+    # Do a limited test in a container, as writing to /dev is usually restrited
+    : "getty-generator: \$container_ttys env (container)"
+    # In a container we allow only /dev/pts/* ptys
+    PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+
+    # console-getty.service is always pulled in in containers
+    link_endswith "$OUT_DIR/normal/getty.target.wants/console-getty.service" "/lib/systemd/system/console-getty.service"
+    link_endswith "$OUT_DIR/normal/getty.target.wants/container-getty@0.service" "/lib/systemd/system/container-getty@.service"
+    test ! -e "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service"
+    test ! -h "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service"
+
+    exit 0
+fi
+
+DUMMY_ACTIVE_CONSOLES=(
+    "hvc99"
+    "xvc99"
+    "hvsi99"
+    "sclp_line99"
+    "ttysclp99"
+    "3270!tty99"
+    "dummy99"
+)
+DUMMY_INACTIVE_CONSOLES=(
+    "inactive99"
+    "xvc199"
+)
+DUMMY_CONSOLES=(
+    "${DUMMY_ACTIVE_CONSOLES[@]}"
+    "${DUMMY_INACTIVE_CONSOLES[@]}"
+)
+# Create a bunch of dummy consoles
+for console in "${DUMMY_CONSOLES[@]}"; do
+    mknod "/dev/$console" c 4 0
+done
+# Sneak in one "not-a-tty" console
+touch /dev/notatty99
+# Temporarily replace /sys/class/tty/console/active with our list of dummy
+# consoles so getty-generator can process them
+echo -ne "${DUMMY_ACTIVE_CONSOLES[@]}" /dev/notatty99  >/tmp/dummy-active-consoles
+mount -v --bind /tmp/dummy-active-consoles /sys/class/tty/console/active
+
+: "getty-generator: no arguments"
+# Sneak in an invalid value for $SYSTEMD_GETTY_AUTO to test things out
+PID1_ENVIRON="SYSTEMD_GETTY_AUTO=foo" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+for console in "${DUMMY_ACTIVE_CONSOLES[@]}"; do
+    unit="$(systemd-escape --template serial-getty@.service "$console")"
+    link_endswith "$OUT_DIR/normal/getty.target.wants/$unit" "/lib/systemd/system/serial-getty@.service"
+done
+for console in "${DUMMY_INACTIVE_CONSOLES[@]}" /dev/notatty99; do
+    unit="$(systemd-escape --template serial-getty@.service "$console")"
+    test ! -e "$OUT_DIR/normal/getty.target.wants/$unit"
+    test ! -h "$OUT_DIR/normal/getty.target.wants/$unit"
+done
+
+: "getty-generator: systemd.getty_auto=0 on kernel cmdline"
+SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+: "getty-generator: SYSTEMD_GETTY_AUTO=0 in PID1's environment"
+PID1_ENVIRON="SYSTEMD_GETTY_AUTO=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+# Cleanup
+umount /sys/class/tty/console/active
+rm -f "${DUMMY_CONSOLES[@]/#//dev/}" /dev/notatty99
diff --git a/test/units/testsuite-81.run-generator.sh b/test/units/testsuite-81.run-generator.sh
new file mode 100755 (executable)
index 0000000..9bd74ef
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-run-generator"
+OUT_DIR="$(mktemp -d /tmp/run-generator.XXX)"
+
+at_exit() {
+    rm -frv "${OUT_DIR:?}"
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+check_kernel_cmdline_target() {
+    local out_dir="${1:?}/normal"
+
+    cat "$out_dir/kernel-command-line.target"
+    grep -qE "^Requires=kernel-command-line.service$" "$out_dir/kernel-command-line.target"
+    grep -qE "^After=kernel-command-line.service$" "$out_dir/kernel-command-line.target"
+
+    link_eq "$out_dir/default.target" "kernel-command-line.target"
+}
+
+: "run-generator: empty cmdline"
+SYSTEMD_PROC_CMDLINE="" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+: "run-generator: single command"
+CMDLINE="systemd.run='echo hello world'"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_kernel_cmdline_target "$OUT_DIR"
+UNIT="$OUT_DIR/normal/kernel-command-line.service"
+cat "$UNIT"
+systemd-analyze verify --man=no --recursive-errors=no "$UNIT"
+grep -qE "^SuccessAction=exit$" "$UNIT"
+grep -qE "^FailureAction=exit$" "$UNIT"
+grep -qE "^ExecStart=echo hello world$" "$UNIT"
+
+: "run-generator: multiple commands + success/failure actions"
+ARGS=(
+    # These should be ignored
+    "systemd.run"
+    "systemd.run_success_action"
+    "systemd.run_failure_action"
+
+    # Set actions which we will overwrite later
+    "systemd.run_success_action="
+    "systemd.run_failure_action="
+
+    "systemd.run=/bin/false"
+    "systemd.run="
+    "systemd.run=/bin/true"
+    "systemd.run='echo this is a long string'"
+
+    "systemd.run_success_action=reboot"
+    "systemd.run_failure_action=poweroff-force"
+)
+CMDLINE="${ARGS[*]}"
+SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+check_kernel_cmdline_target "$OUT_DIR"
+UNIT="$OUT_DIR/normal/kernel-command-line.service"
+cat "$UNIT"
+systemd-analyze verify --man=no --recursive-errors=no "$UNIT"
+grep -qE "^SuccessAction=reboot$" "$UNIT"
+grep -qE "^FailureAction=poweroff-force$" "$UNIT"
+grep -qE "^ExecStart=/bin/false$" "$UNIT"
+grep -qE "^ExecStart=$" "$UNIT"
+grep -qE "^ExecStart=/bin/true$" "$UNIT"
+grep -qE "^ExecStart=echo this is a long string$" "$UNIT"
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..13c767e
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+: >/failed
+
+for script in "${0%.sh}".*.sh; do
+    echo "Running $script"
+    "./$script"
+done
+
+touch /testok
+rm /failed
diff --git a/test/units/testsuite-81.system-update-generator.sh b/test/units/testsuite-81.system-update-generator.sh
new file mode 100755 (executable)
index 0000000..8ee1fee
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/generator-utils.sh
+. "$(dirname "$0")/generator-utils.sh"
+
+GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-system-update-generator"
+OUT_DIR="$(mktemp -d /tmp/system-update-generator-generator.XXX)"
+
+at_exit() {
+    rm -frv "${OUT_DIR:?}" /system-update
+}
+
+trap at_exit EXIT
+
+test -x "${GENERATOR_BIN:?}"
+
+rm -f /system-update
+
+: "system-update-generator: no /system-update flag"
+run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]]
+
+: "system-update-generator: with /system-update flag"
+touch /system-update
+run_and_list "$GENERATOR_BIN" "$OUT_DIR"
+link_endswith "$OUT_DIR/early/default.target" "/lib/systemd/system/system-update.target"
+
+: "system-update-generator: kernel cmdline warnings"
+# We should warn if the default target is overridden on the kernel cmdline
+# by a runlevel or systemd.unit=, but still generate the symlink
+SYSTEMD_PROC_CMDLINE="systemd.unit=foo.bar 3" run_and_list "$GENERATOR_BIN" "$OUT_DIR" |& tee /tmp/system-update-generator.log
+link_endswith "$OUT_DIR/early/default.target" "/lib/systemd/system/system-update.target"
+grep -qE "Offline system update overridden .* systemd.unit=" /tmp/system-update-generator.log
+grep -qE "Offline system update overridden .* runlevel" /tmp/system-update-generator.log
diff --git a/tools/dump-auxv.py b/tools/dump-auxv.py
new file mode 100644 (file)
index 0000000..36f3b37
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+"""
+A program to parse auxv (e.g. /proc/self/auxv).
+
+By default, current arch is assumed, but options can be used to override the
+endianness and word size.
+"""
+
+import struct
+
+import click
+
+# From /usr/include/elf.h
+AT_AUXV = {
+    'AT_NULL' :          0,              # End of vector
+    'AT_IGNORE' :        1,              # Entry should be ignored
+    'AT_EXECFD' :        2,              # File descriptor of program
+    'AT_PHDR' :          3,              # Program headers for program
+    'AT_PHENT' :         4,              # Size of program header entry
+    'AT_PHNUM' :         5,              # Number of program headers
+    'AT_PAGESZ' :        6,              # System page size
+    'AT_BASE' :          7,              # Base address of interpreter
+    'AT_FLAGS' :         8,              # Flags
+    'AT_ENTRY' :         9,              # Entry point of program
+    'AT_NOTELF' :        10,             # Program is not ELF
+    'AT_UID' :           11,             # Real uid
+    'AT_EUID' :          12,             # Effective uid
+    'AT_GID' :           13,             # Real gid
+    'AT_EGID' :          14,             # Effective gid
+    'AT_CLKTCK' :        17,             # Frequency of times()
+
+    # Some more special a_type values describing the hardware.
+    'AT_PLATFORM' :      15,             # String identifying platform.
+    'AT_HWCAP' :         16,             # Machine-dependent hints about processor capabilities.
+
+    # This entry gives some information about the FPU initialization performed by the kernel.
+    'AT_FPUCW' :         18,             # Used FPU control word.
+
+    # Cache block sizes.
+    'AT_DCACHEBSIZE' :   19,             # Data cache block size.
+    'AT_ICACHEBSIZE' :   20,             # Instruction cache block size.
+    'AT_UCACHEBSIZE' :   21,             # Unified cache block size.
+
+    # A special ignored value for PPC, used by the kernel to control the
+    # interpretation of the AUXV. Must be > 16.
+    'AT_IGNOREPPC' :     22,             # Entry should be ignored.
+
+    'AT_SECURE' :        23,             # Boolean, was exec setuid-like?
+
+    'AT_BASE_PLATFORM' : 24,             # String identifying real platforms.
+
+    'AT_RANDOM' :        25,             # Address of 16 random bytes.
+
+    'AT_HWCAP2' :        26,             # More machine-dependent hints about processor capabilities.
+
+    'AT_EXECFN' :        31,             # Filename of executable.
+
+    # Pointer to the global system page used for system calls and other nice things.
+    'AT_SYSINFO' :       32,
+    'AT_SYSINFO_EHDR' :  33,
+
+    # Shapes of the caches.  Bits 0-3 contains associativity; bits 4-7 contains
+    # log2 of line size; mask those to get cache size.
+    'AT_L1I_CACHESHAPE' :    34,
+    'AT_L1D_CACHESHAPE' :    35,
+    'AT_L2_CACHESHAPE' :     36,
+    'AT_L3_CACHESHAPE' :     37,
+
+    # Shapes of the caches, with more room to describe them.
+    # GEOMETRY are comprised of cache line size in bytes in the bottom 16 bits
+    # and the cache associativity in the next 16 bits.
+    'AT_L1I_CACHESIZE' :     40,
+    'AT_L1I_CACHEGEOMETRY' : 41,
+    'AT_L1D_CACHESIZE' :     42,
+    'AT_L1D_CACHEGEOMETRY' : 43,
+    'AT_L2_CACHESIZE' :      44,
+    'AT_L2_CACHEGEOMETRY' :  45,
+    'AT_L3_CACHESIZE'     :  46,
+    'AT_L3_CACHEGEOMETRY' :  47,
+
+    'AT_MINSIGSTKSZ'      :  51,         # Stack needed for signal delivery
+}
+AT_AUXV_NAMES = {v:k for k,v in AT_AUXV.items()}
+
+@click.command(help=__doc__)
+@click.option('-b', '--big-endian', 'endian',
+              flag_value='>',
+              help='Input is big-endian')
+@click.option('-l', '--little-endian', 'endian',
+              flag_value='<',
+              help='Input is little-endian')
+@click.option('-3', '--32', 'field_width',
+              flag_value=32,
+              help='Input is 32-bit')
+@click.option('-6', '--64', 'field_width',
+              flag_value=64,
+              help='Input is 64-bit')
+@click.argument('file',
+                type=click.File(mode='rb'))
+def dump(endian, field_width, file):
+    data = file.read()
+
+    if field_width is None:
+        field_width = struct.calcsize('P') * 8
+    if endian is None:
+        endian = '@'
+
+    width = {32:'II', 64:'QQ'}[field_width]
+
+    format = f'{endian}{width}'
+    print(f'# {format=}')
+
+    seen_null = False
+
+    for item in struct.iter_unpack(format, data):
+        key, val = item
+        name = AT_AUXV_NAMES.get(key, f'unknown ({key})')
+        if name.endswith(('UID', 'GID')):
+            pref, fmt = '', 'd'
+        else:
+            pref, fmt = '0x', 'x'
+
+        if seen_null:
+            print('# trailing garbarbage after AT_NULL')
+
+        print(f'{name:18} = {pref}{val:{fmt}}')
+
+        if name == 'AT_NULL':
+            seen_null = True
+
+    if not seen_null:
+        print('# array not terminated with AT_NULL')
+
+if __name__ == '__main__':
+    dump()
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',
index 5a5dd725a13df74fce2af99d6b312759f04c6149..a54e74567e1fe0b06335c54c2f7c37135dcb99f7 100644 (file)
@@ -29,7 +29,9 @@ SuccessAction=reboot
 # reboot or some other action on its own.
 ConditionPathExists=|/system-update
 ConditionPathIsSymbolicLink=|/system-update
+ConditionPathExists=|/etc/system-update
+ConditionPathIsSymbolicLink=|/etc/system-update
 
 [Service]
 Type=oneshot
-ExecStart=rm -fv /system-update
+ExecStart=rm -fv /system-update /etc/system-update
diff --git a/units/systemd-confext.service b/units/systemd-confext.service
new file mode 100644 (file)
index 0000000..3b46eca
--- /dev/null
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Merge System Configuration Images into /etc/
+Documentation=man:systemd-confext.service(8)
+
+ConditionCapability=CAP_SYS_ADMIN
+ConditionDirectoryNotEmpty=|/run/confexts
+ConditionDirectoryNotEmpty=|/var/lib/confexts
+ConditionDirectoryNotEmpty=|/usr/local/lib/confexts
+ConditionDirectoryNotEmpty=|/usr/lib/confexts
+
+DefaultDependencies=no
+After=local-fs.target
+Before=sysinit.target systemd-tmpfiles-setup.service
+Conflicts=shutdown.target initrd-switch-root.target
+Before=shutdown.target initrd-switch-root.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=systemd-confext refresh
+ExecReload=systemd-confext refresh
+ExecStop=systemd-confext unmerge
+
+[Install]
+WantedBy=sysinit.target
index 565374698d76d8b8d67330e08ec6c010866a2fd4..a2d457fc027eaf236322e3aad7dbc679131b8274 100644 (file)
@@ -11,7 +11,7 @@
 Description=Process Core Dump Socket
 Documentation=man:systemd-coredump(8)
 DefaultDependencies=no
-Before=shutdown.target
+Before=shutdown.target systemd-sysctl.service
 Conflicts=shutdown.target
 
 [Socket]
index f8c26f5fbfa3eaed9cda32e90a4f2d00221f569e..5c11eba7c974d3d2cf0657c421d3f2d669244f95 100644 (file)
@@ -15,8 +15,7 @@ ConditionCapability=CAP_SYS_ADMIN
 ConditionDirectoryNotEmpty=|/etc/extensions
 ConditionDirectoryNotEmpty=|/run/extensions
 ConditionDirectoryNotEmpty=|/var/lib/extensions
-ConditionDirectoryNotEmpty=|/usr/local/lib/extensions
-ConditionDirectoryNotEmpty=|/usr/lib/extensions
+ConditionDirectoryNotEmpty=|/.extra/sysext
 
 DefaultDependencies=no
 After=local-fs.target
@@ -27,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]